From 086fccd997a7339187b0ed17ac0727e5f2d92321 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Thu, 14 May 2020 17:27:50 +0100 Subject: [PATCH 1/5] Move the package library into a separate module Hopefully this makes it a little easier for people to use in custom shells (and anything else where it might be useful). --- .../lua/rom/modules/main/cc/require.lua | 121 ++++++++++++++++++ .../computercraft/lua/rom/programs/shell.lua | 93 +------------- .../spec/modules/cc/shell/require_spec.lua | 79 ++++++++++++ 3 files changed, 205 insertions(+), 88 deletions(-) create mode 100644 src/main/resources/assets/computercraft/lua/rom/modules/main/cc/require.lua create mode 100644 src/test/resources/test-rom/spec/modules/cc/shell/require_spec.lua diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/require.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/require.lua new file mode 100644 index 000000000..a90e189b8 --- /dev/null +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/require.lua @@ -0,0 +1,121 @@ +--- This provides a pure Lua implementation of the builtin @{require} function +-- and @{package} library. +-- +-- Generally you do not need to use this module - it is injected into the +-- every program's environment. However, it may be useful when building a +-- custom shell or when running programs yourself. +-- +-- @module cc.require +-- @usage Construct the package and require function, and insert them into a +-- custom environment. +-- +-- local env = setmetatable({}, { __index = _ENV }) +-- local r = require "cc.require" +-- env.require, env.package = r.make(env, "/") + +local expect = require and require("cc.expect") or dofile("rom/modules/main/cc/expect.lua") +local expect = expect.expect + +local function preload(package) + return function(name) + if package.preload[name] then + return package.preload[name] + else + return nil, "no field package.preload['" .. name .. "']" + end + end +end + +local function from_file(package, env, dir) + return function(name) + local fname = string.gsub(name, "%.", "/") + local sError = "" + for pattern in string.gmatch(package.path, "[^;]+") do + local sPath = string.gsub(pattern, "%?", fname) + if sPath:sub(1, 1) ~= "/" then + sPath = fs.combine(dir, sPath) + end + if fs.exists(sPath) and not fs.isDir(sPath) then + local fnFile, sError = loadfile(sPath, nil, env) + if fnFile then + return fnFile, sPath + else + return nil, sError + end + else + if #sError > 0 then + sError = sError .. "\n " + end + sError = sError .. "no file '" .. sPath .. "'" + end + end + return nil, sError + end +end + +local function make_require(package) + local sentinel = {} + return function(name) + expect(1, name, "string") + + if package.loaded[name] == sentinel then + error("loop or previous error loading module '" .. name .. "'", 0) + end + + if package.loaded[name] then + return package.loaded[name] + end + + local sError = "module '" .. name .. "' not found:" + for _, searcher in ipairs(package.loaders) do + local loader = table.pack(searcher(name)) + if loader[1] then + package.loaded[name] = sentinel + local result = loader[1](name, table.unpack(loader, 2, loader.n)) + if result == nil then result = true end + + package.loaded[name] = result + return result + else + sError = sError .. "\n " .. loader[2] + end + end + error(sError, 2) + end +end + +--- Build an implementation of Lua's @{package} library, and a @{require} +-- function to load modules within it. +-- +-- @tparam table env The environment to load packages into. +-- @tparam string dir The directory that relative packages are loaded from. +-- @treturn function The new @{require} function. +-- @treturn table The new @{package} library. +local function make_package(env, dir) + expect(1, env, "table") + expect(2, dir, "string") + + local package = {} + package.loaded = { + _G = _G, + bit32 = bit32, + coroutine = coroutine, + math = math, + package = package, + string = string, + table = table, + } + package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua" + if turtle then + package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua" + elseif commands then + package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua" + end + package.config = "/\n;\n?\n!\n-" + package.preload = {} + package.loaders = { preload(package), from_file(package, env, dir) } + + return make_require(package), package +end + +return { make = make_package } diff --git a/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua b/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua index 843e28e71..0c6b5868b 100644 --- a/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua +++ b/src/main/resources/assets/computercraft/lua/rom/programs/shell.lua @@ -11,6 +11,7 @@ -- @module[module] shell local expect = dofile("rom/modules/main/cc/expect.lua").expect +local make_package = dofile("rom/modules/main/cc/require.lua").make local multishell = multishell local parentShell = shell @@ -28,94 +29,10 @@ local tCompletionInfo = parentShell and parentShell.getCompletionInfo() or {} local tProgramStack = {} local shell = {} --- @export -local function createShellEnv(sDir) - local tEnv = {} - tEnv.shell = shell - tEnv.multishell = multishell - - local package = {} - package.loaded = { - _G = _G, - bit32 = bit32, - coroutine = coroutine, - math = math, - package = package, - string = string, - table = table, - } - package.path = "?;?.lua;?/init.lua;/rom/modules/main/?;/rom/modules/main/?.lua;/rom/modules/main/?/init.lua" - if turtle then - package.path = package.path .. ";/rom/modules/turtle/?;/rom/modules/turtle/?.lua;/rom/modules/turtle/?/init.lua" - elseif commands then - package.path = package.path .. ";/rom/modules/command/?;/rom/modules/command/?.lua;/rom/modules/command/?/init.lua" - end - package.config = "/\n;\n?\n!\n-" - package.preload = {} - package.loaders = { - function(name) - if package.preload[name] then - return package.preload[name] - else - return nil, "no field package.preload['" .. name .. "']" - end - end, - function(name) - local fname = string.gsub(name, "%.", "/") - local sError = "" - for pattern in string.gmatch(package.path, "[^;]+") do - local sPath = string.gsub(pattern, "%?", fname) - if sPath:sub(1, 1) ~= "/" then - sPath = fs.combine(sDir, sPath) - end - if fs.exists(sPath) and not fs.isDir(sPath) then - local fnFile, sError = loadfile(sPath, nil, tEnv) - if fnFile then - return fnFile, sPath - else - return nil, sError - end - else - if #sError > 0 then - sError = sError .. "\n " - end - sError = sError .. "no file '" .. sPath .. "'" - end - end - return nil, sError - end, - } - - local sentinel = {} - local function require(name) - expect(1, name, "string") - if package.loaded[name] == sentinel then - error("loop or previous error loading module '" .. name .. "'", 0) - end - if package.loaded[name] then - return package.loaded[name] - end - - local sError = "module '" .. name .. "' not found:" - for _, searcher in ipairs(package.loaders) do - local loader = table.pack(searcher(name)) - if loader[1] then - package.loaded[name] = sentinel - local result = loader[1](name, table.unpack(loader, 2, loader.n)) - if result == nil then result = true end - - package.loaded[name] = result - return result - else - sError = sError .. "\n " .. loader[2] - end - end - error(sError, 2) - end - - tEnv.package = package - tEnv.require = require - - return tEnv +local function createShellEnv(dir) + local env = { shell = shell, multishell = multishell } + env.require, env.package = make_package(env, dir) + return env end -- Colours diff --git a/src/test/resources/test-rom/spec/modules/cc/shell/require_spec.lua b/src/test/resources/test-rom/spec/modules/cc/shell/require_spec.lua new file mode 100644 index 000000000..721476f9d --- /dev/null +++ b/src/test/resources/test-rom/spec/modules/cc/shell/require_spec.lua @@ -0,0 +1,79 @@ +describe("cc.require", function() + local r = require "cc.require" + local function mk() + local env = setmetatable({}, { __index = _ENV }) + env.require, env.package = r.make({}, "/test-files/modules") + return env.require, env.package + end + + local function setup(path, contents) + fs.delete("/test-files/modules") + io.open(path, "w"):write(contents):close() + end + + describe("require", function() + it("errors on recursive modules", function() + local require, package = mk() + package.preload.pkg = function() require "pkg" end + expect.error(require, "pkg"):eq("loop or previous error loading module 'pkg'") + end) + + it("supplies the current module name", function() + local require, package = mk() + package.preload.pkg = table.pack + expect(require("pkg")):same { n = 1, "pkg" } + end) + + it("returns true instead of nil", function() + local require, package = mk() + package.preload.pkg = function() return nil end + expect(require("pkg")):eq(true) + end) + + it("returns a constant value", function() + local require, package = mk() + package.preload.pkg = function() return {} end + expect(require("pkg")):eq(require("pkg")) + end) + + it("returns an error on not-found modules", function() + local require, package = mk() + package.path = "/?;/?.lua" + expect.error(require, "pkg"):eq( + "module 'pkg' not found:\n" .. + " no field package.preload['pkg']\n" .. + " no file '/pkg'\n" .. + " no file '/pkg.lua'") + end) + end) + + describe("the file loader", function() + local function get(path) + local require, package = mk() + if path then package.path = path end + return require + end + + it("works on absolute paths", function() + local require = get("/test-files/?.lua") + setup("test-files/some_module.lua", "return 123") + expect(require("some_module")):eq(123) + end) + + it("works on relative paths", function() + local require = get("?.lua") + setup("test-files/modules/some_module.lua", "return 123") + expect(require("some_module")):eq(123) + end) + + it("fails on syntax errors", function() + local require = get("?.lua") + setup("test-files/modules/some_module.lua", "1") + expect.error(require, "some_module"):str_match( + "^module 'some_module' not found:\n" .. + " no field package.preload%['some_module'%]\n" .. + " [^:]*some_module.lua:1: unexpected symbol near '1'$" + ) + end) + end) +end) From 96e7b60285a771ddaeecd20985cdb88904ebadb8 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Thu, 14 May 2020 19:11:57 +0100 Subject: [PATCH 2/5] Display function arguments and positions in the REPL - cc.pretty.pretty now accepts two additional options: - function_args: Show function arguments - function_source: Show where functions are defined. - Expose the two options as lua.* settings (defaulting function_args to true, and function_source to false). These are then used in the Lua REPL. Closes #361 --- .../assets/computercraft/lua/bios.lua | 12 ++++ .../lua/rom/modules/main/cc/pretty.lua | 59 ++++++++++++++++--- .../computercraft/lua/rom/programs/lua.lua | 5 +- .../test-rom/spec/modules/cc/pretty_spec.lua | 19 +++++- 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/src/main/resources/assets/computercraft/lua/bios.lua b/src/main/resources/assets/computercraft/lua/bios.lua index 55688a4f2..4573f9826 100644 --- a/src/main/resources/assets/computercraft/lua/bios.lua +++ b/src/main/resources/assets/computercraft/lua/bios.lua @@ -938,11 +938,23 @@ settings.define("motd.path", { description = [[The path to load random messages from. Should be a colon (":") separated string of file paths.]], type = "string", }) + settings.define("lua.warn_against_use_of_local", { default = true, description = [[Print a message when input in the Lua REPL starts with the word 'local'. Local variables defined in the Lua REPL are be inaccessable on the next input.]], type = "boolean", }) +settings.define("lua.function_args", { + default = true, + description = "Show function arguments when printing functions.", + type = "boolean", +}) +settings.define("lua.function_source", { + default = false, + description = "Show where a function was defined when printing functions.", + type = "boolean", +}) + if term.isColour() then settings.define("bios.use_multishell", { default = true, diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua index 65718f949..33fce2c7a 100644 --- a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua @@ -19,8 +19,12 @@ -- local pretty = require "cc.pretty" -- pretty.write(pretty.group(pretty.text("hello") .. pretty.space_line .. pretty.text("world"))) -local expect = require "cc.expect".expect -local type, getmetatable, setmetatable, colours, str_write = type, getmetatable, setmetatable, colours, write +local expect = require "cc.expect" +local expect, field = expect.expect, expect.field + +local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring +local debug_info = type(debug) == "table" and type(debug.getinfo) == "function" and debug.getinfo +local debug_local = type(debug) == "table" and type(debug.getlocal) == "function" and debug.getlocal --- @{table.insert} alternative, but with the length stored inline. local function append(out, value) @@ -343,13 +347,38 @@ local function key_compare(a, b) return false end -local function pretty_impl(obj, tracking) +local function show_function(fn, options) + local info = debug_info and debug_info(fn, "Su") + + -- Include function source position if available + local name + if options.function_source and info and info.short_src and info.linedefined and info.linedefined >= 1 then + name = "function<" .. info.short_src .. ":" .. info.linedefined .. ">" + else + name = tostring(fn) + end + + -- Include arguments if a Lua function and if available. Lua will report "C" + -- functions as variadic. + if options.function_args and info and info.what == "Lua" and info.nparams and debug_local then + local args = {} + for i = 1, info.nparams do args[i] = debug_local(fn, i) or "?" end + if info.isvararg then args[#args + 1] = "..." end + name = name .. "(" .. table.concat(args, ", ") .. ")" + end + + return name +end + +local function pretty_impl(obj, options, tracking) local obj_type = type(obj) if obj_type == "string" then local formatted = ("%q"):format(obj):gsub("\\\n", "\\n") return text(formatted, colours.red) elseif obj_type == "number" then return text(tostring(obj), colours.magenta) + elseif obj_type == "function" then + return text(show_function(obj, options), colours.lightGrey) elseif obj_type ~= "table" or tracking[obj] then return text(tostring(obj), colours.lightGrey) elseif getmetatable(obj) ~= nil and getmetatable(obj).__tostring then @@ -371,15 +400,15 @@ local function pretty_impl(obj, tracking) local v = obj[k] local ty = type(k) if ty == "number" and k % 1 == 0 and k >= 1 and k <= length then - append(doc, pretty_impl(v, tracking)) + append(doc, pretty_impl(v, options, tracking)) elseif ty == "string" and not keywords[k] and k:match("^[%a_][%a%d_]*$") then append(doc, text(k .. " = ")) - append(doc, pretty_impl(v, tracking)) + append(doc, pretty_impl(v, options, tracking)) else append(doc, obracket) - append(doc, pretty_impl(k, tracking)) + append(doc, pretty_impl(k, options, tracking)) append(doc, cbracket) - append(doc, pretty_impl(v, tracking)) + append(doc, pretty_impl(v, options, tracking)) end end @@ -393,12 +422,24 @@ end -- This can then be rendered with @{write} or @{print}. -- -- @param obj The object to pretty-print. +-- @tparam[opt] { function_args = boolean, function_source = boolean } options +-- Controls how various properties are displayed. +-- - `function_args`: Show the arguments to a function if known (`false` by default). +-- - `function_source: Show where the function was defined, instead of +-- `function: xxxxxxxx` (`false` by default). -- @treturn Doc The object formatted as a document. -- @usage Display a table on the screen -- local pretty = require "cc.pretty" -- pretty.print(pretty.pretty({ 1, 2, 3 })) -local function pretty(obj) - return pretty_impl(obj, {}) +local function pretty(obj, options) + expect(2, options, "table", "nil") + options = options or {} + + local actual_options = { + function_source = field(options, "function_source", "boolean", "nil") or false, + function_args = field(options, "function_args", "boolean", "nil") or false, + } + return pretty_impl(obj, actual_options, {}) end return { diff --git a/src/main/resources/assets/computercraft/lua/rom/programs/lua.lua b/src/main/resources/assets/computercraft/lua/rom/programs/lua.lua index 7ab9bda42..7418a017f 100644 --- a/src/main/resources/assets/computercraft/lua/rom/programs/lua.lua +++ b/src/main/resources/assets/computercraft/lua/rom/programs/lua.lua @@ -95,7 +95,10 @@ while bRunning do local n = 1 while n < tResults.n or n <= nForcePrint do local value = tResults[n + 1] - local ok, serialised = pcall(pretty.pretty, value) + local ok, serialised = pcall(pretty.pretty, value, { + function_args = settings.get("lua.function_args"), + function_source = settings.get("lua.function_source"), + }) if ok then pretty.print(serialised) else diff --git a/src/test/resources/test-rom/spec/modules/cc/pretty_spec.lua b/src/test/resources/test-rom/spec/modules/cc/pretty_spec.lua index 2949c1a75..174751606 100644 --- a/src/test/resources/test-rom/spec/modules/cc/pretty_spec.lua +++ b/src/test/resources/test-rom/spec/modules/cc/pretty_spec.lua @@ -165,7 +165,7 @@ describe("cc.pretty", function() describe("pretty", function() -- We make use of "render" here, as it's considerably easier than checking against the actual structure. -- However, it does also mean our tests are less unit-like. - local function pretty(x, width) return pp.render(pp.pretty(x), width) end + local function pretty(x, width, options) return pp.render(pp.pretty(x, options), width) end describe("tables", function() it("displays empty tables", function() @@ -201,8 +201,21 @@ describe("cc.pretty", function() expect(pretty("hello\nworld")):eq('"hello\\nworld"') end) - it("shows functions", function() - expect(pretty(pretty)):eq(tostring(pretty)) + describe("functions", function() + it("shows functions", function() + expect(pretty(pretty)):eq(tostring(pretty)) + end) + + it("shows function arguments", function() + local f = function(a, ...) end + expect(pretty(f, nil, { function_args = true })):eq(tostring(f) .. "(a, ...)") + end) + + it("shows the function source", function() + local f = function(a, ...) end + expect(pretty(f, nil, { function_source = true })) + :str_match("^function<.*pretty_spec%.lua:%d+>$") + end) end) end) end) From c6b6b4479cbfc00173ac3701bfb99bda60faf5b9 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Thu, 14 May 2020 19:23:47 +0100 Subject: [PATCH 3/5] Update changelog and fix doc typo --- .../resources/assets/computercraft/lua/rom/help/changelog.txt | 2 ++ .../resources/assets/computercraft/lua/rom/help/whatsnew.txt | 2 ++ .../assets/computercraft/lua/rom/modules/main/cc/pretty.lua | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/computercraft/lua/rom/help/changelog.txt b/src/main/resources/assets/computercraft/lua/rom/help/changelog.txt index 3d9f00769..6e6804037 100644 --- a/src/main/resources/assets/computercraft/lua/rom/help/changelog.txt +++ b/src/main/resources/assets/computercraft/lua/rom/help/changelog.txt @@ -7,6 +7,8 @@ * The Lua REPL warns when declaring locals (lupus590, exerro) * Add config to allow using command computers in survival. * Add fs.isDriveRoot - checks if a path is the root of a drive. +* `cc.pretty` can now display a function's arguments and where it was defined. The Lua REPL will show arguments by default. +* Move the shell's `require`/`package` implementation to a separate `cc.require` module. And several bug fixes: * Fix io.lines not accepting arguments. diff --git a/src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt b/src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt index a3cb561de..772f17738 100644 --- a/src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt +++ b/src/main/resources/assets/computercraft/lua/rom/help/whatsnew.txt @@ -7,6 +7,8 @@ New features in CC: Tweaked 1.88.0 * The Lua REPL warns when declaring locals (lupus590, exerro) * Add config to allow using command computers in survival. * Add fs.isDriveRoot - checks if a path is the root of a drive. +* `cc.pretty` can now display a function's arguments and where it was defined. The Lua REPL will show arguments by default. +* Move the shell's `require`/`package` implementation to a separate `cc.require` module. And several bug fixes: * Fix io.lines not accepting arguments. diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua index 33fce2c7a..b934aa995 100644 --- a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/pretty.lua @@ -425,7 +425,7 @@ end -- @tparam[opt] { function_args = boolean, function_source = boolean } options -- Controls how various properties are displayed. -- - `function_args`: Show the arguments to a function if known (`false` by default). --- - `function_source: Show where the function was defined, instead of +-- - `function_source`: Show where the function was defined, instead of -- `function: xxxxxxxx` (`false` by default). -- @treturn Doc The object formatted as a document. -- @usage Display a table on the screen From 161a5b47077b3e3dfb9652cca55c5431a127735c Mon Sep 17 00:00:00 2001 From: SquidDev Date: Fri, 15 May 2020 10:03:47 +0100 Subject: [PATCH 4/5] Document and test the redstone library The tests may be a little agressive, but I wanted some sanity checks for the 1.15 API rewrite. --- doc/stub/redstone.lua | 114 +++++++++++++++++- illuaminate.sexp | 5 +- .../computercraft/core/apis/RedstoneAPI.java | 24 ++-- .../rom/modules/main/cc/shell/completion.lua | 12 +- .../test-rom/spec/apis/redstone_spec.lua | 92 ++++++++++++++ 5 files changed, 221 insertions(+), 26 deletions(-) create mode 100644 src/test/resources/test-rom/spec/apis/redstone_spec.lua diff --git a/doc/stub/redstone.lua b/doc/stub/redstone.lua index 217d41766..d19a80487 100644 --- a/doc/stub/redstone.lua +++ b/doc/stub/redstone.lua @@ -1,14 +1,120 @@ +--[[- Interact with redstone attached to this computer. + +The @{redstone} library exposes three "types" of redstone control: + - Binary input/output (@{setOutput}/@{getInput}): These simply check if a + redstone wire has any input or output. A signal strength of 1 and 15 are + treated the same. + - Analogue input/output (@{setAnalogueOutput}/@{getAnalogueInput}): These + work with the actual signal strength of the redstone wired, from 0 to 15. + - Bundled cables (@{setBundledOutput}/@{getBundledInput}): These interact with + "bundled" cables, such as those from Project:Red. These allow you to send + 16 separate on/off signals. Each channel corresponds to a colour, with the + first being @{colors.white} and the last @{colors.black}. + +Whenever a redstone input changes, a `redstone` event will be fired. This may +be used in or + +This module may also be referred to as `rs`. For example, one may call +`rs.getSides()` instead of @{redstone.getSides}. + +@module redstone +@usage Toggle the redstone signal above the computer every 0.5 seconds. + + while true do + redstone.setOutput("top", not redstone.getOutput("top")) + sleep(0.5) + end +@usage Mimic a redstone comparator in [subtraction mode][comparator]. + + while true do + local rear = rs.getAnalogueInput("back") + local sides = math.max(rs.getAnalogueInput("left"), rs.getAnalogueInput("right")) + rs.setAnalogueOutput("front", math.max(rear - sides, 0)) + + os.pullEvent("redstone") -- Wait for a change to inputs. + end + +[comparator]: https://minecraft.gamepedia.com/Redstone_Comparator#Subtract_signal_strength "Redstone Comparator on the Minecraft wiki." +]] + +--- Returns a table containing the six sides of the computer. Namely, "top", +-- "bottom", "left", "right", "front" and "back". +-- +-- @treturn { string... } A table of valid sides. function getSides() end + +--- Turn the redstone signal of a specific side on or off. +-- +-- @tparam string side The side to set. +-- @tparam boolean on Whether the redstone signal should be on or off. When on, +-- a signal strength of 15 is emitted. function setOutput(side, on) end + +--- Get the current redstone output of a specific side. +-- +-- @tparam string side The side to get. +-- @treturn boolean Whether the redstone output is on or off. +-- @see setOutput function getOutput(side) end + +--- Get the current redstone input of a specific side. +-- +-- @tparam string side The side to get. +-- @treturn boolean Whether the redstone input is on or off. function getInput(side) end -function setBundledOutput(side, output) end -function getBundledOutput(side) end -function getBundledInput(side) end -function testBundledInput(side, mask) end + +--- Set the redstone signal strength for a specific side. +-- +-- @tparam string side The side to set. +-- @tparam number value The signal strength, between 0 and 15. +-- @throws If `value` is not between 0 and 15. function setAnalogOutput(side, value) end setAnalogueOutput = setAnalogOutput + +--- Get the redstone output signal strength for a specific side. +-- +-- @tparam string side The side to get. +-- @treturn number The output signal strength, between 0 and 15. +-- @see setAnalogueOutput function getAnalogOutput(sid) end getAnalogueOutput = getAnalogOutput + +--- Get the redstone input signal strength for a specific side. +-- +-- @tparam string side The side to get. +-- @treturn number The input signal strength, between 0 and 15. function getAnalogInput(side) end getAnalogueInput = getAnalogInput + +--- Set the bundled cable output for a specific side. +-- +-- @tparam string side The side to set. +-- @tparam number The colour bitmask to set. +-- @see colors.subtract For removing a colour from the bitmask. +-- @see colors.combine For adding a colour to the bitmask. +function setBundledOutput(side, output) end + +--- Get the bundled cable output for a specific side. +-- +-- @tparam string side The side to get. +-- @treturn number The bundled cable's output. +function getBundledOutput(side) end + +--- Get the bundled cable input for a specific side. +-- +-- @tparam string side The side to get. +-- @treturn number The bundled cable's input. +-- @see testBundledInput To determine if a specific colour is set. +function getBundledInput(side) end + +--- Determine if a specific combination of colours are on for the given side. +-- +-- @tparam string side The side to test. +-- @tparam number mask The mask to test. +-- @see getBundledInput +-- @see colors.combine For adding a colour to the bitmask. +-- @usage Check if @{colors.white} and @{colors.black} are on for above the +-- computer. +-- +-- print(redstone.testBundledInput("top", colors.combine(colors.white, colors.black))) +function testBundledInput(side, mask) end diff --git a/illuaminate.sexp b/illuaminate.sexp index 631a9eb58..769eba175 100644 --- a/illuaminate.sexp +++ b/illuaminate.sexp @@ -73,12 +73,10 @@ (/doc/stub/fs.lua /doc/stub/http.lua /doc/stub/os.lua - /doc/stub/redstone.lua /doc/stub/term.lua /doc/stub/turtle.lua /src/main/resources/*/computercraft/lua/rom/apis/io.lua - /src/main/resources/*/computercraft/lua/rom/apis/window.lua - /src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua) + /src/main/resources/*/computercraft/lua/rom/apis/window.lua) (linters -doc:undocumented -doc:undocumented-arg)) @@ -87,7 +85,6 @@ (/src/main/resources/*/computercraft/lua/rom/apis/textutils.lua /src/main/resources/*/computercraft/lua/rom/modules/main/cc/completion.lua /src/main/resources/*/computercraft/lua/rom/modules/main/cc/shell/completion.lua - /src/main/resources/*/computercraft/lua/rom/programs/advanced/multishell.lua /src/main/resources/*/computercraft/lua/rom/programs/shell.lua) (linters -doc:unresolved-reference)) diff --git a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java index 1a987d519..fbb8feffa 100644 --- a/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/RedstoneAPI.java @@ -16,11 +16,11 @@ public class RedstoneAPI implements ILuaAPI { - private IAPIEnvironment m_environment; + private final IAPIEnvironment environment; public RedstoneAPI( IAPIEnvironment environment ) { - m_environment = environment; + this.environment = environment; } @Override @@ -63,31 +63,31 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O // setOutput ComputerSide side = parseSide( args ); boolean output = getBoolean( args, 1 ); - m_environment.setOutput( side, output ? 15 : 0 ); + environment.setOutput( side, output ? 15 : 0 ); return null; } case 2: // getOutput - return new Object[] { m_environment.getOutput( parseSide( args ) ) > 0 }; + return new Object[] { environment.getOutput( parseSide( args ) ) > 0 }; case 3: // getInput - return new Object[] { m_environment.getInput( parseSide( args ) ) > 0 }; + return new Object[] { environment.getInput( parseSide( args ) ) > 0 }; case 4: { // setBundledOutput ComputerSide side = parseSide( args ); int output = getInt( args, 1 ); - m_environment.setBundledOutput( side, output ); + environment.setBundledOutput( side, output ); return null; } case 5: // getBundledOutput - return new Object[] { m_environment.getBundledOutput( parseSide( args ) ) }; + return new Object[] { environment.getBundledOutput( parseSide( args ) ) }; case 6: // getBundledInput - return new Object[] { m_environment.getBundledInput( parseSide( args ) ) }; + return new Object[] { environment.getBundledInput( parseSide( args ) ) }; case 7: { // testBundledInput ComputerSide side = parseSide( args ); int mask = getInt( args, 1 ); - int input = m_environment.getBundledInput( side ); + int input = environment.getBundledInput( side ); return new Object[] { (input & mask) == mask }; } case 8: @@ -100,15 +100,15 @@ public Object[] callMethod( @Nonnull ILuaContext context, int method, @Nonnull O { throw new LuaException( "Expected number in range 0-15" ); } - m_environment.setOutput( side, output ); + environment.setOutput( side, output ); return null; } case 10: case 11: // getAnalogOutput/getAnalogueOutput - return new Object[] { m_environment.getOutput( parseSide( args ) ) }; + return new Object[] { environment.getOutput( parseSide( args ) ) }; case 12: case 13: // getAnalogInput/getAnalogueInput - return new Object[] { m_environment.getInput( parseSide( args ) ) }; + return new Object[] { environment.getInput( parseSide( args ) ) }; default: return null; } diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua index 6be54fe2f..91d9ea7a4 100644 --- a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua +++ b/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/shell/completion.lua @@ -141,12 +141,12 @@ return { program = program, -- Re-export various other functions - help = wrap(help.completeTopic), - choice = wrap(completion.choice), - peripheral = wrap(completion.peripheral), - side = wrap(completion.side), - setting = wrap(completion.setting), - command = wrap(completion.command), + help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function. + choice = wrap(completion.choice), --- Wraps @{cc.completion.choice} as a @{build} compatible function. + peripheral = wrap(completion.peripheral), --- Wraps @{cc.completion.peripheral} as a @{build} compatible function. + side = wrap(completion.side), --- Wraps @{cc.completion.side} as a @{build} compatible function. + setting = wrap(completion.setting), --- Wraps @{cc.completion.setting} as a @{build} compatible function. + command = wrap(completion.command), --- Wraps @{cc.completion.command} as a @{build} compatible function. build = build, } diff --git a/src/test/resources/test-rom/spec/apis/redstone_spec.lua b/src/test/resources/test-rom/spec/apis/redstone_spec.lua new file mode 100644 index 000000000..17d8acab0 --- /dev/null +++ b/src/test/resources/test-rom/spec/apis/redstone_spec.lua @@ -0,0 +1,92 @@ +local function it_side(func, ...) + local arg = table.pack(...) + it("requires a specific side", function() + expect.error(func, 0):eq("bad argument #1 (string expected, got number)") + expect.error(func, "blah", table.unpack(arg)):eq("Invalid side.") + + func("top", table.unpack(arg)) + func("Top", table.unpack(arg)) + func("toP", table.unpack(arg)) + end) +end + +describe("The redstone library", function() + describe("redstone.setOutput", function() + it_side(redstone.setOutput, false) + + it("sets the output strength correctly", function() + redstone.setOutput("top", false) + expect(redstone.getAnalogueOutput("top")):eq(0) + + redstone.setOutput("top", true) + expect(redstone.getAnalogueOutput("top")):eq(15) + end) + end) + + describe("redstone.getOutput", function() + it_side(redstone.getOutput) + + it("gets the output strength correctly", function() + redstone.setAnalogueOutput("top", 0) + expect(redstone.getOutput("top")):eq(false) + + redstone.setAnalogueOutput("top", 1) + expect(redstone.getOutput("top")):eq(true) + + redstone.setAnalogueOutput("top", 15) + expect(redstone.getOutput("top")):eq(true) + end) + end) + + describe("redstone.getInput", function() + it_side(redstone.getInput) + end) + + describe("redstone.setAnalogueOutput", function() + it_side(redstone.setAnalogueOutput, 0) + + it("checks the strength parameter", function() + expect.error(redstone.setAnalogueOutput, "top", true):eq("bad argument #2 (number expected, got boolean)") + expect.error(redstone.setAnalogueOutput, "top", 0 / 0):eq("bad argument #2 (number expected, got nan)") + expect.error(redstone.setAnalogueOutput, "top", math.huge):eq("bad argument #2 (number expected, got inf)") + expect.error(redstone.setAnalogueOutput, "top", -1):eq("Expected number in range 0-15") + expect.error(redstone.setAnalogueOutput, "top", 16):eq("Expected number in range 0-15") + end) + end) + + describe("redstone.getAnalogueOutput", function() + it_side(redstone.getAnalogueOutput) + end) + + describe("redstone.getAnalogueInput", function() + it_side(redstone.getAnalogueInput) + end) + + describe("redstone.setBundledOutput", function() + it_side(redstone.setBundledOutput, 0) + + it("checks the mask parameter", function() + expect.error(redstone.setBundledOutput, "top", true):eq("bad argument #2 (number expected, got boolean)") + expect.error(redstone.setBundledOutput, "top", 0 / 0):eq("bad argument #2 (number expected, got nan)") + expect.error(redstone.setBundledOutput, "top", math.huge):eq("bad argument #2 (number expected, got inf)") + end) + end) + + describe("redstone.getBundledOutput", function() + it_side(redstone.getBundledOutput) + end) + + describe("redstone.getBundledInput", function() + it_side(redstone.getBundledInput) + end) + + describe("redstone.testBundledInput", function() + it_side(redstone.testBundledInput, 0) + + it("checks the mask parameter", function() + expect.error(redstone.testBundledInput, "top", true):eq("bad argument #2 (number expected, got boolean)") + expect.error(redstone.testBundledInput, "top", 0 / 0):eq("bad argument #2 (number expected, got nan)") + expect.error(redstone.testBundledInput, "top", math.huge):eq("bad argument #2 (number expected, got inf)") + end) + end) +end) From 13de2c4dd0143da86cf58c3ec7b3d99612b039ff Mon Sep 17 00:00:00 2001 From: SquidDev Date: Fri, 15 May 2020 10:28:35 +0100 Subject: [PATCH 5/5] Fix location of cc.require Yay for 1.12->1.13 changes! --- src/main/resources/data/computercraft/lua/rom/help/changelog.txt | 1 + src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt | 1 + .../computercraft/lua/rom/modules/main/cc/require.lua | 0 3 files changed, 2 insertions(+) rename src/main/resources/{assets => data}/computercraft/lua/rom/modules/main/cc/require.lua (100%) diff --git a/src/main/resources/data/computercraft/lua/rom/help/changelog.txt b/src/main/resources/data/computercraft/lua/rom/help/changelog.txt index 4cf0ce6b0..5616c8bc0 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/changelog.txt +++ b/src/main/resources/data/computercraft/lua/rom/help/changelog.txt @@ -13,6 +13,7 @@ And several bug fixes: * Fix io.lines not accepting arguments. * Fix settings.load using an unknown global (MCJack123). +* Prevent computers scanning peripherals twice. # New features in CC: Tweaked 1.87.1 diff --git a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt index 772f17738..d99423023 100644 --- a/src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt +++ b/src/main/resources/data/computercraft/lua/rom/help/whatsnew.txt @@ -13,5 +13,6 @@ New features in CC: Tweaked 1.88.0 And several bug fixes: * Fix io.lines not accepting arguments. * Fix settings.load using an unknown global (MCJack123). +* Prevent computers scanning peripherals twice. Type "help changelog" to see the full version history. diff --git a/src/main/resources/assets/computercraft/lua/rom/modules/main/cc/require.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua similarity index 100% rename from src/main/resources/assets/computercraft/lua/rom/modules/main/cc/require.lua rename to src/main/resources/data/computercraft/lua/rom/modules/main/cc/require.lua