diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..4048cba --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,28 @@ +name: Computercraft + +on: [push] + +jobs: + build: + strategy: + matrix: + include: + - python: "3.11.3" + - python: "3.10.11" + - python: "3.9.16" + - python: "3.8.16" + + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + pip install pytest + pip install -r requirements.txt + - name: Test with pytest + run: | + pytest -vv diff --git a/.gitignore b/.gitignore index e8a20a1..ad2598e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,9 @@ __pycache__/ __pypackages__/ +.pytest_cache/ *.pyc /build/ /*.egg-info/ /dist/ /todo.txt +/copy_programs.fish diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3a2c372 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# docker build --tag neumond/python-computer-craft: . +# docker run -it -p 8080:8080 neumond/python-computer-craft: + +FROM python:3.8-alpine +RUN apk add --update \ + gcc musl-dev \ + build-base + +WORKDIR /wheels +ENV PIP_DISABLE_PIP_VERSION_CHECK=1 +RUN pip install wheel +RUN pip download computercraft +RUN pip wheel -w . computercraft +RUN ls -l + +FROM python:3.8-alpine + +WORKDIR /wheels +COPY --from=0 /wheels/*.whl ./wheels/ +RUN pip install --no-index -f ./wheels computercraft +WORKDIR /home +ENV PYTHONDONTWRITEBYTECODE=1 +EXPOSE 8080/tcp +ENTRYPOINT [ "python", "-m", "computercraft.server" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..86ce93a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 neumond + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 6f4b306..b46c0d2 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,8 @@ [(read more)](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html). Never use code in this repo if you don't trust your players! -1. Download and install wheel from github releases - ```sh - pip install computercraft-*.whl - ``` - -2. Enable localhost in mod server config +1. Before you start Minecraft, enable localhost in mod server config In case of singleplayer it's located inside your saves folder. In case of multiplayer check your server folder. @@ -23,13 +18,24 @@ Never use code in this repo if you don't trust your players! action = "allow" # change here deny to allow ``` -3. Start python server: +2. Install & start python language server + + Choose one of the following: + + Docker way: ```sh + docker run -it -p 8080:8080 neumond/python-computer-craft + ``` + + Install & run manually: + + ```sh + pip install computercraft python -m computercraft.server ``` -4. In minecraft, open up any computer and type: +3. Start Minecraft, open up any computer and type: ```sh wget http://127.0.0.1:8080/ py @@ -39,6 +45,13 @@ Never use code in this repo if you don't trust your players! Now you have python REPL in computercraft! To quit REPL type `exit()` and press enter. + To run any program: + + ```sh + py program.py # relative to current dir + py /path/to/program.py + ``` + `py` is short Lua program that interacts with the server. `cc` module contains almost everything *as is* in ComputerCraft documentation: @@ -108,4 +121,4 @@ p = import_file('/disk/program.py') # absolute m = import_file('lib.py', __file__) # relative to current file ``` -More examples can be found in repository. +More examples can be found in this repository. diff --git a/computercraft/back.lua b/computercraft/back.lua index aebe817..c84b8d5 100644 --- a/computercraft/back.lua +++ b/computercraft/back.lua @@ -1,113 +1,415 @@ -local genv = getfenv() -local temp = {} -local event_sub = {} -genv.temp = temp -local url = 'http://127.0.0.1:4343/' -local tasks = {} -local filters = {} -local ycounts = {} - -ws = http.websocket(url..'ws/') -if ws == false then - error('unable to connect to server '..url..'ws/') -end -ws.send(textutils.serializeJSON{ - action='run', - computer=os.getComputerID(), - args={...}, -}) - -function nullify_array(a, size) - local r = {} - for k=1,size do - if a[k] == nil then - r[k] = textutils.json_null +local _py = { + cc_url = '__cc_url__', + oc_host = '__oc_host__', + oc_port = __oc_port__, + proto_version = 5, + event_sub = {}, + tasks = {}, + filters = {}, + coparams = {}, + modules = {}, + mcache = {}, + temp = {}, +} + +if type(getfenv) == 'function' then + _py.genv = getfenv() +elseif type(_ENV) == 'table' then + _py.genv = _ENV +elseif type(_G) == 'table' then + _py.genv = _G -- TODO: necessary? +else + error('E001: Can\'t get environment') +end +_py.genv.temp = _py.temp +_py.genv._m = _py.modules + +if type(loadstring) == 'function' then + -- 5.1: prefer loadstring + function _py.loadstring(source) + local r, err = loadstring(source) + if r ~= nil then setfenv(r, _py.genv) end + return r, err + end +else + -- 5.2+: load can deal with strings as well + function _py.loadstring(source) + return load(source, nil, nil, _py.genv) + end +end + +if type(require) == 'function' then + function _py.try_import(module, fn_name, save_as) + local r, m = pcall(function() return require(module) end) + if (not r + or type(m) ~= 'table' + or type(m[fn_name]) ~= 'function') then return false end + if save_as == nil then + _py._impfn = m[fn_name] else - r[k] = a[k] + _py[save_as] = m[fn_name] end + return true end - return r +else + function _py.try_import() return false end end -while true do - local event, p1, p2, p3, p4, p5 = os.pullEvent() +function _py.loadmethod(code) + -- 0..N R:module: -- require(module) + -- 0..1 G:module: -- use builtin module + -- choice: + -- M:method$ -- take method of loaded module + -- code$ -- arbitrary code + if _py.mcache[code] ~= nil then return _py.mcache[code] end - if event == 'websocket_message' then - msg = textutils.unserializeJSON(p2) - if msg.action == 'task' then - local fn, err = loadstring(msg.code) - if fn == nil then - ws.send(textutils.serializeJSON{ - action='task_result', - task_id=msg.task_id, - result={false, err}, - yields=0, - }) - else - setfenv(fn, genv) - if msg.immediate then - ws.send(textutils.serializeJSON{ - action='task_result', - task_id=msg.task_id, - result={fn()}, - yields=0, - }) - else - tasks[msg.task_id] = coroutine.create(fn) - ycounts[msg.task_id] = 0 - end + local mod, modname + while true do + local _, _, rmod, mcode = string.find(code, '^R:(%a%w*):(.*)$') + if rmod == nil then break end + if _py.modules[rmod] == nil then + local r, v = pcall(require, rmod) + if not r then return nil, 'module not found' end + _py.modules[rmod] = v + end + mod, code = _py.modules[rmod], mcode + end + do + local _, _, rmod, mcode = string.find(code, '^G:(%a%w*):(.*)$') + if rmod ~= nil then + mod, code = _py.genv[rmod], mcode + if mod == nil then return nil, 'module not found' end + end + end + + local fn + do + local _, _, meth = string.find(code, '^M:(%a%w*)$') + if meth ~= nil then + fn = mod[meth] + if fn == nil then return nil, 'method not found' end + else + local err + fn, err = _py.loadstring(code) + if not fn then return nil, err end + end + end + _py.mcache[code] = fn + return fn +end + +if type(os) == 'table' and type(os.pullEventRaw) == 'function' then + _py.pullEvent = os.pullEventRaw -- computercraft, preferrable +elseif type(os) == 'table' and type(os.pullEvent) == 'function' then + _py.pullEvent = os.pullEvent -- computercraft +elseif _py.try_import('event', 'pull') then + _py.pullEvent = _py._impfn -- opencomputers +else + error('E002: Can\'t detect pullEvent method') +end + +if type(arg) == 'table' then + _py.argv = arg -- includes program name +else + _py.argv = {...} +end + +do + local function s_rec(v, tracking) + local t = type(v) + if v == nil then + return 'N' + elseif v == false then + return 'F' + elseif v == true then + return 'T' + elseif t == 'number' then + return '[' .. tostring(v) .. ']' + elseif t == 'string' then + return string.format('<%u>', #v) .. v + elseif t == 'table' then + if tracking[v] ~= nil then + error('Cannot serialize table with recursive entries', 0) end - elseif msg.action == 'drop' then - for _, task_id in ipairs(msg.task_ids) do - tasks[task_id] = nil - filters[task_id] = nil - ycounts[task_id] = nil + tracking[v] = true + local r = '{' + for k, x in pairs(v) do + r = r .. ':' .. s_rec(k, tracking) .. s_rec(x, tracking) end - elseif msg.action == 'sub' then - event_sub[msg.event] = true - elseif msg.action == 'unsub' then - event_sub[msg.event] = nil - elseif msg.action == 'close' then - if msg.error ~= nil then - io.stderr:write(msg.error) + return r .. '}' + else + error('Cannot serialize type ' .. t, 0) + end + end + _py.serialize = function(v) return s_rec(v, {}) end +end + +function _py.create_stream(s, idx) + if idx == nil then idx = 1 end + return { + getidx=function() return idx end, + isend=function() return idx > #s end, + fixed=function(n) + local r = s:sub(idx, idx + n - 1) + if #r ~= n then error('Unexpected end of stream') end + idx = idx + n + return r + end, + tostop=function(sym) + local newidx = s:find(sym, idx, true) + if newidx == nil then error('Unexpected end of stream') end + local r = s:sub(idx, newidx - 1) + idx = newidx + 1 + return r + end, + } +end + +function _py.deserialize(stream) + local tok = stream.fixed(1) + if tok == 'N' then + return nil + elseif tok == 'F' then + return false + elseif tok == 'T' then + return true + elseif tok == '[' then + return tonumber(stream.tostop(']')) + elseif tok == '<' then + local slen = tonumber(stream.tostop('>')) + return stream.fixed(slen) + elseif tok == 'E' then + -- same as string (<), but intended for evaluation + local slen = tonumber(stream.tostop('>')) + local fn = assert(_py.loadstring(stream.fixed(slen))) + return fn() + elseif tok == 'X' then + local slen = tonumber(stream.tostop('>')) + local key = stream.fixed(slen) + return _py.temp[key] + elseif tok == '{' then + local r = {} + while true do + tok = stream.fixed(1) + if tok == ':' then + local key = _py.deserialize(stream) + r[key] = _py.deserialize(stream) + else break end + end + return r + else + error('Unknown token ' .. tok) + end +end + +function _py.drop_task(task_id) + _py.tasks[task_id] = nil + _py.filters[task_id] = nil + _py.coparams[task_id] = nil +end + +-- nil-safe +if type(table.maxn) == 'function' then + function _py.safe_unpack(a) + return table.unpack(a, 1, table.maxn(a)) + end +else + function _py.safe_unpack(a) + local maxn = #a + -- TODO: better solution? + for k in pairs(a) do + if type(k) == 'number' and k > maxn then maxn = k end + end + return table.unpack(a, 1, maxn) + end +end + +if type(http) == 'table' and type(http.websocket) == 'function' then + function _py.start_connection() + local ws = http.websocket(_py.cc_url) + if not ws then + error('Unable to connect to server ' .. _py.cc_url) + end + _py.ws = { + send = function(m) return ws.send(m, true) end, + close = function() ws.close() end, + } + end +elseif _py.try_import('internet', 'socket', 'oc_connect') then + function _py.start_connection() + local s = _py.oc_connect(_py.oc_host, _py.oc_port) + if not s or s.socket.finishConnect() == nil then + error('Unable to connect to server ' .. _py.oc_host .. ':' .. _py.oc_port) + end + local bit32 = require('bit32') + local buf = '' + _py.ws = { + send = function(frame) + local z = #frame + s.socket.write(string.char( + bit32.band(bit32.rshift(z, 16), 255), + bit32.band(bit32.rshift(z, 8), 255), + bit32.band(z, 255) + )) + s.socket.write(frame) + end, + close = function() s.socket.close() end, + read_pending = function(frame_callback) + local inc = s.socket.read() + if inc == nil then + return true, 'Connection with server has been closed' + end + buf = buf .. inc + while #buf >= 3 do + local frame_size = ( + bit32.lshift(string.byte(buf, 1), 16) + + bit32.lshift(string.byte(buf, 2), 8) + + string.byte(buf, 3)) + if #buf < frame_size + 3 then break end + if frame_callback(string.sub(buf, 4, 3 + frame_size)) then + return true, nil + end + buf = string.sub(buf, 4 + frame_size) + end + return false + end, + } + end +else + error('E003: Can\'t detect connection method') +end + +function _py.ws_send(action, ...) + local m = action + for _, v in ipairs({...}) do + m = m .. _py.serialize(v) + end + _py.ws.send(m) +end + +function _py.exec_python_directive(dstring) + local msg = _py.create_stream(dstring) + local action = msg.fixed(1) + + if action == 'T' or action == 'I' then -- new task + local task_id = _py.deserialize(msg) + local code = _py.deserialize(msg) + local params = _py.deserialize(msg) + + local fn, err = _py.loadmethod(code) + if fn == nil then + -- couldn't compile + _py.ws_send('T', task_id, _py.serialize{false, err}) + else + if action == 'I' then + _py.ws_send('T', task_id, _py.serialize{fn(_py.safe_unpack(params))}) + else + _py.tasks[task_id] = coroutine.create(fn) + _py.coparams[task_id] = params end - break end - elseif event_sub[event] == true then - ws.send(textutils.serializeJSON{ - action='event', - event=event, - params=nullify_array({p1, p2, p3, p4, p5}, 5), - }) + elseif action == 'D' then -- drop tasks + while not msg.isend() do + _py.drop_task(_py.deserialize(msg)) + end + elseif action == 'S' or action == 'U' then -- (un)subscribe to event + local event = _py.deserialize(msg) + if action == 'S' then + _py.event_sub[event] = true + else + _py.event_sub[event] = nil + end + elseif action == 'C' then -- close session + local err = _py.deserialize(msg) + if err ~= nil then + io.stderr:write(err .. '\n') + end + return true end +end +function _py.resume_coros(event, p1, p2, p3, p4, p5) local del_tasks = {} - for task_id in pairs(tasks) do - if filters[task_id] == nil or filters[task_id] == event then - local r = {coroutine.resume(tasks[task_id], event, p1, p2, p3, p4, p5)} - if coroutine.status(tasks[task_id]) == 'dead' then - ws.send(textutils.serializeJSON{ - action='task_result', - task_id=task_id, - result=r, - yields=ycounts[task_id], - }) + for task_id in pairs(_py.tasks) do + if _py.filters[task_id] == nil or _py.filters[task_id] == event then + local r + if _py.coparams[task_id] ~= nil then + r = {coroutine.resume( + _py.tasks[task_id], + _py.safe_unpack(_py.coparams[task_id]))} + _py.coparams[task_id] = nil + else + r = {coroutine.resume( + _py.tasks[task_id], + event, p1, p2, p3, p4, p5)} + end + if coroutine.status(_py.tasks[task_id]) == 'dead' then + _py.ws_send('T', task_id, _py.serialize(r)) del_tasks[task_id] = true else if r[1] == true then - filters[task_id] = r[2] + _py.filters[task_id] = r[2] else - filters[task_id] = nil + _py.filters[task_id] = nil end - ycounts[task_id] = ycounts[task_id] + 1 end end end - for task_id in pairs(del_tasks) do - tasks[task_id] = nil - filters[task_id] = nil - ycounts[task_id] = nil + for task_id in pairs(del_tasks) do _py.drop_task(task_id) end +end + +if type(fs) == 'table' and type(fs.combine) == 'function' then + function _py.start_program(name) + local path = fs.combine(shell.dir(), name) + if not fs.exists(path) then return nil end + if fs.isDir(path) then return nil end + local f = fs.open(path, 'r') + local code = f.readAll() + f.close() + return path, code + end +else + function _py.start_program(name) + local filesystem = require('filesystem') + local shell = require('shell') + local path = filesystem.concat(shell.getWorkingDirectory(), name) + if not filesystem.exists(path) then return nil end + if filesystem.isDirectory(path) then return nil end + local f = io.open(path, 'rb') + local code = f:read('*a') + f:close() + return path, code end end -ws.close() +_py.start_connection() +do + local path, code = nil, nil + if _py.argv[1] ~= nil then + path, code = _py.start_program(_py.argv[1]) + if path == nil then error('Program not found') end + end + _py.ws_send('0', _py.proto_version, _py.argv, path, code) +end +while true do + local event, p1, p2, p3, p4, p5 = _py.pullEvent() + if event == 'websocket_message' then + -- TODO: filter by address + if _py.exec_python_directive(p2) then break end + elseif event == 'websocket_closed' then + -- TODO: filter by address + error('Connection with server has been closed') + elseif event == 'internet_ready' then + -- TODO: filter by address + local must_exit, err = _py.ws.read_pending(_py.exec_python_directive) + if must_exit then + if err == nil then break else error(err) end + end + elseif event == 'terminate' then + _py.ws_send('C') -- CC:T trigger KeyboardInterrupt + elseif _py.event_sub[event] == true then + _py.ws_send('E', event, {p1, p2, p3, p4, p5}) + end + _py.resume_coros(event, p1, p2, p3, p4, p5) +end +_py.ws.close() diff --git a/computercraft/subapis/__init__.py b/computercraft/cc/__init__.py similarity index 100% rename from computercraft/subapis/__init__.py rename to computercraft/cc/__init__.py diff --git a/computercraft/subapis/_pkg.py b/computercraft/cc/_pkg.py similarity index 56% rename from computercraft/subapis/_pkg.py rename to computercraft/cc/_pkg.py index 5da475d..8d9b661 100644 --- a/computercraft/subapis/_pkg.py +++ b/computercraft/cc/_pkg.py @@ -1,8 +1,6 @@ from types import ModuleType from ..errors import LuaException -from ..lua import lua_string -from ..rproc import boolean, option_string from ..sess import eval_lua @@ -18,43 +16,38 @@ def import_file(path: str, relative_to: str = None): - mod = ModuleType(path) - mod.__file__ = path - path_expr = lua_string(path) - if relative_to is not None: - path_expr = 'fs.combine(fs.getDir({}), {})'.format( - lua_string(relative_to), - path_expr, - ) - source = option_string(eval_lua(''' -local p = {} + source = eval_lua(b''' +local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end if not fs.exists(p) then return nil end if fs.isDir(p) then return nil end f = fs.open(p, "r") local src = f.readAll() f.close() return src -'''.lstrip().format( - path_expr, - ))) +'''.strip(), path, relative_to).take_option_string() if source is None: raise ImportError('File not found: {}'.format(path)) + mod = ModuleType(path) + mod.__file__ = path cc = compile(source, mod.__name__, 'exec') exec(cc, vars(mod)) return mod def is_commands() -> bool: - return boolean(eval_lua('return commands ~= nil')) + return eval_lua(b'return commands ~= nil').take_bool() def is_multishell() -> bool: - return boolean(eval_lua('return multishell ~= nil')) + return eval_lua(b'return multishell ~= nil').take_bool() def is_turtle() -> bool: - return boolean(eval_lua('return turtle ~= nil')) + return eval_lua(b'return turtle ~= nil').take_bool() def is_pocket() -> bool: - return boolean(eval_lua('return pocket ~= nil')) + return eval_lua(b'return pocket ~= nil').take_bool() diff --git a/computercraft/subapis/colors.py b/computercraft/cc/colors.py similarity index 76% rename from computercraft/subapis/colors.py rename to computercraft/cc/colors.py index 208026e..010d186 100644 --- a/computercraft/subapis/colors.py +++ b/computercraft/cc/colors.py @@ -1,10 +1,6 @@ from typing import Tuple -from ..rproc import boolean, integer, tuple3_number -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('colors.') +from ..sess import eval_lua __all__ = ( @@ -55,23 +51,24 @@ # combine, subtract and test are mostly for redstone.setBundledOutput def combine(*colors: int) -> int: - return integer(method('combine', *colors)) + return eval_lua(b'G:colors:M:combine', *colors).take_int() def subtract(color_set: int, *colors: int) -> int: - return integer(method('subtract', color_set, *colors)) + return eval_lua(b'G:colors:M:subtract', color_set, *colors).take_int() def test(colors: int, color: int) -> bool: - return boolean(method('test', colors, color)) + return eval_lua(b'G:colors:M:test', colors, color).take_bool() def packRGB(r: float, g: float, b: float) -> int: - return integer(method('packRGB', r, g, b)) + return eval_lua(b'G:colors:M:packRGB', r, g, b).take_int() def unpackRGB(rgb: int) -> Tuple[float, float, float]: - return tuple3_number(method('unpackRGB', rgb)) + rp = eval_lua(b'G:colors:M:unpackRGB', rgb) + return tuple(rp.take_number() for _ in range(3)) # use these chars for term.blit diff --git a/computercraft/cc/commands.py b/computercraft/cc/commands.py new file mode 100644 index 0000000..1bf1e82 --- /dev/null +++ b/computercraft/cc/commands.py @@ -0,0 +1,37 @@ +from typing import Tuple, List, Optional + +from ..sess import eval_lua + + +__all__ = ( + 'exec', + 'list', + 'getBlockPosition', + 'getBlockInfo', + 'getBlockInfos', +) + + +def exec(command: str) -> Tuple[bool, List[str], Optional[int]]: + rp = eval_lua(b'G:commands:M:exec', command) + success = rp.take_bool() + log = rp.take_list_of_strings() + n = rp.take_option_int() + return success, log, n + + +def list() -> List[str]: + return eval_lua(b'G:commands:M:list').take_list_of_strings() + + +def getBlockPosition() -> Tuple[int, int, int]: + rp = eval_lua(b'G:commands:M:getBlockPosition') + return tuple(rp.take_int() for _ in range(3)) + + +def getBlockInfo(x: int, y: int, z: int) -> dict: + return eval_lua(b'G:commands:M:getBlockInfo', x, y, z).take_dict() + + +def getBlockInfos(x1: int, y1: int, z1: int, x2: int, y2: int, z2: int) -> List[dict]: + return eval_lua(b'G:commands:M:getBlockInfos', x1, y1, z1, x2, y2, z2).take_list() diff --git a/computercraft/cc/disk.py b/computercraft/cc/disk.py new file mode 100644 index 0000000..cf89eec --- /dev/null +++ b/computercraft/cc/disk.py @@ -0,0 +1,62 @@ +from typing import Optional, Union + +from ..sess import eval_lua + + +__all__ = ( + 'isPresent', + 'hasData', + 'getMountPath', + 'setLabel', + 'getLabel', + 'getID', + 'hasAudio', + 'getAudioTitle', + 'playAudio', + 'stopAudio', + 'eject', +) + + +def isPresent(side: str) -> bool: + return eval_lua(b'G:disk:M:isPresent', side).take_bool() + + +def hasData(side: str) -> bool: + return eval_lua(b'G:disk:M:hasData', side).take_bool() + + +def getMountPath(side: str) -> Optional[str]: + return eval_lua(b'G:disk:M:getMountPath', side).take_option_string() + + +def setLabel(side: str, label: Optional[str]): + return eval_lua(b'G:disk:M:setLabel', side, label).take_none() + + +def getLabel(side: str) -> Optional[str]: + return eval_lua(b'G:disk:M:getLabel', side).take_option_string() + + +def getID(side: str) -> Optional[int]: + return eval_lua(b'G:disk:M:getID', side).take_option_int() + + +def hasAudio(side: str) -> bool: + return eval_lua(b'G:disk:M:hasAudio', side).take_bool() + + +def getAudioTitle(side: str) -> Optional[Union[bool, str]]: + return eval_lua(b'G:disk:M:getAudioTitle', side).take_option_string_bool() + + +def playAudio(side: str): + return eval_lua(b'G:disk:M:playAudio', side).take_none() + + +def stopAudio(side: str): + return eval_lua(b'G:disk:M:stopAudio', side).take_none() + + +def eject(side: str): + return eval_lua(b'G:disk:M:eject', side).take_none() diff --git a/computercraft/cc/fs.py b/computercraft/cc/fs.py new file mode 100644 index 0000000..2fe46e0 --- /dev/null +++ b/computercraft/cc/fs.py @@ -0,0 +1,197 @@ +from contextlib import contextmanager +from typing import Any, Dict, Optional, List + +from ..sess import eval_lua, lua_context_object, ContextObject + + +__all__ = ( + 'list', + 'exists', + 'isDir', + 'isReadOnly', + 'getDrive', + 'getSize', + 'getFreeSpace', + 'getCapacity', + 'makeDir', + 'move', + 'copy', + 'delete', + 'combine', + 'open', + 'find', + 'getDir', + 'getName', + 'isDriveRoot', + 'complete', + 'attributes', +) + + +class SeekMixin: + def seek(self, whence: str = None, offset: int = None) -> int: + # whence: set, cur, end + rp = self._call(b'seek', whence, offset) + rp.check_nil_error() + return rp.take_int() + + +class ReadMixin: + def _take(self, rp): + raise NotImplementedError + + def read(self, count: int = 1) -> Optional[str]: + return self._take(self._call(b'read', count)) + + def readLine(self, withTrailing: bool = False) -> Optional[str]: + return self._take(self._call(b'readLine', withTrailing)) + + def readAll(self) -> Optional[str]: + return self._take(self._call(b'readAll')) + + def __iter__(self): + return self + + def __next__(self): + line = self.readLine() + if line is None: + raise StopIteration + return line + + +class WriteMixin: + def _put(self, t): + raise NotImplementedError + + def write(self, text: str) -> None: + return self._call(b'write', self._put(text)).take_none() + + def flush(self) -> None: + return self._call(b'flush').take_none() + + +class ReadHandle(ReadMixin, ContextObject): + def _take(self, rp): + return rp.take_option_unicode() + + +class BinaryReadHandle(ReadMixin, SeekMixin, ContextObject): + def _take(self, rp): + return rp.take_option_bytes() + + +class WriteHandle(WriteMixin, ContextObject): + def _put(self, t: str) -> bytes: + return t.encode('utf-8') + + def writeLine(self, text: str) -> None: + return self.write(text + '\n') + + +class BinaryWriteHandle(WriteMixin, SeekMixin, ContextObject): + def _put(self, b: bytes) -> bytes: + return b + + +def list(path: str) -> List[str]: + return eval_lua(b'G:fs:M:list', path).take_list_of_strings() + + +def exists(path: str) -> bool: + return eval_lua(b'G:fs:M:exists', path).take_bool() + + +def isDir(path: str) -> bool: + return eval_lua(b'G:fs:M:isDir', path).take_bool() + + +def isReadOnly(path: str) -> bool: + return eval_lua(b'G:fs:M:isReadOnly', path).take_bool() + + +def getDrive(path: str) -> Optional[str]: + return eval_lua(b'G:fs:M:getDrive', path).take_option_string() + + +def getSize(path: str) -> int: + return eval_lua(b'G:fs:M:getSize', path).take_int() + + +def getFreeSpace(path: str) -> int: + return eval_lua(b'G:fs:M:getFreeSpace', path).take_int() + + +def getCapacity(path: str) -> int: + return eval_lua(b'G:fs:M:getCapacity', path).take_int() + + +def makeDir(path: str) -> None: + return eval_lua(b'G:fs:M:makeDir', path).take_none() + + +def move(fromPath: str, toPath: str) -> None: + return eval_lua(b'G:fs:M:move', fromPath, toPath).take_none() + + +def copy(fromPath: str, toPath: str) -> None: + return eval_lua(b'G:fs:M:copy', fromPath, toPath).take_none() + + +def delete(path: str) -> None: + return eval_lua(b'G:fs:M:delete', path).take_none() + + +def combine(basePath: str, localPath: str) -> str: + return eval_lua(b'G:fs:M:combine', basePath, localPath).take_string() + + +@contextmanager +def open(path: str, mode: str): + ''' + Usage: + + with fs.open('filename', 'w') as f: + f.writeLine('textline') + + with fs.open('filename', 'r') as f: + for line in f: + ... + ''' + with lua_context_object( + b'fs.open(...)', + (path, mode.replace('b', '') + 'b'), + b'{e}.close()', + ) as fid: + if 'b' in mode: + hcls = BinaryReadHandle if 'r' in mode else BinaryWriteHandle + else: + hcls = ReadHandle if 'r' in mode else WriteHandle + yield hcls(fid) + + +def find(wildcard: str) -> List[str]: + return eval_lua(b'G:fs:M:find', wildcard).take_list_of_strings() + + +def getDir(path: str) -> str: + return eval_lua(b'G:fs:M:getDir', path).take_string() + + +def getName(path: str) -> str: + return eval_lua(b'G:fs:M:getName', path).take_string() + + +def isDriveRoot(path: str) -> bool: + return eval_lua(b'G:fs:M:isDriveRoot', path).take_bool() + + +def complete( + partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None, +) -> List[str]: + return eval_lua( + b'G:fs:M:complete', partialName, path, includeFiles, includeDirs, + ).take_list_of_strings() + + +def attributes(path: str) -> Dict[str, Any]: + return eval_lua(b'G:fs:M:attributes', path).take_dict() diff --git a/computercraft/cc/gps.py b/computercraft/cc/gps.py new file mode 100644 index 0000000..4e28d2e --- /dev/null +++ b/computercraft/cc/gps.py @@ -0,0 +1,20 @@ +from typing import Tuple, Optional + +from ..lua import LuaNum +from ..sess import eval_lua + + +__all__ = ( + 'CHANNEL_GPS', + 'locate', +) + + +CHANNEL_GPS = 65534 + + +def locate(timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[LuaNum, LuaNum, LuaNum]]: + rp = eval_lua(b'G:gps:M:locate', timeout, debug) + if rp.peek() is None: + return None + return tuple(rp.take_number() for _ in range(3)) diff --git a/computercraft/cc/help.py b/computercraft/cc/help.py new file mode 100644 index 0000000..adfec72 --- /dev/null +++ b/computercraft/cc/help.py @@ -0,0 +1,32 @@ +from typing import Optional, List + +from ..sess import eval_lua + + +__all__ = ( + 'path', + 'setPath', + 'lookup', + 'topics', + 'completeTopic', +) + + +def path() -> str: + return eval_lua(b'G:help:M:path').take_string() + + +def setPath(path: str) -> None: + return eval_lua(b'G:help:M:setPath', path).take_none() + + +def lookup(topic: str) -> Optional[str]: + return eval_lua(b'G:help:M:lookup', topic).take_option_string() + + +def topics() -> List[str]: + return eval_lua(b'G:help:M:topics').take_list_of_strings() + + +def completeTopic(topicPrefix: str) -> List[str]: + return eval_lua(b'G:help:M:completeTopic', topicPrefix).take_list_of_strings() diff --git a/computercraft/cc/keys.py b/computercraft/cc/keys.py new file mode 100644 index 0000000..2e862e7 --- /dev/null +++ b/computercraft/cc/keys.py @@ -0,0 +1,24 @@ +from typing import Optional + +from ..sess import eval_lua + + +__all__ = ( + 'getCode', + 'getName', +) + + +def getCode(name: str) -> Optional[int]: + # replaces properties + # keys.space → keys.getCode('space') + return eval_lua(b''' +local k = ... +if type(keys[k]) == 'number' then + return keys[k] +end +return nil''', name).take_option_int() + + +def getName(code: int) -> Optional[str]: + return eval_lua(b'G:keys:M:getName', code).take_option_string() diff --git a/computercraft/cc/multishell.py b/computercraft/cc/multishell.py new file mode 100644 index 0000000..706d0fc --- /dev/null +++ b/computercraft/cc/multishell.py @@ -0,0 +1,42 @@ +from typing import Optional + +from ..sess import eval_lua + + +__all__ = ( + 'getCurrent', + 'getCount', + 'launch', + 'setTitle', + 'getTitle', + 'setFocus', + 'getFocus', +) + + +def getCurrent() -> int: + return eval_lua(b'G:multishell:M:getCurrent').take_int() + + +def getCount() -> int: + return eval_lua(b'G:multishell:M:getCount').take_int() + + +def launch(environment: dict, programPath: str, *args: str) -> int: + return eval_lua(b'G:multishell:M:launch', environment, programPath, *args).take_int() + + +def setTitle(tabID: int, title: str): + return eval_lua(b'G:multishell:M:setTitle', tabID, title).take_none() + + +def getTitle(tabID: int) -> Optional[str]: + return eval_lua(b'G:multishell:M:getTitle', tabID).take_option_string() + + +def setFocus(tabID: int) -> bool: + return eval_lua(b'G:multishell:M:setFocus', tabID).take_bool() + + +def getFocus() -> int: + return eval_lua(b'G:multishell:M:getFocus').take_int() diff --git a/computercraft/subapis/os.py b/computercraft/cc/os.py similarity index 51% rename from computercraft/subapis/os.py rename to computercraft/cc/os.py index 73c1784..aaca36a 100644 --- a/computercraft/subapis/os.py +++ b/computercraft/cc/os.py @@ -1,11 +1,8 @@ -from typing import Optional, List +from typing import Optional from ..lua import LuaNum -from ..rproc import nil, string, option_string, number, integer, boolean -from ..sess import eval_lua_method_factory, get_current_greenlet - - -method = eval_lua_method_factory('os.') +from ..sess import eval_lua +from ..sess import get_current_greenlet __all__ = ( @@ -31,23 +28,23 @@ def version() -> str: - return string(method('version')) + return eval_lua(b'G:os:M:version').take_string() def getComputerID() -> int: - return integer(method('getComputerID')) + return eval_lua(b'G:os:M:getComputerID').take_int() def getComputerLabel() -> Optional[str]: - return option_string(method('getComputerLabel')) + return eval_lua(b'G:os:M:getComputerLabel').take_option_string() def setComputerLabel(label: Optional[str]): - return nil(method('setComputerLabel', label)) + return eval_lua(b'G:os:M:setComputerLabel', label).take_none() -def run(environment: dict, programPath: str, *args: List[str]): - return boolean(method('run', environment, programPath, *args)) +def run(environment: dict, programPath: str, *args: str): + return eval_lua(b'G:os:M:run', environment, programPath, *args).take_bool() def captureEvent(event: str): @@ -68,56 +65,56 @@ def captureEvent(event: str): def queueEvent(event: str, *params): - return nil(method('queueEvent', event, *params)) + return eval_lua(b'G:os:M:queueEvent', event, *params).take_none() def clock() -> LuaNum: # number of game ticks * 0.05, roughly seconds - return number(method('clock')) + return eval_lua(b'G:os:M:clock').take_number() # regarding ingame parameter below: # python has great stdlib to deal with real current time # we keep here only in-game time methods and parameters -def time() -> LuaNum: +def time(locale=b'ingame') -> LuaNum: # in hours 0..24 - return number(method('time', 'ingame')) + return eval_lua(b'G:os:M:time', locale).take_number() -def day() -> int: - return integer(method('day', 'ingame')) +def day(locale=b'ingame') -> int: + return eval_lua(b'G:os:M:day', locale).take_int() -def epoch() -> int: - return integer(method('epoch', 'ingame')) +def epoch(locale=b'ingame') -> int: + return eval_lua(b'G:os:M:epoch', locale).take_int() def sleep(seconds: LuaNum): - return nil(method('sleep', seconds)) + return eval_lua(b'G:os:M:sleep', seconds).take_none() def startTimer(timeout: LuaNum) -> int: - return integer(method('startTimer', timeout)) + return eval_lua(b'G:os:M:startTimer', timeout).take_int() def cancelTimer(timerID: int): - return nil(method('cancelTimer', timerID)) + return eval_lua(b'G:os:M:cancelTimer', timerID).take_none() def setAlarm(time: LuaNum) -> int: # takes time of the day in hours 0..24 # returns integer alarmID - return integer(method('setAlarm', time)) + return eval_lua(b'G:os:M:setAlarm', time).take_int() -def cancelAlarm(alarmID: int): - return nil(method('cancelAlarm', alarmID)) +def cancelAlarm(alarmID: int) -> None: + return eval_lua(b'G:os:M:cancelAlarm', alarmID).take_none() -def shutdown(): - return nil(method('shutdown')) +def shutdown() -> None: + return eval_lua(b'G:os:M:shutdown').take_none() -def reboot(): - return nil(method('reboot')) +def reboot() -> None: + return eval_lua(b'G:os:M:reboot').take_none() diff --git a/computercraft/cc/paintutils.py b/computercraft/cc/paintutils.py new file mode 100644 index 0000000..cff5de3 --- /dev/null +++ b/computercraft/cc/paintutils.py @@ -0,0 +1,42 @@ +from typing import List + +from ..sess import eval_lua + + +__all__ = ( + 'parseImage', + 'loadImage', + 'drawPixel', + 'drawLine', + 'drawBox', + 'drawFilledBox', + 'drawImage', +) + + +def parseImage(data: bytes) -> List[List[int]]: + return eval_lua(b'G:paintutils:M:parseImage', data).take_2d_int() + + +def loadImage(path: str) -> List[List[int]]: + return eval_lua(b'G:paintutils:M:loadImage', path).take_2d_int() + + +def drawPixel(x: int, y: int, color: int = None): + return eval_lua(b'G:paintutils:M:drawPixel', x, y, color).take_none() + + +def drawLine(startX: int, startY: int, endX: int, endY: int, color: int = None): + return eval_lua(b'G:paintutils:M:drawLine', startX, startY, endX, endY, color).take_none() + + +def drawBox(startX: int, startY: int, endX: int, endY: int, color: int = None): + return eval_lua(b'G:paintutils:M:drawBox', startX, startY, endX, endY, color).take_none() + + +def drawFilledBox(startX: int, startY: int, endX: int, endY: int, color: int = None): + return eval_lua(b'G:paintutils:M:drawFilledBox', startX, startY, endX, endY, color).take_none() + + +def drawImage(image: List[List[int]], xPos: int, yPos: int): + return eval_lua(b'G:paintutils:M:drawImage', image, xPos, yPos).take_none() diff --git a/computercraft/subapis/parallel.py b/computercraft/cc/parallel.py similarity index 75% rename from computercraft/subapis/parallel.py rename to computercraft/cc/parallel.py index 9e9aebf..76baf14 100644 --- a/computercraft/subapis/parallel.py +++ b/computercraft/cc/parallel.py @@ -11,9 +11,8 @@ def waitForAny(*task_fns): pgl = get_current_greenlet().cc_greenlet sess = pgl._sess - gs = [CCGreenlet(fn) for fn in task_fns] - for g in gs: - g.defer_switch() + for fn in task_fns: + CCGreenlet(fn) try: sess._server_greenlet.switch() @@ -25,9 +24,8 @@ def waitForAll(*task_fns): pgl = get_current_greenlet().cc_greenlet sess = pgl._sess - gs = [CCGreenlet(fn) for fn in task_fns] - for g in gs: - g.defer_switch() + for fn in task_fns: + CCGreenlet(fn) try: for _ in range(len(task_fns)): diff --git a/computercraft/cc/peripheral.py b/computercraft/cc/peripheral.py new file mode 100644 index 0000000..a36e566 --- /dev/null +++ b/computercraft/cc/peripheral.py @@ -0,0 +1,62 @@ +import inspect +from typing import List, Optional, Type, TypeVar + +from ..sess import eval_lua +from ..cc_peripherals import register_std_peripherals +from ..cc_peripherals._base import BasePeripheral + + +__all__ = ( + 'UnknownPeripheralError', + 'isPresent', + 'getType', + 'getNames', + 'wrap', + 'registerType', +) + + +P = TypeVar('Peripheral') +type_map = {} + + +class UnknownPeripheralError(TypeError): + pass + + +def isPresent(side: str) -> bool: + return eval_lua(b'G:peripheral:M:isPresent', side).take_bool() + + +def getType(side: str) -> Optional[str]: + return eval_lua(b'G:peripheral:M:getType', side).take_option_string() + + +def getNames() -> List[str]: + return eval_lua(b'G:peripheral:M:getNames').take_list_of_strings() + + +# use instead getMethods and call +def wrap(side: str) -> Optional[BasePeripheral]: + ptype = getType(side) + if ptype is None: + return None + + if ptype not in type_map: + raise UnknownPeripheralError(ptype) + + cls = type_map[ptype] + if inspect.isclass(cls): + return cls(side) + else: + def _call(method, *args): + return eval_lua(b'G:peripheral:M:call', side, method, *args) + + return cls(side, ptype, _call) + + +def registerType(peripheralType: str, pcls: Type[P]): + type_map[peripheralType] = pcls + + +register_std_peripherals(registerType) diff --git a/computercraft/cc/pocket.py b/computercraft/cc/pocket.py new file mode 100644 index 0000000..b857b61 --- /dev/null +++ b/computercraft/cc/pocket.py @@ -0,0 +1,15 @@ +from ..sess import eval_lua + + +__all__ = ( + 'equipBack', + 'unequipBack', +) + + +def equipBack(): + return eval_lua(b'G:pocket:M:equipBack').check_bool_error() + + +def unequipBack(): + return eval_lua(b'G:pocket:M:unequipBack').check_bool_error() diff --git a/computercraft/cc/rednet.py b/computercraft/cc/rednet.py new file mode 100644 index 0000000..d3bd667 --- /dev/null +++ b/computercraft/cc/rednet.py @@ -0,0 +1,71 @@ +from typing import Any, List, Optional, Tuple, Union + +from ..lua import LuaNum +from ..sess import eval_lua + + +__all__ = ( + 'CHANNEL_REPEAT', + 'CHANNEL_BROADCAST', + 'open', + 'close', + 'send', + 'broadcast', + 'receive', + 'isOpen', + 'host', + 'unhost', + 'lookup', +) + + +CHANNEL_REPEAT = 65533 +CHANNEL_BROADCAST = 65535 + + +def open(side: str) -> None: + return eval_lua(b'G:rednet:M:open', side).take_none() + + +def close(side: str = None) -> None: + return eval_lua(b'G:rednet:M:close', side).take_none() + + +def send(receiverID: int, message: Any, protocol: str = None) -> bool: + return eval_lua(b'G:rednet:M:send', receiverID, message, protocol).take_bool() + + +def broadcast(message: Any, protocol: str = None) -> None: + return eval_lua(b'G:rednet:M:broadcast', message, protocol).take_none() + + +def receive( + protocolFilter: str = None, timeout: LuaNum = None, +) -> Optional[Tuple[int, Any, Optional[str]]]: + rp = eval_lua(b'G:rednet:M:receive', protocolFilter, timeout) + if rp.peek() is None: + return None + return (rp.take_int(), rp.take(), rp.take_option_string()) + + +def isOpen(side: str = None) -> bool: + return eval_lua(b'G:rednet:M:isOpen', side).take_bool() + + +def host(protocol: str, hostname: str) -> None: + return eval_lua(b'G:rednet:M:host', protocol, hostname).take_none() + + +def unhost(protocol: str) -> None: + return eval_lua(b'G:rednet:M:unhost', protocol).take_none() + + +def lookup(protocol: str, hostname: str = None) -> Union[Optional[int], List[int]]: + rp = eval_lua(b'G:rednet:M:lookup', protocol, hostname) + if hostname is None: + r = [] + while rp.peek() is not None: + r.append(rp.take_int()) + return r + else: + return rp.take_option_int() diff --git a/computercraft/cc/redstone.py b/computercraft/cc/redstone.py new file mode 100644 index 0000000..4ffdbcd --- /dev/null +++ b/computercraft/cc/redstone.py @@ -0,0 +1,64 @@ +from typing import List + +from ..sess import eval_lua + + +__all__ = ( + 'getSides', + 'getInput', + 'setOutput', + 'getOutput', + 'getAnalogInput', + 'setAnalogOutput', + 'getAnalogOutput', + 'getBundledInput', + 'setBundledOutput', + 'getBundledOutput', + 'testBundledInput', +) + + +def getSides() -> List[str]: + return eval_lua(b'G:redstone:M:getSides').take_list_of_strings() + + +def getInput(side: str) -> bool: + return eval_lua(b'G:redstone:M:getInput', side).take_bool() + + +def setOutput(side: str, value: bool) -> None: + return eval_lua(b'G:redstone:M:setOutput', side, value).take_none() + + +def getOutput(side: str) -> bool: + return eval_lua(b'G:redstone:M:getOutput', side).take_bool() + + +def getAnalogInput(side: str) -> int: + return eval_lua(b'G:redstone:M:getAnalogInput', side).take_int() + + +def setAnalogOutput(side: str, strength: int) -> None: + return eval_lua(b'G:redstone:M:setAnalogOutput', side, strength).take_none() + + +def getAnalogOutput(side: str) -> int: + return eval_lua(b'G:redstone:M:getAnalogOutput', side).take_int() + + +# bundled cables are not available in vanilla + +def getBundledInput(side: str) -> int: + return eval_lua(b'G:redstone:M:getBundledInput', side).take_int() + + +def setBundledOutput(side: str, colors: int): + return eval_lua(b'G:redstone:M:setBundledOutput', side, colors).take_none() + + +def getBundledOutput(side: str) -> int: + return eval_lua(b'G:redstone:M:getBundledOutput', side).take_int() + + +def testBundledInput(side: str, color: int) -> bool: + return eval_lua(b'G:redstone:M:testBundledInput', side, color).take_bool() diff --git a/computercraft/cc/settings.py b/computercraft/cc/settings.py new file mode 100644 index 0000000..11c6c5e --- /dev/null +++ b/computercraft/cc/settings.py @@ -0,0 +1,66 @@ +from typing import Any, List + +from .. import ser +from ..sess import eval_lua + + +__all__ = ( + 'define', + 'undefine', + 'getDetails', + 'set', + 'get', + 'unset', + 'clear', + 'getNames', + 'load', + 'save', +) + + +def define(name: str, description: str = None, default: Any = None, type: str = None): + options = {} + if description is not None: + options[b'description'] = description + if default is not None: + options[b'default'] = default + if type is not None: + options[b'type'] = type + return eval_lua(b'G:settings:M:define', name, options).take_none() + + +def undefine(name: str) -> None: + return eval_lua(b'G:settings:M:undefine', name).take_none() + + +def getDetails(name: str) -> dict: + return eval_lua(b'G:settings:M:getDetails', name).take_dict() + + +def set(name: str, value: Any) -> None: + return eval_lua(b'G:settings:M:set', name, value).take_none() + + +def get(name: str, default: Any = None) -> Any: + r = eval_lua(b'G:settings:M:get', name, default).take() + return r.decode(ser._CC_ENC) if isinstance(r, bytes) else r + + +def unset(name: str) -> None: + return eval_lua(b'G:settings:M:unset', name).take_none() + + +def clear() -> None: + return eval_lua(b'G:settings:M:clear').take_none() + + +def getNames() -> List[str]: + return eval_lua(b'G:settings:M:getNames').take_list_of_strings() + + +def load(path: str = None) -> bool: + return eval_lua(b'G:settings:M:load', path).take_bool() + + +def save(path: str = None) -> bool: + return eval_lua(b'G:settings:M:save', path).take_bool() diff --git a/computercraft/cc/shell.py b/computercraft/cc/shell.py new file mode 100644 index 0000000..efe1aff --- /dev/null +++ b/computercraft/cc/shell.py @@ -0,0 +1,107 @@ +from typing import List, Dict, Optional + +from ..sess import eval_lua + + +__all__ = ( + 'exit', + 'dir', + 'setDir', + 'path', + 'setPath', + 'resolve', + 'resolveProgram', + 'aliases', + 'setAlias', + 'clearAlias', + 'programs', + 'getRunningProgram', + 'run', + 'execute', + 'openTab', + 'switchTab', + 'complete', + 'completeProgram', +) + + +def exit() -> None: + return eval_lua(b'G:shell:M:exit').take_none() + + +def dir() -> str: + return eval_lua(b'G:shell:M:dir').take_string() + + +def setDir(path: str) -> None: + return eval_lua(b'G:shell:M:setDir', path).take_none() + + +def path() -> str: + return eval_lua(b'G:shell:M:path').take_string() + + +def setPath(path: str) -> None: + return eval_lua(b'G:shell:M:setPath', path).take_none() + + +def resolve(localPath: str) -> str: + return eval_lua(b'G:shell:M:resolve', localPath).take_string() + + +def resolveProgram(name: str) -> Optional[str]: + return eval_lua(b'G:shell:M:resolveProgram', name).take_option_string() + + +def aliases() -> Dict[str, str]: + return eval_lua(b'G:shell:M:aliases').take_dict() + + +def setAlias(alias: str, program: str) -> None: + return eval_lua(b'G:shell:M:setAlias', alias, program).take_none() + + +def clearAlias(alias: str) -> None: + return eval_lua(b'G:shell:M:clearAlias', alias).take_none() + + +def programs(showHidden: bool = None) -> List[str]: + return eval_lua(b'G:shell:M:programs', showHidden).take_list_of_strings() + + +def getRunningProgram() -> str: + return eval_lua(b'G:shell:M:getRunningProgram').take_string() + + +def run(command: str, *args: str) -> bool: + return eval_lua(b'G:shell:M:run', command, *args).take_bool() + + +def execute(command: str, *args: str) -> bool: + return eval_lua(b'G:shell:M:execute', command, *args).take_bool() + + +def openTab(command: str, *args: str) -> int: + return eval_lua(b'G:shell:M:openTab', command, *args).take_int() + + +def switchTab(tabID: int) -> None: + return eval_lua(b'G:shell:M:switchTab', tabID).take_none() + + +def complete(prefix: str) -> List[str]: + return eval_lua(b'G:shell:M:complete', prefix).take_list_of_strings() + + +def completeProgram(prefix: str) -> List[str]: + return eval_lua(b'G:shell:M:completeProgram', prefix).take_list_of_strings() + +# TODO: ? +# these functions won't be implemented +# it's far better to keep this in lua code + +# setCompletionFunction +# getCompletionInfo + +# we can create callbacks to python code, but this will require +# connection to python, and will break the shell if python disconnects diff --git a/computercraft/cc/term.py b/computercraft/cc/term.py new file mode 100644 index 0000000..d5a8043 --- /dev/null +++ b/computercraft/cc/term.py @@ -0,0 +1,147 @@ +from contextlib import contextmanager +from typing import Tuple + +from .. import ser +from ..sess import eval_lua, lua_context_object, ReferenceObject + + +__all__ = ( + 'write', + 'blit', + 'clear', + 'clearLine', + 'getCursorPos', + 'setCursorPos', + 'getCursorBlink', + 'setCursorBlink', + 'isColor', + 'getSize', + 'scroll', + 'setTextColor', + 'getTextColor', + 'setBackgroundColor', + 'getBackgroundColor', + 'getPaletteColor', + 'setPaletteColor', + 'nativePaletteColor', + 'redirect', + 'current', + 'native', + 'get_current_target', + 'get_native_target', +) + + +class TermMixin: + def write(self, text: str) -> None: + return self._call(b'write', ser.cc_dirty_encode(text)).take_none() + + def blit(self, text: str, textColors: bytes, backgroundColors: bytes) -> None: + return self._call(b'blit', ser.cc_dirty_encode(text), textColors, backgroundColors).take_none() + + def clear(self) -> None: + return self._call(b'clear').take_none() + + def clearLine(self) -> None: + return self._call(b'clearLine').take_none() + + def getCursorPos(self) -> Tuple[int, int]: + rp = self._call(b'getCursorPos') + return tuple(rp.take_int() for _ in range(2)) + + def setCursorPos(self, x: int, y: int) -> None: + return self._call(b'setCursorPos', x, y).take_none() + + def getCursorBlink(self) -> bool: + return self._call(b'getCursorBlink').take_bool() + + def setCursorBlink(self, value: bool) -> None: + return self._call(b'setCursorBlink', value).take_none() + + def isColor(self) -> bool: + return self._call(b'isColor').take_bool() + + def getSize(self) -> Tuple[int, int]: + rp = self._call(b'getSize') + return tuple(rp.take_int() for _ in range(2)) + + def scroll(self, lines: int) -> None: + return self._call(b'scroll', lines).take_none() + + def setTextColor(self, colorID: int) -> None: + return self._call(b'setTextColor', colorID).take_none() + + def getTextColor(self) -> int: + return self._call(b'getTextColor').take_int() + + def setBackgroundColor(self, colorID: int) -> None: + return self._call(b'setBackgroundColor', colorID).take_none() + + def getBackgroundColor(self) -> int: + return self._call(b'getBackgroundColor').take_int() + + def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]: + rp = self._call(b'getPaletteColor', colorID) + return tuple(rp.take_number() for _ in range(3)) + + def setPaletteColor(self, colorID: int, r: float, g: float, b: float) -> None: + return self._call(b'setPaletteColor', colorID, r, g, b).take_none() + + +class _Proxy(TermMixin): + def _call(self, method, *args): + return eval_lua(b'G:term:M:' + method, *args) + + +_p = _Proxy() + + +write = _p.write +blit = _p.blit +clear = _p.clear +clearLine = _p.clearLine +getCursorPos = _p.getCursorPos +setCursorPos = _p.setCursorPos +getCursorBlink = _p.getCursorBlink +setCursorBlink = _p.setCursorBlink +isColor = _p.isColor +getSize = _p.getSize +scroll = _p.scroll +setTextColor = _p.setTextColor +getTextColor = _p.getTextColor +setBackgroundColor = _p.setBackgroundColor +getBackgroundColor = _p.getBackgroundColor +getPaletteColor = _p.getPaletteColor +setPaletteColor = _p.setPaletteColor + + +def nativePaletteColor(colorID: int) -> Tuple[float, float, float]: + rp = eval_lua(b'G:term:M:nativePaletteColor', colorID) + return tuple(rp.take_number() for _ in range(3)) + + +class TermTarget(ReferenceObject, TermMixin): + pass + + +@contextmanager +def redirect(target: TermTarget): + with lua_context_object( + b'term.redirect(...)', + (target, ), + b'term.redirect({e})', + ): + yield + + +def current() -> TermTarget: + return TermTarget(lua_context_object(b'term.current()', ())) + + +def native() -> TermTarget: + return TermTarget(lua_context_object(b'term.native()', ())) + + +# compat +get_current_target = current +get_native_target = native diff --git a/computercraft/cc/textutils.py b/computercraft/cc/textutils.py new file mode 100644 index 0000000..fd5c654 --- /dev/null +++ b/computercraft/cc/textutils.py @@ -0,0 +1,66 @@ +from typing import List, Union + +from .. import ser +from ..lua import LuaNum +from ..sess import eval_lua + + +__all__ = ( + 'slowWrite', + 'slowPrint', + 'formatTime', + 'tabulate', + 'pagedTabulate', + 'pagedPrint', + 'complete', +) + + +def slowWrite(text: str, rate: LuaNum = None): + return eval_lua(b'G:textutils:M:slowWrite', ser.cc_dirty_encode(text), rate).take_none() + + +def slowPrint(text: str, rate: LuaNum = None): + return eval_lua(b'G:textutils:M:slowPrint', ser.cc_dirty_encode(text), rate).take_none() + + +def formatTime(time: LuaNum, twentyFourHour: bool = None) -> str: + return eval_lua(b'G:textutils:M:formatTime', time, twentyFourHour).take_string() + + +def _prepareTab(rows_and_colors): + r = [] + for item in rows_and_colors: + if isinstance(item, int): + r.append(item) + else: + r.append([ser.cc_dirty_encode(x) for x in item]) + return r + + +def tabulate(*rows_and_colors: Union[List[str], int]): + return eval_lua(b'G:textutils:M:tabulate', *_prepareTab(rows_and_colors)).take_none() + + +def pagedTabulate(*rows_and_colors: Union[List[str], int]): + return eval_lua(b'G:textutils:M:pagedTabulate', *_prepareTab(rows_and_colors)).take_none() + + +def pagedPrint(text: str, freeLines: int = None) -> int: + return eval_lua(b'G:textutils:M:pagedPrint', ser.cc_dirty_encode(text), freeLines).take_int() + + +def complete(partial: str, possible: List[str]) -> List[str]: + return [p[len(partial):] for p in possible if p.startswith(partial)] + + +# Questionable to implement +# serialize +# unserialize + +# Will not implement, use pythonic equivalents +# serializeJSON +# unserializeJSON +# urlEncode +# json_null +# empty_json_array diff --git a/computercraft/cc/turtle.py b/computercraft/cc/turtle.py new file mode 100644 index 0000000..71b7cd0 --- /dev/null +++ b/computercraft/cc/turtle.py @@ -0,0 +1,266 @@ +from typing import Optional + +from ..errors import LuaException +from ..sess import eval_lua + + +def inspect_result(rp): + success = rp.take_bool() + if not success: + msg = rp.take_string() + if msg == 'No block to inspect': + return None + raise LuaException(msg) + return rp.take_dict() + + +def dig_result(rp): + success = rp.take_bool() + if not success: + msg = rp.take_string() + if msg == 'Nothing to dig here': + return False + raise LuaException(msg) + return True + + +def suck_result(rp): + success = rp.take_bool() + if not success: + msg = rp.take_string() + if msg == 'No items to take': + return False + raise LuaException(msg) + return True + + +def attack_result(rp): + success = rp.take_bool() + if not success: + msg = rp.take_string() + if msg == 'Nothing to attack here': + return False + raise LuaException(msg) + return True + + +__all__ = ( + 'craft', + 'forward', + 'back', + 'up', + 'down', + 'turnLeft', + 'turnRight', + 'select', + 'getSelectedSlot', + 'getItemCount', + 'getItemSpace', + 'getItemDetail', + 'equipLeft', + 'equipRight', + 'attack', + 'attackUp', + 'attackDown', + 'dig', + 'digUp', + 'digDown', + 'place', + 'placeUp', + 'placeDown', + 'detect', + 'detectUp', + 'detectDown', + 'inspect', + 'inspectUp', + 'inspectDown', + 'compare', + 'compareUp', + 'compareDown', + 'compareTo', + 'drop', + 'dropUp', + 'dropDown', + 'suck', + 'suckUp', + 'suckDown', + 'refuel', + 'getFuelLevel', + 'getFuelLimit', + 'transferTo', +) + + +def craft(quantity: int = 64) -> None: + return eval_lua(b'G:turtle:M:craft', quantity).check_bool_error() + + +def forward() -> None: + return eval_lua(b'G:turtle:M:forward').check_bool_error() + + +def back() -> None: + return eval_lua(b'G:turtle:M:back').check_bool_error() + + +def up() -> None: + return eval_lua(b'G:turtle:M:up').check_bool_error() + + +def down() -> None: + return eval_lua(b'G:turtle:M:down').check_bool_error() + + +def turnLeft() -> None: + return eval_lua(b'G:turtle:M:turnLeft').check_bool_error() + + +def turnRight() -> None: + return eval_lua(b'G:turtle:M:turnRight').check_bool_error() + + +def select(slotNum: int) -> None: + return eval_lua(b'G:turtle:M:select', slotNum).check_bool_error() + + +def getSelectedSlot() -> int: + return eval_lua(b'G:turtle:M:getSelectedSlot').take_int() + + +def getItemCount(slotNum: int = None) -> int: + return eval_lua(b'G:turtle:M:getItemCount', slotNum).take_int() + + +def getItemSpace(slotNum: int = None) -> int: + return eval_lua(b'G:turtle:M:getItemSpace', slotNum).take_int() + + +def getItemDetail(slotNum: int = None) -> Optional[dict]: + rp = eval_lua(b'G:turtle:M:getItemDetail', slotNum) + if rp.peek() is None: + return None + return rp.take_dict() + + +def equipLeft() -> None: + return eval_lua(b'G:turtle:M:equipLeft').check_bool_error() + + +def equipRight() -> None: + return eval_lua(b'G:turtle:M:equipRight').check_bool_error() + + +def attack() -> bool: + return attack_result(eval_lua(b'G:turtle:M:attack')) + + +def attackUp() -> bool: + return attack_result(eval_lua(b'G:turtle:M:attackUp')) + + +def attackDown() -> bool: + return attack_result(eval_lua(b'G:turtle:M:attackDown')) + + +def dig() -> bool: + return dig_result(eval_lua(b'G:turtle:M:dig')) + + +def digUp() -> bool: + return dig_result(eval_lua(b'G:turtle:M:digUp')) + + +def digDown() -> bool: + return dig_result(eval_lua(b'G:turtle:M:digDown')) + + +def place(signText: str = None) -> None: + return eval_lua(b'G:turtle:M:place', signText).check_bool_error() + + +def placeUp(signText: str = None) -> None: + return eval_lua(b'G:turtle:M:placeUp', signText).check_bool_error() + + +def placeDown(signText: str = None) -> None: + return eval_lua(b'G:turtle:M:placeDown', signText).check_bool_error() + + +def detect() -> bool: + return eval_lua(b'G:turtle:M:detect').take_bool() + + +def detectUp() -> bool: + return eval_lua(b'G:turtle:M:detectUp').take_bool() + + +def detectDown() -> bool: + return eval_lua(b'G:turtle:M:detectDown').take_bool() + + +def inspect() -> Optional[dict]: + return inspect_result(eval_lua(b'G:turtle:M:inspect')) + + +def inspectUp() -> Optional[dict]: + return inspect_result(eval_lua(b'G:turtle:M:inspectUp')) + + +def inspectDown() -> Optional[dict]: + return inspect_result(eval_lua(b'G:turtle:M:inspectDown')) + + +def compare() -> bool: + return eval_lua(b'G:turtle:M:compare').take_bool() + + +def compareUp() -> bool: + return eval_lua(b'G:turtle:M:compareUp').take_bool() + + +def compareDown() -> bool: + return eval_lua(b'G:turtle:M:compareDown').take_bool() + + +def compareTo(slot: int) -> bool: + return eval_lua(b'G:turtle:M:compareTo', slot).take_bool() + + +def drop(count: int = None) -> None: + return eval_lua(b'G:turtle:M:drop', count).check_bool_error() + + +def dropUp(count: int = None) -> None: + return eval_lua(b'G:turtle:M:dropUp', count).check_bool_error() + + +def dropDown(count: int = None) -> None: + return eval_lua(b'G:turtle:M:dropDown', count).check_bool_error() + + +def suck(amount: int = None) -> bool: + return suck_result(eval_lua(b'G:turtle:M:suck', amount)) + + +def suckUp(amount: int = None) -> bool: + return suck_result(eval_lua(b'G:turtle:M:suckUp', amount)) + + +def suckDown(amount: int = None) -> bool: + return suck_result(eval_lua(b'G:turtle:M:suckDown', amount)) + + +def refuel(quantity: int = None) -> None: + return eval_lua(b'G:turtle:M:refuel', quantity).check_bool_error() + + +def getFuelLevel() -> int: + return eval_lua(b'G:turtle:M:getFuelLevel').take_int() + + +def getFuelLimit() -> int: + return eval_lua(b'G:turtle:M:getFuelLimit').take_int() + + +def transferTo(slot: int, quantity: int = None) -> None: + return eval_lua(b'G:turtle:M:transferTo', slot, quantity).check_bool_error() diff --git a/computercraft/cc/window.py b/computercraft/cc/window.py new file mode 100644 index 0000000..21e58b3 --- /dev/null +++ b/computercraft/cc/window.py @@ -0,0 +1,37 @@ +from typing import Tuple + +from ..sess import lua_context_object, ReferenceObject +from .term import TermMixin, TermTarget + + +__all__ = ('create', ) + + +class TermWindow(ReferenceObject, TermMixin): + def setVisible(self, visibility: bool) -> None: + return self._call(b'setVisible', visibility).take_none() + + def redraw(self) -> None: + return self._call(b'redraw').take_none() + + def restoreCursor(self) -> None: + return self._call(b'restoreCursor').take_none() + + def getPosition(self) -> Tuple[int, int]: + rp = self._call(b'getPosition') + return tuple(rp.take_int() for _ in range(2)) + + def reposition(self, x: int, y: int, width: int = None, height: int = None, parent: TermTarget = None) -> None: + return self._call(b'reposition', x, y, width, height, parent).take_none() + + def getLine(self, y: int) -> Tuple[str, bytes, bytes]: + rp = self._call(b'getLine', y) + return rp.take_string(), rp.take_bytes(), rp.take_bytes() + + +def create( + parentTerm: TermTarget, x: int, y: int, width: int, height: int, visible: bool = None, +) -> TermWindow: + return TermWindow(lua_context_object( + b'window.create(...)', + (parentTerm, x, y, width, height, visible))) diff --git a/computercraft/cc_peripherals/__init__.py b/computercraft/cc_peripherals/__init__.py new file mode 100644 index 0000000..fa92df2 --- /dev/null +++ b/computercraft/cc_peripherals/__init__.py @@ -0,0 +1,52 @@ +def register_std_peripherals(register): + from .command import CommandPeripheral + from .computer import ComputerPeripheral + from .drive import DrivePeripheral + from .inventory import InventoryPeripheral + from .modem import WirelessModemPeripheral, WiredModemPeripheral + from .printer import PrinterPeripheral + from .speaker import SpeakerPeripheral + from .term import MonitorPeripheral + from .workbench import WorkbenchPeripheral + + register('command', CommandPeripheral) + register('drive', DrivePeripheral) + + def _cmp(side, ptype, call): + p = ComputerPeripheral(side) + p.TYPE = ptype + return p + + for k in ['computer', 'turtle']: + register(k, _cmp) + + def _inv(side, ptype, call): + p = InventoryPeripheral(side) + p.TYPE = ptype + return p + + for k in [ + 'chest', + 'furnace', + 'barrel', + 'hopper', + 'dropper', + 'dispenser', + 'blast_furnace', + 'smoker', + 'shulker_box', + 'brewing_stand', + ]: + register('minecraft:' + k, _inv) + + def _modem(side, ptype, call): + if call(b'isWireless').take_bool(): + return WirelessModemPeripheral(side) + else: + return WiredModemPeripheral(side) + + register('modem', _modem) + register('printer', PrinterPeripheral) + register('speaker', SpeakerPeripheral) + register('monitor', MonitorPeripheral) + register('workbench', WorkbenchPeripheral) diff --git a/computercraft/cc_peripherals/_base.py b/computercraft/cc_peripherals/_base.py new file mode 100644 index 0000000..373f1ad --- /dev/null +++ b/computercraft/cc_peripherals/_base.py @@ -0,0 +1,22 @@ +from ..lua import LuaExpr +from ..sess import eval_lua + + +class BasePeripheral(LuaExpr): + TYPE = 'peripheral' + + def __init__(self, side: str): + self._side = side + + @property + def side(self): + return self._side + + def __str__(self): + return ''.format(self.TYPE, self._side) + + def _call(self, method, *args): + return eval_lua(b'G:peripheral:M:call', self._side, method, *args) + + def get_expr_code(self): + return b'return peripheral.wrap("' + self._side.encode('ascii') + b'")' diff --git a/computercraft/cc_peripherals/command.py b/computercraft/cc_peripherals/command.py new file mode 100644 index 0000000..d306c98 --- /dev/null +++ b/computercraft/cc_peripherals/command.py @@ -0,0 +1,17 @@ +from ._base import BasePeripheral + + +__all__ = ('CommandPeripheral', ) + + +class CommandPeripheral(BasePeripheral): + TYPE = 'command' + + def getCommand(self) -> str: + return self._call(b'getCommand').take_string() + + def setCommand(self, command: str) -> None: + return self._call(b'setCommand', command).take_none() + + def runCommand(self): + return self._call(b'runCommand').check_bool_error() diff --git a/computercraft/cc_peripherals/computer.py b/computercraft/cc_peripherals/computer.py new file mode 100644 index 0000000..9d4406b --- /dev/null +++ b/computercraft/cc_peripherals/computer.py @@ -0,0 +1,28 @@ +from typing import Optional + +from ._base import BasePeripheral + + +__all__ = ('ComputerPeripheral', ) + + +class ComputerPeripheral(BasePeripheral): + TYPE = 'computer' + + def turnOn(self) -> None: + return self._call(b'turnOn').take_none() + + def shutdown(self) -> None: + return self._call(b'shutdown').take_none() + + def reboot(self) -> None: + return self._call(b'reboot').take_none() + + def getID(self) -> int: + return self._call(b'getID').take_int() + + def getLabel(self) -> Optional[str]: + return self._call(b'getLabel').take_option_string() + + def isOn(self) -> bool: + return self._call(b'isOn').take_bool() diff --git a/computercraft/cc_peripherals/drive.py b/computercraft/cc_peripherals/drive.py new file mode 100644 index 0000000..9b1b4ae --- /dev/null +++ b/computercraft/cc_peripherals/drive.py @@ -0,0 +1,43 @@ +from typing import Optional, Union + +from ._base import BasePeripheral + + +__all__ = ('DrivePeripheral', ) + + +class DrivePeripheral(BasePeripheral): + TYPE = 'drive' + + def isDiskPresent(self) -> bool: + return self._call(b'isDiskPresent').take_bool() + + def getDiskLabel(self) -> Optional[str]: + return self._call(b'getDiskLabel').take_option_string() + + def setDiskLabel(self, label: Optional[str]) -> None: + return self._call(b'setDiskLabel', label).take_none() + + def hasData(self) -> bool: + return self._call(b'hasData').take_bool() + + def getMountPath(self) -> Optional[str]: + return self._call(b'getMountPath').take_option_string() + + def hasAudio(self) -> bool: + return self._call(b'hasAudio').take_bool() + + def getAudioTitle(self) -> Optional[Union[bool, str]]: + return self._call(b'getAudioTitle').take_option_string_bool() + + def playAudio(self) -> None: + return self._call(b'playAudio').take_none() + + def stopAudio(self) -> None: + return self._call(b'stopAudio').take_none() + + def ejectDisk(self) -> None: + return self._call(b'ejectDisk').take_none() + + def getDiskID(self) -> Optional[int]: + return self._call(b'getDiskID').take_option_int() diff --git a/computercraft/cc_peripherals/inventory.py b/computercraft/cc_peripherals/inventory.py new file mode 100644 index 0000000..bed2cad --- /dev/null +++ b/computercraft/cc_peripherals/inventory.py @@ -0,0 +1,25 @@ +from typing import Dict, Optional + +from ._base import BasePeripheral + + +__all__ = ('InventoryPeripheral', ) + + +class InventoryPeripheral(BasePeripheral): + TYPE = 'inventory' + + def getItemDetail(self, slot: int) -> Optional[dict]: + return self._call(b'getItemDetail', slot).take() + + def list(self) -> Dict[int, dict]: + return self._call(b'list').take_dict() + + def pullItems(self, fromName: str, fromSlot: int, limit: int = None, toSlot: int = None) -> int: + return self._call(b'pullItems', fromName, fromSlot, limit, toSlot).take_int() + + def pushItems(self, toName: str, fromSlot: int, limit: int = None, toSlot: int = None) -> int: + return self._call(b'pushItems', toName, fromSlot, limit, toSlot).take_int() + + def size(self) -> int: + return self._call(b'size').take_int() diff --git a/computercraft/cc_peripherals/modem.py b/computercraft/cc_peripherals/modem.py new file mode 100644 index 0000000..c748304 --- /dev/null +++ b/computercraft/cc_peripherals/modem.py @@ -0,0 +1,79 @@ +from dataclasses import dataclass +from typing import Any, List, Optional + +from .. import ser +from ..lua import LuaNum +from ._base import BasePeripheral + + +__all__ = ('ModemMessage', 'WirelessModemPeripheral', 'WiredModemPeripheral') + + +@dataclass +class ModemMessage: + reply_channel: int + content: Any + distance: LuaNum + + +class ModemMixin: + def isOpen(self, channel: int) -> bool: + return self._call(b'isOpen', channel).take_bool() + + def open(self, channel: int) -> None: + return self._call(b'open', channel).take_none() + + def close(self, channel: int) -> None: + return self._call(b'close', channel).take_none() + + def closeAll(self) -> None: + return self._call(b'closeAll').take_none() + + def transmit(self, channel: int, replyChannel: int, message: Any) -> None: + return self._call(b'transmit', channel, replyChannel, message).take_none() + + def isWireless(self) -> bool: + return self._call(b'isWireless').take_bool() + + def receive(self, channel: int): + from ..cc.os import captureEvent + + if self.isOpen(channel): + raise Exception('Channel is busy') + + side = self._side.encode(ser._CC_ENC) + self.open(channel) + try: + for evt in captureEvent('modem_message'): + if evt[0] != side: + continue + if evt[1] != channel: + continue + yield ModemMessage(*evt[2:]) + finally: + self.close(channel) + + +class WirelessModemPeripheral(BasePeripheral, ModemMixin): + TYPE = 'modem' + + +class WiredModemPeripheral(BasePeripheral, ModemMixin): + TYPE = 'modem' + + def getNameLocal(self) -> Optional[str]: + return self._call(b'getNameLocal').take_option_string() + + def getNamesRemote(self) -> List[str]: + return self._call(b'getNamesRemote').take_list_of_strings() + + def getTypeRemote(self, peripheralName: str) -> Optional[str]: + return self._call(b'getTypeRemote', peripheralName).take_option_string() + + def isPresentRemote(self, peripheralName: str) -> bool: + return self._call(b'isPresentRemote', peripheralName).take_bool() + + def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]: + # use instead getMethodsRemote and callRemote + from ..cc.peripheral import wrap + return wrap(peripheralName) diff --git a/computercraft/cc_peripherals/printer.py b/computercraft/cc_peripherals/printer.py new file mode 100644 index 0000000..fee06a8 --- /dev/null +++ b/computercraft/cc_peripherals/printer.py @@ -0,0 +1,40 @@ +from typing import Tuple + +from .. import ser +from ._base import BasePeripheral + + +__all__ = ('PrinterPeripheral', ) + + +class PrinterPeripheral(BasePeripheral): + TYPE = 'printer' + + def newPage(self) -> bool: + return self._call(b'newPage').take_bool() + + def endPage(self) -> bool: + return self._call(b'endPage').take_bool() + + def write(self, text: str) -> None: + return self._call(b'write', ser.cc_dirty_encode(text)).take_none() + + def setCursorPos(self, x: int, y: int) -> None: + return self._call(b'setCursorPos', x, y).take_none() + + def getCursorPos(self) -> Tuple[int, int]: + rp = self._call(b'getCursorPos') + return tuple(rp.take_int() for _ in range(2)) + + def getPageSize(self) -> Tuple[int, int]: + rp = self._call(b'getPageSize') + return tuple(rp.take_int() for _ in range(2)) + + def setPageTitle(self, title: str) -> None: + return self._call(b'setPageTitle', title).take_none() + + def getPaperLevel(self) -> int: + return self._call(b'getPaperLevel').take_int() + + def getInkLevel(self) -> int: + return self._call(b'getInkLevel').take_int() diff --git a/computercraft/cc_peripherals/speaker.py b/computercraft/cc_peripherals/speaker.py new file mode 100644 index 0000000..188c6de --- /dev/null +++ b/computercraft/cc_peripherals/speaker.py @@ -0,0 +1,36 @@ +from ._base import BasePeripheral + + +__all__ = ('SpeakerPeripheral', ) + + +class SpeakerPeripheral(BasePeripheral): + TYPE = 'speaker' + + def playNote(self, instrument: str, volume: int = 1, pitch: int = 1) -> bool: + # instrument: + # https://minecraft.gamepedia.com/Note_Block#Instruments + # bass + # basedrum + # bell + # chime + # flute + # guitar + # hat + # snare + # xylophone + # iron_xylophone + # pling + # banjo + # bit + # didgeridoo + # cow_bell + + # volume 0..3 + # pitch 0..24 + return self._call(b'playNote', instrument, volume, pitch).take_bool() + + def playSound(self, sound: str, volume: int = 1, pitch: int = 1) -> bool: + # volume 0..3 + # pitch 0..2 + return self._call(b'playSound', sound, volume, pitch).take_bool() diff --git a/computercraft/cc_peripherals/term.py b/computercraft/cc_peripherals/term.py new file mode 100644 index 0000000..7405937 --- /dev/null +++ b/computercraft/cc_peripherals/term.py @@ -0,0 +1,15 @@ +from ..cc.term import TermMixin +from ._base import BasePeripheral + + +__all__ = ('MonitorPeripheral', ) + + +class MonitorPeripheral(BasePeripheral, TermMixin): + TYPE = 'monitor' + + def getTextScale(self) -> int: + return self._call(b'getTextScale').take_int() + + def setTextScale(self, scale: int) -> None: + return self._call(b'setTextScale', scale).take_none() diff --git a/computercraft/cc_peripherals/workbench.py b/computercraft/cc_peripherals/workbench.py new file mode 100644 index 0000000..600ee77 --- /dev/null +++ b/computercraft/cc_peripherals/workbench.py @@ -0,0 +1,11 @@ +from ._base import BasePeripheral + + +__all__ = ('WorkbenchPeripheral', ) + + +class WorkbenchPeripheral(BasePeripheral): + TYPE = 'workbench' + + def craft(self, quantity: int = 64): + return self._call(b'craft', quantity).check_bool_error() diff --git a/computercraft/errors.py b/computercraft/errors.py index e2207d5..0d6d75e 100644 --- a/computercraft/errors.py +++ b/computercraft/errors.py @@ -1,2 +1,6 @@ class LuaException(Exception): - pass + @property + def message(self): + if len(self.args) < 1: + return None + return self.args[0] diff --git a/computercraft/lua.py b/computercraft/lua.py index ee6f93d..951b938 100644 --- a/computercraft/lua.py +++ b/computercraft/lua.py @@ -10,74 +10,6 @@ def get_expr_code(self): raise NotImplementedError -class ArbLuaExpr(LuaExpr): - def __init__(self, code: str): - self._code = code - - def get_expr_code(self): - return self._code - - -def lua_string(v): - return '"{}"'.format( - v.replace('\\', '\\\\') - .replace('\a', '\\a') - .replace('\b', '\\b') - .replace('\f', '\\f') - .replace('\n', '\\n') - .replace('\r', '\\r') - .replace('\t', '\\t') - .replace('\v', '\\v') - .replace('"', '\\"') - .replace("'", "\\'") - .replace('[', '\\[') - .replace(']', '\\]') - ) - - -def lua_list(v): - return '{' + ', '.join(lua_value(x) for x in v) + '}' - - -def lua_dict(v): - return '{' + ', '.join( - '[{}]={}'.format(lua_value(k), lua_value(v)) for k, v in v.items() - ) + '}' - - -def lua_value(v): - if v is None: - return 'nil' - if v is False: - return 'false' - if v is True: - return 'true' - if isinstance(v, str): - return lua_string(v) - if isinstance(v, (int, float)): - return str(v) - if isinstance(v, list): - return lua_list(v) - if isinstance(v, dict): - return lua_dict(v) - if isinstance(v, LuaExpr): - return v.get_expr_code() - raise ValueError('Can\'t convert a value to lua {}'.format(v)) - - -def lua_args(*params): - for idx in range(len(params) - 1, -1, -1): - if params[idx] is not None: - break - else: - idx = -1 - params = params[:idx + 1] - return ', '.join(lua_value(p) for p in params) - - -def lua_call(name, *params): - return '{}({})'.format(name, lua_args(*params)) - - -def return_lua_call(name, *params): - return 'return ' + lua_call(name, *params) +class TempObject: + def __init__(self, fid: bytes): + self._fid = fid diff --git a/computercraft/oc/__init__.py b/computercraft/oc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/computercraft/oc/_pkg.py b/computercraft/oc/_pkg.py new file mode 100644 index 0000000..ea9b783 --- /dev/null +++ b/computercraft/oc/_pkg.py @@ -0,0 +1 @@ +__all__ = () diff --git a/computercraft/oc/colors.py b/computercraft/oc/colors.py new file mode 100644 index 0000000..6e739e5 --- /dev/null +++ b/computercraft/oc/colors.py @@ -0,0 +1,37 @@ +_index = ( + 'white', + 'orange', + 'magenta', + 'lightblue', + 'yellow', + 'lime', + 'pink', + 'gray', + 'silver', + 'cyan', + 'purple', + 'blue', + 'brown', + 'green', + 'red', + 'black', +) + + +class Colors: + def __getitem__(self, key: int) -> str: + return _index[key] + + def __iter__(self): + return enumerate(_index) + + def __len__(self): + return len(_index) + + +for i, v in enumerate(_index): + setattr(Colors, v, i) +del i, v + + +__replace_module__ = Colors() diff --git a/computercraft/oc/component.py b/computercraft/oc/component.py new file mode 100644 index 0000000..d711a99 --- /dev/null +++ b/computercraft/oc/component.py @@ -0,0 +1,75 @@ +from typing import Dict, Type, TypeVar +from uuid import UUID + +from ..sess import eval_lua +from ..oc_components import register_std_components + + +__all__ = ( + 'isAvailable', + 'slot', + 'type', + 'list', + 'proxy', + 'getPrimaryAddress', + 'getPrimary', + 'registerType', + 'setPrimary', +) + + +C = TypeVar('Component') +type_map = {} + + +def isAvailable(componentType: str) -> bool: + return eval_lua(b'R:component:M:isAvailable', componentType).take_bool() + + +def slot(address: UUID) -> int: + r = eval_lua(b'R:component:M:slot', address) + r.check_nil_error() + return r.take_int() + + +def type(address: UUID) -> str: + r = eval_lua(b'R:component:M:type', address) + r.check_nil_error() + return r.take_string() + + +def list() -> Dict[UUID, str]: + # TODO: support parameters + return { + UUID(k): v for k, v in + eval_lua(b'R:component:M:list').take_dict().items()} + + +def proxy(address: UUID) -> C: + t = type(address) + if t not in type_map: + raise TypeError('Unknown component type ' + t) + return type_map[t](address) + + +def getPrimaryAddress(componentType: str) -> UUID: + return eval_lua( + b'R:component:return _m.component.getPrimary(...).address', + componentType, + ).take_uuid() + + +def getPrimary(componentType: str) -> C: + return proxy(getPrimaryAddress(componentType)) + + +def setPrimary(componentType: str, address: UUID) -> None: + return eval_lua( + b'R:component:M:setPrimary', componentType, address).take_none() + + +def registerType(componentType: str, pcls: Type[C]): + type_map[componentType] = pcls + + +register_std_components(registerType) diff --git a/computercraft/oc/computer.py b/computercraft/oc/computer.py new file mode 100644 index 0000000..7a380e6 --- /dev/null +++ b/computercraft/oc/computer.py @@ -0,0 +1,77 @@ +from typing import Optional, Union +from uuid import UUID + +from ..sess import eval_lua + + +__all__ = ( + 'address', + 'tmpAddress', + 'freeMemory', + 'totalMemory', + 'energy', + 'maxEnergy', + 'uptime', + 'shutdown', + 'getBootAddress', + 'setBootAddress', + 'runlevel', +) + + +def address() -> UUID: + return eval_lua(b'R:computer:M:address').take_uuid() + + +def tmpAddress() -> UUID: + return eval_lua(b'R:computer:M:tmpAddress').take_uuid() + + +def freeMemory() -> int: + return eval_lua(b'R:computer:M:freeMemory').take_int() + + +def totalMemory() -> int: + return eval_lua(b'R:computer:M:totalMemory').take_int() + + +def energy() -> float: + return eval_lua(b'R:computer:M:energy').take_number() + + +def maxEnergy() -> float: + return eval_lua(b'R:computer:M:maxEnergy').take_number() + + +def uptime() -> float: + return eval_lua(b'R:computer:M:uptime').take_number() + + +def shutdown(reboot: bool = False) -> None: + # TODO: this function never returns + return eval_lua(b'R:computer:M:shutdown', reboot).take_none() + + +def getBootAddress() -> UUID: + return eval_lua(b'R:computer:M:getBootAddress').take_uuid() + + +def setBootAddress(address: Optional[UUID] = None) -> None: + return eval_lua(b'R:computer:M:setBootAddress', address).take_none() + + +def runlevel() -> Union[str, int]: + r = eval_lua(b'R:computer:M:runlevel') + if r.peek_type() is bytes: + return r.take_string() + return r.take_int() + + +# TODO: +# computer.users(): string, ... +# computer.addUser(name: string): boolean or nil, string +# computer.removeUser(name: string): boolean +# computer.pushSignal(name: string[, ...]) +# computer.pullSignal([timeout: number]): name, ... +# computer.beep([frequency:string or number[, duration: number]) +# computer.getDeviceInfo(): table diff --git a/computercraft/oc/filesystem.py b/computercraft/oc/filesystem.py new file mode 100644 index 0000000..2d6eb1b --- /dev/null +++ b/computercraft/oc/filesystem.py @@ -0,0 +1,193 @@ +from contextlib import contextmanager +from typing import List, Optional, Tuple, Union +from uuid import UUID + +from ..sess import eval_lua, lua_context_object, ContextObject + + +__all__ = ( + 'isAutorunEnabled', + 'setAutorunEnabled', + 'canonical', + 'segments', + 'concat', + 'path', + 'name', + 'mount', + 'mounts', + 'umount', + 'isLink', + 'link', + 'get', + 'exists', + 'size', + 'isDirectory', + 'lastModified', + 'list', + 'makeDirectory', + 'remove', + 'rename', + 'copy', + 'File', + 'open', +) + + +def isAutorunEnabled() -> bool: + return eval_lua(b'R:filesystem:M:isAutorunEnabled').take_bool() + + +def setAutorunEnabled(value: bool) -> None: + return eval_lua(b'R:filesystem:M:setAutorunEnabled', value).take_none() + + +def canonical(path: str) -> str: + return eval_lua(b'R:filesystem:M:canonical', path).take_string() + + +def segments(path: str) -> List[str]: + return eval_lua(b'R:filesystem:M:segments', path).take_list_of_strings() + + +def concat(pathA: str, pathB: str, *paths: str) -> str: + return eval_lua(b'R:filesystem:M:concat', pathA, pathB, *paths).take_string() + + +def path(path: str) -> str: + return eval_lua(b'R:filesystem:M:path', path).take_string() + + +def name(path: str) -> str: + return eval_lua(b'R:filesystem:M:name', path).take_string() + + +# TODO: implement: +# filesystem.proxy(filter: string): table or nil, string +# same as component.proxy + + +def mount(fs: UUID, path: str) -> bool: + # TODO: support component object as parameter + r = eval_lua(b'R:filesystem:M:mount', fs, path) + r.check_nil_error() + return r.take_bool() + + +def mounts() -> List[Tuple[UUID, str]]: + r = eval_lua( + b'R:filesystem:local r={};' + b'for c,t in _m.filesystem.mounts() do ' + b'r[#r+1]=c.address;r[#r+1]=t;end;return r').take_proc() + o = [] + while r.peek() is not None: + addr = r.take_uuid() + path = r.take_string() + o.append((addr, path)) + return o + + +def umount(fsOrPath: Union[UUID, str]) -> bool: + # TODO: support component object as parameter + return eval_lua(b'R:filesystem:M:umount', fsOrPath).take_bool() + + +def isLink(path: str) -> Tuple[bool, Optional[str]]: + r = eval_lua(b'R:filesystem:M:isLink', path) + is_link = r.take_bool() + target = r.take_option_string() + return is_link, target + + +def link(target: str, linkpath: str) -> bool: + r = eval_lua(b'R:filesystem:M:link', target, linkpath) + r.check_nil_error() + return r.take_bool() + + +def get(path: str) -> Tuple[UUID, str]: + r = eval_lua( + b'R:filesystem:local a,b=_m.filesystem.get(...);' + b'if a then a=a.address end;return a,b', path) + r.check_nil_error() + addr = r.take_uuid() + target = r.take_string() + return addr, target + + +def exists(path: str) -> bool: + return eval_lua(b'R:filesystem:M:exists', path).take_bool() + + +def size(path: str) -> int: + return eval_lua(b'R:filesystem:M:size', path).take_int() + + +def isDirectory(path: str) -> bool: + return eval_lua(b'R:filesystem:M:isDirectory', path).take_bool() + + +def lastModified(path: str) -> float: + return eval_lua(b'R:filesystem:M:lastModified', path).take_number() + + +def list(path: str) -> List[str]: + r = eval_lua( + b'R:filesystem:local r={};' + b'for n in _m.filesystem.list(...) do r[#r+1]=n end;return r', + path) + r.check_nil_error() + return r.take_list_of_strings() + + +def makeDirectory(path: str) -> bool: + r = eval_lua(b'R:filesystem:M:makeDirectory', path) + r.check_nil_error() + return r.take_bool() + + +def remove(path: str) -> bool: + r = eval_lua(b'R:filesystem:M:remove', path) + r.check_nil_error() + return r.take_bool() + + +def rename(oldPath: str, newPath: str) -> bool: + r = eval_lua(b'R:filesystem:M:rename', oldPath, newPath) + r.check_nil_error() + return r.take_bool() + + +def copy(fromPath: str, toPath: str) -> bool: + r = eval_lua(b'R:filesystem:M:copy', fromPath, toPath) + r.check_nil_error() + return r.take_bool() + + +class File(ContextObject): + CALL_OP = b':' + + def read(self, n: int) -> Optional[bytes]: + r = self._call(b'read', n) + r.check_nil_error(allow_nil_nil=True) + return r.take_option_bytes() + + def seek(self, whence: str, offset: int = 0) -> int: + r = self._call(b'seek', whence, offset) + r.check_nil_error() + return r.take_int() + + def write(self, data: bytes) -> bool: + r = self._call(b'write', data) + r.check_nil_error() + return r.take_bool() + + +@contextmanager +def open(path: str, mode: str = 'r'): + with lua_context_object( + b'_m.filesystem.open(...)', + (path, mode), + b'{e}:close()', + b'R:filesystem:', + ) as fid: + yield File(fid) diff --git a/computercraft/oc/keyboard.py b/computercraft/oc/keyboard.py new file mode 100644 index 0000000..40f81dd --- /dev/null +++ b/computercraft/oc/keyboard.py @@ -0,0 +1,179 @@ +from typing import Union + +from ..sess import eval_lua + + +# curl -s https://raw.githubusercontent.com/MightyPirates/OpenComputers/master-MC1.12/src/main/\ +# resources/assets/opencomputers/loot/openos/lib/core/full_keyboard.lua \ +# | grep -E '^keyboard\.keys' \ +# | sed -E 's/^keyboard\.keys\.(\S+)\s*\=\s*([0-9A-Fx]+).*$/\2: '\''\1'\'',/' \ +# | sed -E 's/^keyboard\.keys\["(\S+)"\]\s*\=\s*([0-9A-Fx]+).*$/\2: '\''\1'\'',/' + +_index = { + 0x02: '1', + 0x03: '2', + 0x04: '3', + 0x05: '4', + 0x06: '5', + 0x07: '6', + 0x08: '7', + 0x09: '8', + 0x0A: '9', + 0x0B: '0', + 0x1E: 'a', + 0x30: 'b', + 0x2E: 'c', + 0x20: 'd', + 0x12: 'e', + 0x21: 'f', + 0x22: 'g', + 0x23: 'h', + 0x17: 'i', + 0x24: 'j', + 0x25: 'k', + 0x26: 'l', + 0x32: 'm', + 0x31: 'n', + 0x18: 'o', + 0x19: 'p', + 0x10: 'q', + 0x13: 'r', + 0x1F: 's', + 0x14: 't', + 0x16: 'u', + 0x2F: 'v', + 0x11: 'w', + 0x2D: 'x', + 0x15: 'y', + 0x2C: 'z', + 0x28: 'apostrophe', + 0x91: 'at', + 0x0E: 'back', + 0x2B: 'backslash', + 0x3A: 'capital', + 0x92: 'colon', + 0x33: 'comma', + 0x1C: 'enter', + 0x0D: 'equals', + 0x29: 'grave', + 0x1A: 'lbracket', + 0x1D: 'lcontrol', + 0x38: 'lmenu', + 0x2A: 'lshift', + 0x0C: 'minus', + 0x45: 'numlock', + 0xC5: 'pause', + 0x34: 'period', + 0x1B: 'rbracket', + 0x9D: 'rcontrol', + 0xB8: 'rmenu', + 0x36: 'rshift', + 0x46: 'scroll', + 0x27: 'semicolon', + 0x35: 'slash', + 0x39: 'space', + 0x95: 'stop', + 0x0F: 'tab', + 0x93: 'underline', + 0xC8: 'up', + 0xD0: 'down', + 0xCB: 'left', + 0xCD: 'right', + 0xC7: 'home', + 0xCF: 'end', + 0xC9: 'pageUp', + 0xD1: 'pageDown', + 0xD2: 'insert', + 0xD3: 'delete', + 0x3B: 'f1', + 0x3C: 'f2', + 0x3D: 'f3', + 0x3E: 'f4', + 0x3F: 'f5', + 0x40: 'f6', + 0x41: 'f7', + 0x42: 'f8', + 0x43: 'f9', + 0x44: 'f10', + 0x57: 'f11', + 0x58: 'f12', + 0x64: 'f13', + 0x65: 'f14', + 0x66: 'f15', + 0x67: 'f16', + 0x68: 'f17', + 0x69: 'f18', + 0x71: 'f19', + 0x70: 'kana', + 0x94: 'kanji', + 0x79: 'convert', + 0x7B: 'noconvert', + 0x7D: 'yen', + 0x90: 'circumflex', + 0x96: 'ax', + 0x52: 'numpad0', + 0x4F: 'numpad1', + 0x50: 'numpad2', + 0x51: 'numpad3', + 0x4B: 'numpad4', + 0x4C: 'numpad5', + 0x4D: 'numpad6', + 0x47: 'numpad7', + 0x48: 'numpad8', + 0x49: 'numpad9', + 0x37: 'numpadmul', + 0xB5: 'numpaddiv', + 0x4A: 'numpadsub', + 0x4E: 'numpadadd', + 0x53: 'numpaddecimal', + 0xB3: 'numpadcomma', + 0x9C: 'numpadenter', + 0x8D: 'numpadequals', +} + + +class Keys: + def __getitem__(self, key: int) -> str: + return _index[key] + + def __iter__(self): + return iter(_index.items()) + + def __len__(self): + return len(_index) + + +for i, v in _index.items(): + setattr(Keys, v, i) +del i, v +keys = Keys() + + +def isAltDown() -> bool: + return eval_lua(b'R:keyboard:M:isAltDown').take_bool_coerce_nil() + + +def isControl(char: int) -> bool: + return eval_lua(b'R:keyboard:M:isControl', char).take_bool() + + +def isControlDown() -> bool: + return eval_lua(b'R:keyboard:M:isControlDown').take_bool_coerce_nil() + + +def isKeyDown(charOrCode: Union[int, str]) -> bool: + return eval_lua(b'R:keyboard:M:isKeyDown', charOrCode).take_bool_coerce_nil() + + +def isShiftDown() -> bool: + return eval_lua(b'R:keyboard:M:isShiftDown').take_bool_coerce_nil() + + +__all__ = ( + 'keys', + 'isAltDown', + 'isControl', + 'isControlDown', + 'isKeyDown', + 'isShiftDown', +) diff --git a/computercraft/oc/os.py b/computercraft/oc/os.py new file mode 100644 index 0000000..50966ad --- /dev/null +++ b/computercraft/oc/os.py @@ -0,0 +1,45 @@ +from typing import NoReturn + +from ..sess import eval_lua + + +__all__ = ( + 'clock', + 'date', + 'time', + 'sleep', + 'exit', + 'tmpname', +) + + +def clock() -> float: + return eval_lua(b'R:os:M:clock').take_number() + + +def date() -> str: + # TODO: add parameters + return eval_lua(b'R:os:M:date').take_string() + + +def time() -> float: + return eval_lua(b'R:os:M:time').take_number() + + +def sleep(seconds: float) -> None: + return eval_lua(b'R:os:M:sleep').take_none() + + +def exit() -> NoReturn: + # TODO: add exit codes + raise SystemExit + + +def tmpname() -> str: + return eval_lua(b'R:os:M:tmpname').take_string() + + +# TODO: implement +# os.execute +# os.getenv +# os.setenv diff --git a/computercraft/oc/sides.py b/computercraft/oc/sides.py new file mode 100644 index 0000000..24d0853 --- /dev/null +++ b/computercraft/oc/sides.py @@ -0,0 +1,29 @@ +_index = ( + 'bottom', + 'top', + 'back', + 'front', + 'right', + 'left', +) + + +class Sides: + down = negy = bottom = 0 + up = posy = top = 1 + north = negz = back = 2 + south = posz = forward = front = 3 + west = negx = right = 4 + east = posx = left = 5 + + def __getitem__(self, key: int) -> str: + return _index[key] + + def __iter__(self): + return enumerate(_index) + + def __len__(self): + return len(_index) + + +__replace_module__ = Sides() diff --git a/computercraft/oc/term.py b/computercraft/oc/term.py new file mode 100644 index 0000000..b11a077 --- /dev/null +++ b/computercraft/oc/term.py @@ -0,0 +1,84 @@ +from typing import Tuple, Optional +from uuid import UUID + +from ..sess import eval_lua + + +__all__ = ( + 'isAvailable', + 'getViewport', + 'getCursor', + 'setCursor', + 'getCursorBlink', + 'setCursorBlink', + 'clear', + 'clearLine', + 'read', + 'write', + 'screen', + 'keyboard', +) + + +def isAvailable() -> bool: + return eval_lua(b'R:term:M:isAvailable').take_bool() + + +def getViewport() -> Tuple[int, int, int, int, int, int]: + r = eval_lua(b'R:term:M:getViewport') + return tuple(r.take_int() for _ in range(6)) + + +def getCursor() -> Tuple[int, int]: + r = eval_lua(b'R:term:M:getCursor') + return tuple(r.take_int() for _ in range(2)) + + +def setCursor(col: int, row: int) -> None: + return eval_lua(b'R:term:M:setCursor', col, row).take_none() + + +def getCursorBlink() -> bool: + return eval_lua(b'R:term:M:getCursorBlink').take_bool() + + +def setCursorBlink(enabled: bool) -> None: + return eval_lua(b'R:term:M:setCursorBlink', enabled).take_none() + + +def clear() -> None: + return eval_lua(b'R:term:M:clear').take_none() + + +def clearLine() -> None: + return eval_lua(b'R:term:M:clearLine').take_none() + + +def read(dobreak: bool = True, pwchar: Optional[str] = None) -> [str, None, False]: + r = eval_lua( + b'R:term:M:read', + None, # TODO: history: table + dobreak, + None, # TODO: hint:table or function + pwchar) + x = r.peek() + if x is None or x is False: + return x + return r.take_string() + + +def write(value: str, wrap: bool = False) -> None: + return eval_lua(b'R:term:M:write', value, wrap).take_none() + + +def screen() -> UUID: + return eval_lua(b'R:term:M:screen').take_uuid() + + +def keyboard() -> UUID: + return eval_lua(b'R:term:M:keyboard').take_uuid() + + +# TODO: term.gpu(): table +# TODO: term.pull([...]): ... +# TODO: term.bind(gpu) diff --git a/computercraft/oc_components/__init__.py b/computercraft/oc_components/__init__.py new file mode 100644 index 0000000..75fc98f --- /dev/null +++ b/computercraft/oc_components/__init__.py @@ -0,0 +1,4 @@ +def register_std_components(register): + from .gpu import GPUComponent + + register('gpu', GPUComponent) diff --git a/computercraft/oc_components/_base.py b/computercraft/oc_components/_base.py new file mode 100644 index 0000000..da59820 --- /dev/null +++ b/computercraft/oc_components/_base.py @@ -0,0 +1,24 @@ +from uuid import UUID + +from ..lua import LuaExpr +from ..sess import eval_lua + + +class BaseComponent(LuaExpr): + TYPE = 'component' + + def __init__(self, address: UUID): + self._addr = address + + @property + def address(self): + return self._addr + + def __str__(self): + return ''.format(self.TYPE, self._addr) + + def _call(self, method, *args): + return eval_lua(b'R:component:M:invoke', self._addr, method, *args) + + def get_expr_code(self): + return b'component.proxy("' + str(self._addr).encode('ascii') + b'")' diff --git a/computercraft/oc_components/gpu.py b/computercraft/oc_components/gpu.py new file mode 100644 index 0000000..eef4e7f --- /dev/null +++ b/computercraft/oc_components/gpu.py @@ -0,0 +1,25 @@ +from typing import Tuple + +from ._base import BaseComponent + + +class GPUComponent(BaseComponent): + TYPE = 'gpu' + + def maxDepth(self) -> int: + return self._call(b'maxDepth').take_int() + + def getDepth(self) -> int: + return self._call(b'getDepth').take_int() + + def setDepth(self, bit: int) -> str: + return self._call(b'setDepth').take_string() + + def maxResolution(self) -> Tuple[int, int]: + return self._call(b'maxResolution').take_2d_int() + + def getResolution(self) -> Tuple[int, int]: + return self._call(b'getResolution').take_2d_int() + + def setResolution(self, width: int, height: int) -> bool: + return self._call(b'setResolution', width, height).take_bool() diff --git a/computercraft/rproc.py b/computercraft/rproc.py index ae84a9d..27ba742 100644 --- a/computercraft/rproc.py +++ b/computercraft/rproc.py @@ -1,148 +1,165 @@ -from .errors import LuaException - - -def coro(result): - assert isinstance(result, list) - assert len(result) >= 1 - success, *result = result - assert isinstance(success, bool) - if not success: - raise LuaException(result) - if result == []: - return None - if len(result) == 1: - return result[0] - return result - - -def nil(result): - assert result is None - return result - - -def boolean(result): - assert result is True or result is False - return result - - -def integer(result): - assert isinstance(result, int) - assert not isinstance(result, bool) - return result - +from uuid import UUID -def number(result): - assert isinstance(result, (int, float)) - assert not isinstance(result, bool) - return result - - -def string(result): - assert isinstance(result, str) - return result +from .errors import LuaException -def any_dict(result): - assert isinstance(result, dict) - return result +def lua_table_to_list(x, length: int = None, low_index: int = 1): + if not x: + return [] if length is None else [None] * length + assert all(map(lambda k: isinstance(k, int), x.keys())) + assert min(x.keys()) >= low_index + dlen = max(x.keys()) - low_index + 1 + if length is not None: + assert dlen <= length + else: + length = dlen + return [x.get(i + low_index) for i in range(length)] -def any_list(result): - if result == {}: - result = [] - assert isinstance(result, list) - return result +def _decode_rec(enc, d): + if isinstance(d, bytes): + return d.decode(enc) + if isinstance(d, dict): + return { + _decode_rec(enc, k): _decode_rec(enc, v) + for k, v in d.items() + } + if isinstance(d, list): + return [_decode_rec(enc, v) for v in d] + return d -def fact_tuple(*components, tail_nils=0): - def proc(result): - result = any_list(result) - assert len(components) >= len(result) >= len(components) - tail_nils - while len(result) < len(components): - result.append(None) - assert len(components) == len(result) - return tuple(comp(value) for comp, value in zip(components, result)) - return proc +class ResultProc: + def __init__(self, result, enc): + self._v = result + self._enc = enc + self._i = 1 + def forward(self): + self._i += 1 -def fact_array(component): - def proc(result): - result = any_list(result) - return [component(value) for value in result] - return proc + def back(self): + self._i -= 1 + def peek(self): + return self._v.get(self._i) -def fact_option(component): - def proc(result): - if result is None: + def peek_type(self): + v = self.peek() + if v is None: return None - return component(result) - return proc - - -def fact_union(*case_pairs, pelse): - def proc(result): - for detector, processor in case_pairs: - if detector(result): - return processor(result) - return pelse(result) - return proc - - -def fact_scheme_dict(required, optional): - required_keys = set(required.keys()) - optional_keys = set(optional.keys()) - assert required_keys & optional_keys == set() - all_keys = required_keys | optional_keys - all_fns = required.copy() - all_fns.update(optional) - - def proc(result): - result = any_dict(result) - result_keys = set(result.keys()) - assert result_keys.issubset(all_keys) - assert required_keys.issubset(result_keys) - return {key: all_fns[key](value) for key, value in result.items()} - return proc - - -def fact_mono_dict(key, value): - def proc(result): - result = any_dict(result) - return {key(k): value(v) for k, v in result.items()} - return proc - - -tuple3_number = fact_tuple(number, number, number) -tuple2_integer = fact_tuple(integer, integer) -tuple3_integer = fact_tuple(integer, integer, integer) -tuple3_string = fact_tuple(string, string, string) -array_integer = fact_array(integer) -array_string = fact_array(string) -option_integer = fact_option(integer) -option_string = fact_option(string) -_try_result = fact_tuple(boolean, option_string, tail_nils=1) - - -def try_result(result): - success, error_msg = _try_result(result) - if success: - assert error_msg is None - return None - else: - raise LuaException(error_msg) - + return type(v) + + def take(self): + r = self.peek() + self.forward() + return r + + def take_none(self): + x = self.take() + assert x is None + return x + + def take_bool(self): + x = self.take() + assert x is True or x is False + return x + + def take_bool_coerce_nil(self): + x = self.take() + assert x is True or x is False or x is None + return bool(x) + + def take_int(self): + x = self.take() + assert isinstance(x, int) + assert not isinstance(x, bool) + return x + + def take_number(self): + x = self.take() + assert isinstance(x, (int, float)) + assert not isinstance(x, bool) + return x + + def take_bytes(self): + x = self.take() + assert isinstance(x, bytes) + return x + + def take_string(self): + return self.take_bytes().decode(self._enc) + + def take_unicode(self): + # specific case in CC, reading unicode files + return self.take_bytes().decode('utf-8') + + def take_uuid(self): + return UUID(self.take_bytes().decode('ascii')) + + def take_dict(self, decode_bytes=True): + x = self.take() + assert isinstance(x, dict) + if decode_bytes: + return _decode_rec(self._enc, x) + return x + + def take_list(self, length: int = None): + return lua_table_to_list(self.take_dict(), length) + + def take_proc(self): + x = self.take() + assert isinstance(x, dict) + return ResultProc(x, self._enc) + + def check_bool_error(self): + success = self.take_bool() + if not success: + raise LuaException(self.take_string()) + + def check_nil_error(self, allow_nil_nil=False): + if self.peek() is None: + self.forward() + if allow_nil_nil and self.peek() is None: + return + raise LuaException(self.take_string()) + + def take_option_int(self): + if self.peek() is None: + return None + return self.take_int() -def flat_try_result(result): - if result is True: - return None - return try_result(result) + def take_option_bytes(self): + if self.peek() is None: + return None + return self.take_bytes() + def take_option_string(self): + if self.peek() is None: + return None + return self.take_string() -option_string_bool = fact_option(fact_union( - ( - lambda v: v is True or v is False, - boolean, - ), - pelse=string, -)) + def take_option_unicode(self): + if self.peek() is None: + return None + return self.take_unicode() + + def take_option_string_bool(self): + p = self.peek() + if p is None or p is True or p is False: + self.forward() + return p + return self.take_string() + + def take_list_of_strings(self, length: int = None): + x = self.take_list(length) + assert all(isinstance(v, str) for v in x) + return x + + def take_2d_int(self): + x = self.take_list() + x = [lua_table_to_list(item) for item in x] + for row in x: + for item in row: + assert isinstance(item, int) + return x diff --git a/computercraft/ser.py b/computercraft/ser.py new file mode 100644 index 0000000..36f8e05 --- /dev/null +++ b/computercraft/ser.py @@ -0,0 +1,104 @@ +from typing import Any, Tuple +from uuid import UUID + +from . import lua + + +__all__ = ( + '_CC_ENC', + '_OC_ENC', + 'cc_dirty_encode', + 'serialize', + 'deserialize', +) + + +_CC_ENC = 'latin1' +_OC_ENC = 'utf-8' + +# encoding fast check +assert [bytes([i]) for i in range(256)] == [chr(i).encode(_CC_ENC) for i in range(256)] + + +def cc_dirty_encode(s: str) -> bytes: + return s.encode(_CC_ENC, errors='replace') + + +def serialize(v: Any, encoding: str) -> bytes: + if v is None: + return b'N' + elif v is False: + return b'F' + elif v is True: + return b'T' + elif isinstance(v, (int, float)): + return '[{}]'.format(v).encode('ascii') + elif isinstance(v, bytes): + return '<{}>'.format(len(v)).encode('ascii') + v + elif isinstance(v, UUID): + return serialize(str(v).encode('ascii'), encoding) + elif isinstance(v, str): + return serialize(v.encode(encoding), encoding) + elif isinstance(v, (list, tuple)): + items = [] + for k, x in enumerate(v, start=1): + items.append(b':' + serialize(k, encoding) + serialize(x, encoding)) + return b'{' + b''.join(items) + b'}' + elif isinstance(v, dict): + items = [] + for k, x in v.items(): + items.append(b':' + serialize(k, encoding) + serialize(x, encoding)) + return b'{' + b''.join(items) + b'}' + elif isinstance(v, lua.LuaExpr): + code = v.get_expr_code() + return 'E{}>'.format(len(code)).encode('ascii') + code + elif isinstance(v, lua.TempObject): + return 'X{}>'.format(len(v._fid)).encode('ascii') + v._fid + else: + raise ValueError('Value can\'t be serialized: {}'.format(repr(v))) + + +def _deserialize(b: bytes, _idx: int) -> Tuple[Any, int]: + tok = b[_idx] + _idx += 1 + if tok == 78: # N + return None, _idx + elif tok == 70: # F + return False, _idx + elif tok == 84: # T + return True, _idx + elif tok == 91: # [ + newidx = b.index(b']', _idx) + f = float(b[_idx:newidx]) + if f.is_integer(): + f = int(f) + return f, newidx + 1 + elif tok == 60: # < + newidx = b.index(b'>', _idx) + ln = int(b[_idx:newidx]) + return b[newidx + 1:newidx + 1 + ln], newidx + 1 + ln + elif tok == 123: # { + r = {} + while True: + tok = b[_idx] + _idx += 1 + if tok == 125: # } + break + key, _idx = _deserialize(b, _idx) + value, _idx = _deserialize(b, _idx) + r[key] = value + return r, _idx + else: + raise ValueError + + +def deserialize(b: bytes) -> Any: + return _deserialize(b, 0)[0] + + +def dcmditer(b: bytes): + yield b[0:1] + idx = 1 + while idx < len(b): + chunk, idx = _deserialize(b, idx) + yield chunk diff --git a/computercraft/server.py b/computercraft/server.py index eace649..1f2051f 100644 --- a/computercraft/server.py +++ b/computercraft/server.py @@ -1,101 +1,250 @@ import argparse import asyncio -import json from os.path import join, dirname, abspath from aiohttp import web, WSMsgType -from .sess import CCSession +from . import ser, sess +from .rproc import lua_table_to_list THIS_DIR = dirname(abspath(__file__)) LUA_FILE = join(THIS_DIR, 'back.lua') +PROTO_VERSION = 5 +PROTO_ERROR = b'C' + ser.serialize(b'protocol error', 'ascii') + + +def protocol(send, sess_cls=sess.CCSession, oc=False): + # handle first frame + msg = yield + msg = ser.dcmditer(msg) + + action = next(msg) + if action != b'0': + send(PROTO_ERROR) + return + + version = next(msg) + if version != PROTO_VERSION: + send(b'C' + ser.serialize( + 'protocol version mismatch (expected {}, got {}), redownload py'.format( + PROTO_VERSION, version, + ), 'ascii')) + return + + # CC:T starts its "args" with 0, includes program name + # but {...} works normally, starting from 1 + args = next(msg) + args = lua_table_to_list(args, low_index=0 if 0 in args else 1) + path, code = None, None + try: + path = next(msg) + code = next(msg) + except StopIteration: + pass + sess = sess_cls(send, oc=oc) + if code is not None: + sess.run_program(args, path, code) + else: + sess.run_repl() + + # handle the rest of frames + while True: + msg = yield + msg = ser.dcmditer(msg) + action = next(msg) + if action == b'E': + sess.on_event(next(msg), lua_table_to_list(next(msg))) + elif action == b'T': + sess.on_task_result(next(msg), next(msg)) + elif action == b'C': + sess.throw_keyboard_interrupt() + elif action == b'D': # disconnection + return + else: + send(PROTO_ERROR) + return class CCApplication(web.Application): - @staticmethod - async def _json_messages(ws): - async for msg in ws: - # print('ws received', msg) - if msg.type != WSMsgType.TEXT: - continue - # print('ws received', msg.data) - yield json.loads(msg.data.replace('\\\n', '\\n')) - - async def _launch_program(self, ws): - async for msg in self._json_messages(ws): - if msg['action'] != 'run': - await ws.send_json({ - 'action': 'close', - 'error': 'protocol error', - }) - return None - - def sender(data): - asyncio.create_task(ws.send_json(data)) - - sess = CCSession(msg['computer'], sender) - if msg['args']: - sess.run_program(msg['args'][0]) - else: - sess.run_repl() - return sess - async def ws(self, request): ws = web.WebSocketResponse() await ws.prepare(request) - sess = await self._launch_program(ws) - if sess is not None: - async for msg in self._json_messages(ws): - if msg['action'] == 'event': - sess.on_event(msg['event'], msg['params']) - elif msg['action'] == 'task_result': - sess.on_task_result(msg['task_id'], msg['result']) - else: - await ws.send_json({ - 'action': 'close', - 'error': 'protocol error', - }) - break + squeue = [] + pgen = self['protocol_factory'](squeue.append, oc=False) + next(pgen) + mustquit = False + async for msg in ws: + if msg.type != WSMsgType.BINARY: + continue + + try: + pgen.send(msg.data) + except StopIteration: + mustquit = True + + for m in squeue: + await ws.send_bytes(m) + squeue.clear() + + if mustquit: + break + + if not mustquit: # sudden disconnect + try: + pgen.send(b'D') + except StopIteration: + pass return ws + async def tcp(self, reader, writer): + squeue = [] + + def send(m): + squeue.append(m) + + pgen = self['protocol_factory'](squeue.append, oc=True) + next(pgen) + mustquit = False + while True: + try: + frame_size = int.from_bytes( + await reader.readexactly(3), 'big') + frame = await reader.readexactly(frame_size) + except asyncio.IncompleteReadError: + break + + try: + pgen.send(frame) + except StopIteration: + mustquit = True + + for m in squeue: + writer.write(len(m).to_bytes(3, 'big')) + writer.write(m) + squeue.clear() + await writer.drain() + + if mustquit: + break + + writer.close() + await writer.wait_closed() + @staticmethod def backdoor(request): with open(LUA_FILE, 'r') as f: fcont = f.read() - h = request.host - if ':' not in h: + webhost = tcphost = request.host + if ':' in request.host: + tcphost = tcphost.rsplit(':', 1)[0] + else: # fix for malformed Host header - h += ':{}'.format(request.app['port']) - fcont = fcont.replace( - "local url = 'http://127.0.0.1:4343/'", - "local url = '{}://{}/'".format('ws', h) - ) - return web.Response(text=fcont) - - def initialize(self): + webhost += ':{}'.format(request.app['port']) + return web.Response(text=( + fcont + .replace('__cc_url__', 'ws://{}/ws/'.format(webhost)) + .replace('__oc_host__', tcphost) + .replace('__oc_port__', str(request.app['oc_port'])) + )) + + def setup_routes(self): self.router.add_get('/', self.backdoor) self.router.add_get('/ws/', self.ws) -def main(): +def create_parser(): parser = argparse.ArgumentParser() - parser.add_argument('--host') - parser.add_argument('--port', type=int, default=8080) - args = parser.parse_args() + parser.add_argument('--host', default='0.0.0.0') + parser.add_argument( + '--port', type=int, default=8000, + help='Web (for wget) & websocket (for computercraft) port') + parser.add_argument( + '--oc-port', type=int, default=8001, + help='Raw TCP port for opencomputers') + parser.add_argument( + '--capture', type=str, default=None, + help='Capture test data into a file') + return parser - app_kw = {} - if args.host is not None: - app_kw['host'] = args.host - app_kw['port'] = args.port +def main(): + args = create_parser().parse_args() app = CCApplication() app['port'] = args.port - app.initialize() - web.run_app(app, **app_kw) + app['oc_port'] = args.oc_port + app['protocol_factory'] = protocol + app.setup_routes() + + async def tcp_server(app): + server = await asyncio.start_server(app.tcp, args.host, args.oc_port) + async with server: + yield + + async def capture(app): + with open(args.capture, 'wb') as f: + def protocol_factory(send, sess_cls=sess.CCSession, oc=False): + def write_frame(t, m): + ln = str(len(m)).encode('ascii') + f.write(t + ln + b':' + m + b'\n') + + def send_wrap(m): + write_frame(b'S', m) + return send(m) + + class SessOverride(sess_cls): + _drop_command = sess_cls._sorted_drop_command + + p = protocol(send_wrap, sess_cls=SessOverride, oc=oc) + + def pgen(): + next(p) + while True: + m = yield + write_frame(b'R', m) + try: + p.send(m) + except StopIteration as e: + return e.value + + return pgen() + + app['protocol_factory'] = protocol_factory + yield + + app.cleanup_ctx.append(tcp_server) + if args.capture is not None: + sess.python_version = lambda: '' + app.cleanup_ctx.append(capture) + + with sess.patch_std_files(): + web.run_app(app, host=args.host, port=args.port) if __name__ == '__main__': main() + + +# TODO: move greenlets into separate thread +# to prevent hanging + +""" +import ctypes +import time +import threading + +def thmain(): + # hangs + while True: + pass + +th = threading.Thread(target=thmain) +th.start() +time.sleep(5) +ctypes.pythonapi.PyThreadState_SetAsyncExc( + ctypes.c_long(th.ident), + ctypes.py_object(TimeoutError)) +time.sleep(3) +""" diff --git a/computercraft/sess.py b/computercraft/sess.py index 8c50702..6518d0a 100644 --- a/computercraft/sess.py +++ b/computercraft/sess.py @@ -9,13 +9,13 @@ from importlib.abc import MetaPathFinder, Loader from importlib.machinery import ModuleSpec from itertools import count +from platform import python_version from traceback import format_exc from types import ModuleType from greenlet import greenlet, getcurrent as get_current_greenlet -from .lua import lua_string, lua_call, return_lua_call -from . import rproc +from . import rproc, ser, lua __all__ = ( @@ -31,15 +31,15 @@ def debug(*args): sys.__stdout__.flush() -DIGITS = string.digits + string.ascii_lowercase +DIGITS = (string.digits + string.ascii_lowercase).encode('ascii') -def base36(n): - r = '' +def base36(n: int) -> bytes: + r = bytearray() while n: - r += DIGITS[n % 36] + r.append(DIGITS[n % 36]) n //= 36 - return r[::-1] + return bytes(r) def _is_global_greenlet(): @@ -73,22 +73,24 @@ def readline(self, size=-1): raise RuntimeError( "Computercraft environment doesn't support " "stdin readline method with parameter") - return rproc.string(eval_lua( - return_lua_call('io.read') - )) + '\n' + r = eval_lua(b'G:io:M:read') + if r.peek() is None: + return '' # press ctrl+D in OC + if r.peek() is False: + r.take() # press ctrl+C in OC + eval_lua(b'io.stderr:write(...)', r.take_bytes(), immediate=True) + return '' + return r.take_string() + '\n' def write(self, s): if _is_global_greenlet(): return self._native.write(s) else: + s = s.encode(get_current_session()._enc, errors='replace') if self._err: - return rproc.nil(eval_lua( - lua_call('io.stderr:write', s) - )) + return eval_lua(b'io.stderr:write(...)', s).take_none() else: - return rproc.nil(eval_lua( - lua_call('io.write', s) - )) + return eval_lua(b'io.write(...)', s).take_none() def fileno(self): if _is_global_greenlet(): @@ -109,6 +111,10 @@ def find_spec(fullname, path, target=None): return ModuleSpec(fullname, ComputerCraftLoader, is_package=True) if fullname.startswith('cc.'): return ModuleSpec(fullname, ComputerCraftLoader, is_package=False) + if fullname == 'oc': + return ModuleSpec(fullname, OpenComputersLoader, is_package=True) + if fullname.startswith('oc.'): + return ModuleSpec(fullname, OpenComputersLoader, is_package=False) class ComputerCraftLoader(Loader): @@ -118,7 +124,28 @@ def create_module(spec): assert sn[0] == 'cc' if len(sn) == 1: sn.append('_pkg') - rawmod = import_module('.' + sn[1], 'computercraft.subapis') + rawmod = import_module('.' + sn[1], 'computercraft.cc') + mod = ModuleType(spec.name) + for k in rawmod.__all__: + setattr(mod, k, getattr(rawmod, k)) + return mod + + @staticmethod + def exec_module(module): + pass + + +class OpenComputersLoader(Loader): + @staticmethod + def create_module(spec): + sn = spec.name.split('.', 1) + assert sn[0] == 'oc' + if len(sn) == 1: + sn.append('_pkg') + rawmod = import_module('.' + sn[1], 'computercraft.oc') + mod = getattr(rawmod, '__replace_module__', None) + if mod is not None: + return mod mod = ModuleType(spec.name) for k in rawmod.__all__: setattr(mod, k, getattr(rawmod, k)) @@ -135,40 +162,81 @@ def install_import_hook(): install_import_hook() -sys.stdin = StdFileProxy(sys.__stdin__, False) -sys.stdout = StdFileProxy(sys.__stdout__, False) -sys.stderr = StdFileProxy(sys.__stderr__, True) -def eval_lua(lua_code, immediate=False): - result = get_current_session()._server_greenlet.switch({ - 'code': lua_code, - 'immediate': immediate, - }) - # debug('{} → {}'.format(lua_code, repr(result))) +@contextmanager +def patch_std_files(): + pin, pout, perr = sys.stdin, sys.stdout, sys.stderr + sys.stdin = StdFileProxy(pin, False) + sys.stdout = StdFileProxy(pout, False) + sys.stderr = StdFileProxy(perr, True) + try: + yield + finally: + sys.stdin, sys.stdout, sys.stderr = pin, pout, perr + + +def eval_lua(lua_code, *params, immediate=False): + sess = get_current_session() + assert isinstance(lua_code, bytes) + request = ( + (b'I' if immediate else b'T') + + ser.serialize(lua_code, sess._enc) + + ser.serialize(params, sess._enc) + ) + result = sess._server_greenlet.switch(request) + rp = rproc.ResultProc(ser.deserialize(result), sess._enc) if not immediate: - result = rproc.coro(result) - return result + rp.check_bool_error() + return rp @contextmanager -def lua_context_object(create_expr: str, finalizer_template: str = ''): +def lua_context_object( + create_expr: bytes, + create_params: tuple, + finalizer_template: bytes = b'', + prefix: bytes = b'', +): sess = get_current_session() fid = sess.create_task_id() - var = 'temp[{}]'.format(lua_string(fid)) - eval_lua('{} = {}'.format(var, create_expr)) + r = eval_lua( + prefix + + b'return(function(n,...)local o,e=' + + create_expr + + b';if o then temp[n]=o;return true;end' + + b';return o,e;end)(...)', fid, *create_params) + r.check_nil_error() + assert r.take_bool() is True try: - yield var + yield fid finally: - finalizer_template += '; {e} = nil' - finalizer_template = finalizer_template.lstrip(' ;') - eval_lua(finalizer_template.format(e=var)) + f = finalizer_template.replace(b'{e}', b'temp[n]') + if f: + f += b';' # important to not have ;; + eval_lua( + b'local n=...;' + f + b'temp[n]=nil', fid) + + +class _TempObjectExt: + CALL_OP = b'.' + + def _call(self, method: bytes, *args): + return eval_lua( + b'return(function(n,...)return temp[n]' + + self.CALL_OP + + method + + b'(...);end)(...)', self._fid, *args) -def eval_lua_method_factory(obj): - def method(name, *params): - return eval_lua(return_lua_call(obj + name, *params)) - return method +class ContextObject(_TempObjectExt, lua.TempObject): + pass + + +class ReferenceObject(_TempObjectExt, lua.TempObject): + def __init__(self, lua_context_gen): + self._g = lua_context_gen # keep the reference + self._fid = self._g.__enter__() class CCGreenlet: @@ -187,6 +255,7 @@ def __init__(self, body_fn, sess=None): else: self._parent = parent_g.cc_greenlet self._parent._children.add(self._task_id) + self._sess._new_greenlets.append(self._task_id) self._children = set() self._g = greenlet(body_fn) @@ -203,8 +272,10 @@ def _on_death(self, error=None): self.detach_children() if error is not None: if error is True: - error = {} - self._sess._sender({'action': 'close', **error}) + error = None + else: + error = error.encode(self._sess._enc, errors='replace') + self._sess._sender(b'C' + ser.serialize(error, self._sess._enc)) if self._parent is not None: self._parent._children.discard(self._task_id) @@ -221,20 +292,16 @@ def switch(self, *args, **kwargs): self._on_death(True) return except Exception: - self._on_death({'error': format_exc(limit=None, chain=False)}) + self._on_death(format_exc(limit=None, chain=False)) return # lua_eval call or simply idle - if isinstance(task, dict): + if isinstance(task, bytes): x = self while x._g.dead: x = x._parent - tid = x._task_id - self._sess._sender({ - 'action': 'task', - 'task_id': tid, - **task, - }) + self._sess._sender( + task[0:1] + ser.serialize(x._task_id, 'ascii') + task[1:]) if self._g.dead: if self._parent is None: @@ -242,6 +309,9 @@ def switch(self, *args, **kwargs): else: self._on_death() + def throw(self, exc): + self._g.throw(exc) + class CCEventRouter: def __init__(self, on_first_sub, on_last_unsub, resume_task): @@ -294,19 +364,20 @@ def _set_task_status(self, task_id, event, waits: bool): class CCSession: - def __init__(self, computer_id, sender): - # computer_id is unique identifier of a CCSession - self._computer_id = computer_id + def __init__(self, sender, oc=False): self._tid_allocator = map(base36, count(start=1)) self._sender = sender + self._oc = oc + self._enc = ser._OC_ENC if oc else ser._CC_ENC self._greenlets = {} self._server_greenlet = get_current_greenlet() self._program_greenlet = None self._evr = CCEventRouter( - lambda event: self._sender({'action': 'sub', 'event': event}), - lambda event: self._sender({'action': 'unsub', 'event': event}), - lambda task_id: self._greenlets[task_id].defer_switch('event'), + lambda event: self._sender(b'S' + ser.serialize(event, self._enc)), + lambda event: self._sender(b'U' + ser.serialize(event, self._enc)), + lambda task_id: self._greenlets[task_id].switch('event'), ) + self._new_greenlets = [] def on_task_result(self, task_id, result): assert get_current_greenlet() is self._server_greenlet @@ -314,13 +385,25 @@ def on_task_result(self, task_id, result): # ignore for dropped tasks return self._greenlets[task_id].switch(result) + self._run_new_greenlets() def on_event(self, event, params): - self._evr.on_event(event, params) + self._evr.on_event(event.decode(self._enc), params) + self._run_new_greenlets() def create_task_id(self): return next(self._tid_allocator) + def _drop_command(self, all_tids): + return b'D' + b''.join( + ser.serialize(tid, self._enc) for tid in all_tids) + + def _sorted_drop_command(self, all_tids): + # use instead _drop_command in tests + all_tids = sorted(set(all_tids)) + return b'D' + b''.join( + ser.serialize(tid, self._enc) for tid in all_tids) + def drop(self, task_ids): def collect(task_id): yield task_id @@ -332,33 +415,33 @@ def collect(task_id): for task_id in task_ids: all_tids.extend(collect(task_id)) - self._sender({ - 'action': 'drop', - 'task_ids': all_tids, - }) + self._sender(self._drop_command(all_tids)) + + def _run_new_greenlets(self): + while self._new_greenlets: + ng, self._new_greenlets = self._new_greenlets, [] + for tid in ng: + self._greenlets[tid].switch() + + def throw_keyboard_interrupt(self): + self._program_greenlet.throw(EOFError()) def _run_sandboxed_greenlet(self, fn): self._program_greenlet = CCGreenlet(fn, sess=self) self._program_greenlet.switch() - def run_program(self, program): + def run_program(self, args, path, code): def _run_program(): - p, code = eval_lua(''' -local p = fs.combine(shell.dir(), {}) -if not fs.exists(p) then return nil end -if fs.isDir(p) then return nil end -local f = fs.open(p, 'r') -local code = f.readAll() -f.close() -return p, code -'''.lstrip().format(lua_string(program))) - cc = compile(code, p, 'exec') - exec(cc, {'__file__': p}) + cc = compile(code, path, 'exec') + exec(cc, {'__file__': path, 'args': args}) self._run_sandboxed_greenlet(_run_program) def run_repl(self): def _repl(): - InteractiveConsole(locals={}).interact() + InteractiveConsole(locals={}).interact( + banner='Python {}'.format(python_version()), + exitmsg='', + ) self._run_sandboxed_greenlet(_repl) diff --git a/computercraft/subapis/base.py b/computercraft/subapis/base.py deleted file mode 100644 index cad83ba..0000000 --- a/computercraft/subapis/base.py +++ /dev/null @@ -1,15 +0,0 @@ -from ..lua import LuaExpr, lua_args -from ..sess import eval_lua - - -class BaseSubAPI(LuaExpr): - def __init__(self, lua_expr): - self._lua_expr = lua_expr - - def get_expr_code(self): - return self._lua_expr - - def _method(self, name, *params): - return eval_lua('return {}.{}({})'.format( - self.get_expr_code(), name, lua_args(*params), - )) diff --git a/computercraft/subapis/commands.py b/computercraft/subapis/commands.py deleted file mode 100644 index 6c834ac..0000000 --- a/computercraft/subapis/commands.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import Tuple, List, Optional - -from ..rproc import tuple3_integer, any_dict, any_list, array_string, fact_tuple, boolean, option_integer -from ..sess import eval_lua_method_factory - - -command_result = fact_tuple(boolean, array_string, option_integer, tail_nils=1) -method = eval_lua_method_factory('commands.') - - -__all__ = ( - 'exec', - 'list', - 'getBlockPosition', - 'getBlockInfo', - 'getBlockInfos', -) - - -def exec(command: str) -> Tuple[bool, List[str], Optional[int]]: - return command_result(method('exec', command)) - - -def list() -> List[str]: - return array_string(method('list')) - - -def getBlockPosition() -> Tuple[int, int, int]: - return tuple3_integer(method('getBlockPosition')) - - -def getBlockInfo(x: int, y: int, z: int) -> dict: - return any_dict(method('getBlockInfo', x, y, z)) - - -def getBlockInfos(x1: int, y1: int, z1: int, x2: int, y2: int, z2: int) -> List[dict]: - return any_list(method('getBlockInfos', x1, y1, z1, x2, y2, z2)) diff --git a/computercraft/subapis/disk.py b/computercraft/subapis/disk.py deleted file mode 100644 index b9afd48..0000000 --- a/computercraft/subapis/disk.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Optional, Union - -from ..rproc import boolean, nil, option_integer, option_string, option_string_bool -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('disk.') - - -__all__ = ( - 'isPresent', - 'hasData', - 'getMountPath', - 'setLabel', - 'getLabel', - 'getID', - 'hasAudio', - 'getAudioTitle', - 'playAudio', - 'stopAudio', - 'eject', -) - - -def isPresent(side: str) -> bool: - return boolean(method('isPresent', side)) - - -def hasData(side: str) -> bool: - return boolean(method('hasData', side)) - - -def getMountPath(side: str) -> Optional[str]: - return option_string(method('getMountPath', side)) - - -def setLabel(side: str, label: str): - return nil(method('setLabel', side, label)) - - -def getLabel(side: str) -> Optional[str]: - return option_string(method('getLabel', side)) - - -def getID(side: str) -> Optional[int]: - return option_integer(method('getID', side)) - - -def hasAudio(side: str) -> bool: - return boolean(method('hasAudio', side)) - - -def getAudioTitle(side: str) -> Optional[Union[bool, str]]: - return option_string_bool(method('getAudioTitle', side)) - - -def playAudio(side: str): - return nil(method('playAudio', side)) - - -def stopAudio(side: str): - return nil(method('stopAudio', side)) - - -def eject(side: str): - return nil(method('eject', side)) diff --git a/computercraft/subapis/fs.py b/computercraft/subapis/fs.py deleted file mode 100644 index e34b2fc..0000000 --- a/computercraft/subapis/fs.py +++ /dev/null @@ -1,198 +0,0 @@ -import builtins -from contextlib import contextmanager -from typing import Optional, List, Union - -from .base import BaseSubAPI -from ..errors import LuaException -from ..lua import lua_call -from ..rproc import boolean, string, integer, nil, array_string, option_string, option_integer, fact_scheme_dict -from ..sess import eval_lua_method_factory, lua_context_object - - -attribute = fact_scheme_dict({ - 'created': integer, - 'modification': integer, - 'isDir': boolean, - 'size': integer, -}, {}) - - -class SeekMixin: - def seek(self, whence: str = None, offset: int = None) -> int: - # whence: set, cur, end - r = self._method('seek', whence, offset) - if isinstance(r, builtins.list): - assert r[0] is False - raise LuaException(r[1]) - return integer(r) - - -class ReadHandle(BaseSubAPI): - # TODO: binary handle must return bytes instead string - - def read(self, count: int = None) -> Optional[Union[str, int]]: - r = self._method('read', count) - return option_integer(r) if count is None else option_string(r) - - def readLine(self) -> Optional[str]: - return option_string(self._method('readLine')) - - def readAll(self) -> str: - return string(self._method('readAll')) - - def __iter__(self): - return self - - def __next__(self): - line = self.readLine() - if line is None: - raise StopIteration - return line - - -class BinaryReadHandle(ReadHandle, SeekMixin): - pass - - -class WriteHandle(BaseSubAPI): - def write(self, text: str): - return nil(self._method('write', text)) - - def writeLine(self, text: str): - return nil(self._method('writeLine', text)) - - def flush(self): - return nil(self._method('flush')) - - -class BinaryWriteHandle(WriteHandle, SeekMixin): - pass - - -method = eval_lua_method_factory('fs.') - - -__all__ = ( - 'list', - 'exists', - 'isDir', - 'isReadOnly', - 'getDrive', - 'getSize', - 'getFreeSpace', - 'getCapacity', - 'makeDir', - 'move', - 'copy', - 'delete', - 'combine', - 'open', - 'find', - 'getDir', - 'getName', - 'isDriveRoot', - 'complete', - 'attributes', -) - - -def list(path: str) -> List[str]: - return array_string(method('list', path)) - - -def exists(path: str) -> bool: - return boolean(method('exists', path)) - - -def isDir(path: str) -> bool: - return boolean(method('isDir', path)) - - -def isReadOnly(path: str) -> bool: - return boolean(method('isReadOnly', path)) - - -def getDrive(path: str) -> Optional[str]: - return option_string(method('getDrive', path)) - - -def getSize(path: str) -> int: - return integer(method('getSize', path)) - - -def getFreeSpace(path: str) -> int: - return integer(method('getFreeSpace', path)) - - -def getCapacity(path: str) -> int: - return integer(method('getCapacity', path)) - - -def makeDir(path: str): - return nil(method('makeDir', path)) - - -def move(fromPath: str, toPath: str): - return nil(method('move', fromPath, toPath)) - - -def copy(fromPath: str, toPath: str): - return nil(method('copy', fromPath, toPath)) - - -def delete(path: str): - return nil(method('delete', path)) - - -def combine(basePath: str, localPath: str) -> str: - return string(method('combine', basePath, localPath)) - - -@contextmanager -def open(path: str, mode: str): - ''' - Usage: - - with fs.open('filename', 'w') as f: - f.writeLine('textline') - - with fs.open('filename', 'r') as f: - for line in f: - ... - ''' - with lua_context_object( - lua_call('fs.open', path, mode), - '{e}.close()', - ) as var: - if 'b' in mode: - hcls = BinaryReadHandle if 'r' in mode else BinaryWriteHandle - else: - hcls = ReadHandle if 'r' in mode else WriteHandle - yield hcls(var) - - -def find(wildcard: str) -> List[str]: - return array_string(method('find', wildcard)) - - -def getDir(path: str) -> str: - return string(method('getDir', path)) - - -def getName(path: str) -> str: - return string(method('getName', path)) - - -def isDriveRoot(path: str) -> bool: - return boolean(method('isDriveRoot', path)) - - -def complete( - partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None, -) -> List[str]: - return array_string(method( - 'complete', partialName, path, includeFiles, includeDirs)) - - -def attributes(path: str) -> dict: - return attribute(method('attributes', path)) diff --git a/computercraft/subapis/gps.py b/computercraft/subapis/gps.py deleted file mode 100644 index 9a770e9..0000000 --- a/computercraft/subapis/gps.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Tuple, Optional - -from ..lua import LuaNum -from ..rproc import tuple3_number, fact_option -from ..sess import eval_lua_method_factory - - -option_tuple3_number = fact_option(tuple3_number) -method = eval_lua_method_factory('gps.') - - -__all__ = ( - 'CHANNEL_GPS', - 'locate', -) - - -CHANNEL_GPS = 65534 - - -def locate(timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[LuaNum, LuaNum, LuaNum]]: - return option_tuple3_number(method('locate', timeout, debug)) diff --git a/computercraft/subapis/help.py b/computercraft/subapis/help.py deleted file mode 100644 index 3b551f1..0000000 --- a/computercraft/subapis/help.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Optional, List - -from ..rproc import string, nil, array_string, option_string -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('help.') - - -__all__ = ( - 'path', - 'setPath', - 'lookup', - 'topics', - 'completeTopic', -) - - -def path() -> str: - return string(method('path')) - - -def setPath(path: str): - return nil(method('setPath', path)) - - -def lookup(topic: str) -> Optional[str]: - return option_string(method('lookup', topic)) - - -def topics() -> List[str]: - return array_string(method('topics')) - - -def completeTopic(topicPrefix: str) -> List[str]: - return array_string(method('completeTopic', topicPrefix)) diff --git a/computercraft/subapis/keys.py b/computercraft/subapis/keys.py deleted file mode 100644 index cf5e6ef..0000000 --- a/computercraft/subapis/keys.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -from ..lua import lua_string -from ..rproc import option_integer, option_string -from ..sess import eval_lua, eval_lua_method_factory - - -method = eval_lua_method_factory('keys.') - - -__all__ = ( - 'getCode', - 'getName', -) - - -def getCode(name: str) -> Optional[int]: - # replaces properties - # keys.space → keys.getCode('space') - return option_integer(eval_lua(''' -if type(keys[{key}]) == 'number' then - return keys[{key}] -end -return nil'''.format(key=lua_string(name)))) - - -def getName(code: int) -> Optional[str]: - return option_string(method('getName', code)) diff --git a/computercraft/subapis/mixins.py b/computercraft/subapis/mixins.py deleted file mode 100644 index c8843a5..0000000 --- a/computercraft/subapis/mixins.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Tuple - -from ..lua import LuaExpr -from ..rproc import boolean, nil, integer, tuple3_number, tuple2_integer - - -class TermMixin: - def write(self, text: str): - return nil(self._method('write', text)) - - def blit(self, text: str, textColors: str, backgroundColors: str): - return nil(self._method('blit', text, textColors, backgroundColors)) - - def clear(self): - return nil(self._method('clear')) - - def clearLine(self): - return nil(self._method('clearLine')) - - def getCursorPos(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getCursorPos')) - - def setCursorPos(self, x: int, y: int): - return nil(self._method('setCursorPos', x, y)) - - def getCursorBlink(self) -> bool: - return boolean(self._method('getCursorBlink')) - - def setCursorBlink(self, value: bool): - return nil(self._method('setCursorBlink', value)) - - def isColor(self) -> bool: - return boolean(self._method('isColor')) - - def getSize(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getSize')) - - def scroll(self, lines: int): - return nil(self._method('scroll', lines)) - - def setTextColor(self, colorID: int): - return nil(self._method('setTextColor', colorID)) - - def getTextColor(self) -> int: - return integer(self._method('getTextColor')) - - def setBackgroundColor(self, colorID: int): - return nil(self._method('setBackgroundColor', colorID)) - - def getBackgroundColor(self) -> int: - return integer(self._method('getBackgroundColor')) - - def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]: - return tuple3_number(self._method('getPaletteColor', colorID)) - - def setPaletteColor(self, colorID: int, r: float, g: float, b: float): - return nil(self._method('setPaletteColor', colorID, r, g, b)) - - -class TermTarget(LuaExpr): - def __init__(self, code): - self._code = code - - def get_expr_code(self): - return self._code diff --git a/computercraft/subapis/multishell.py b/computercraft/subapis/multishell.py deleted file mode 100644 index 2b5ddf8..0000000 --- a/computercraft/subapis/multishell.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Optional - -from ..rproc import integer, nil, boolean, option_string -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('multishell.') - - -__all__ = ( - 'getCurrent', - 'getCount', - 'launch', - 'setTitle', - 'getTitle', - 'setFocus', - 'getFocus', -) - - -def getCurrent() -> int: - return integer(method('getCurrent')) - - -def getCount() -> int: - return integer(method('getCount')) - - -def launch(environment: dict, programPath: str, *args: str) -> int: - return integer(method('launch', environment, programPath, *args)) - - -def setTitle(tabID: int, title: str): - return nil(method('setTitle', tabID, title)) - - -def getTitle(tabID: int) -> Optional[str]: - return option_string(method('getTitle', tabID)) - - -def setFocus(tabID: int) -> bool: - return boolean(method('setFocus', tabID)) - - -def getFocus() -> int: - return integer(method('getFocus')) diff --git a/computercraft/subapis/paintutils.py b/computercraft/subapis/paintutils.py deleted file mode 100644 index 9d0e825..0000000 --- a/computercraft/subapis/paintutils.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import List - -from ..rproc import nil, integer, fact_array -from ..sess import eval_lua_method_factory - - -array_2d_integer = fact_array(fact_array(integer)) -method = eval_lua_method_factory('paintutils.') - - -__all__ = ( - 'parseImage', - 'loadImage', - 'drawPixel', - 'drawLine', - 'drawBox', - 'drawFilledBox', - 'drawImage', -) - - -def parseImage(data: str) -> List[List[int]]: - return array_2d_integer(method('parseImage', data)) - - -def loadImage(path: str) -> List[List[int]]: - return array_2d_integer(method('loadImage', path)) - - -def drawPixel(x: int, y: int, color: int = None): - return nil(method('drawPixel', x, y, color)) - - -def drawLine(startX: int, startY: int, endX: int, endY: int, color: int = None): - return nil(method('drawLine', startX, startY, endX, endY, color)) - - -def drawBox(startX: int, startY: int, endX: int, endY: int, color: int = None): - return nil(method('drawBox', startX, startY, endX, endY, color)) - - -def drawFilledBox(startX: int, startY: int, endX: int, endY: int, color: int = None): - return nil(method('drawFilledBox', startX, startY, endX, endY, color)) - - -def drawImage(image: List[List[int]], xPos: int, yPos: int): - return nil(method('drawImage', image, xPos, yPos)) diff --git a/computercraft/subapis/peripheral.py b/computercraft/subapis/peripheral.py deleted file mode 100644 index 96d98bf..0000000 --- a/computercraft/subapis/peripheral.py +++ /dev/null @@ -1,312 +0,0 @@ -from dataclasses import dataclass -from typing import Optional, List, Tuple, Any, Union - -from .mixins import TermMixin, TermTarget -from .turtle import craft_result -from ..lua import LuaNum, lua_args, return_lua_call -from ..rproc import ( - boolean, nil, integer, string, option_integer, option_string, - tuple2_integer, array_string, option_string_bool, flat_try_result, -) -from ..sess import eval_lua, eval_lua_method_factory - - -class BasePeripheral: - # NOTE: is not LuaExpr, you can't pass peripheral as parameter - # TODO: to fix this we can supply separate lua expr, result of .wrap() - - def __init__(self, lua_method_expr, *prepend_params): - self._lua_method_expr = lua_method_expr - self._prepend_params = prepend_params - - def _method(self, name, *params): - return eval_lua(return_lua_call( - self._lua_method_expr, - *self._prepend_params, name, *params, - )) - - -class CCDrive(BasePeripheral): - def isDiskPresent(self) -> bool: - return boolean(self._method('isDiskPresent')) - - def getDiskLabel(self) -> Optional[str]: - return option_string(self._method('getDiskLabel')) - - def setDiskLabel(self, label: str): - return nil(self._method('setDiskLabel', label)) - - def hasData(self) -> bool: - return boolean(self._method('hasData')) - - def getMountPath(self) -> Optional[str]: - return option_string(self._method('getMountPath')) - - def hasAudio(self) -> bool: - return boolean(self._method('hasAudio')) - - def getAudioTitle(self) -> Optional[Union[bool, str]]: - return option_string_bool(self._method('getAudioTitle')) - - def playAudio(self): - return nil(self._method('playAudio')) - - def stopAudio(self): - return nil(self._method('stopAudio')) - - def ejectDisk(self): - return nil(self._method('ejectDisk')) - - def getDiskID(self) -> Optional[int]: - return option_integer(self._method('getDiskID')) - - -class CCMonitor(BasePeripheral, TermMixin): - def getTextScale(self) -> int: - return integer(self._method('getTextScale')) - - def setTextScale(self, scale: int): - return nil(self._method('setTextScale', scale)) - - -class ComputerMixin: - def turnOn(self): - return nil(self._method('turnOn')) - - def shutdown(self): - return nil(self._method('shutdown')) - - def reboot(self): - return nil(self._method('reboot')) - - def getID(self) -> int: - return integer(self._method('getID')) - - def getLabel(self) -> Optional[str]: - return option_string(self._method('getLabel')) - - def isOn(self) -> bool: - return boolean(self._method('isOn')) - - -class CCComputer(BasePeripheral, ComputerMixin): - pass - - -class CCTurtle(BasePeripheral, ComputerMixin): - pass - - -@dataclass -class ModemMessage: - reply_channel: int - content: Any - distance: LuaNum - - -class ModemMixin: - def isOpen(self, channel: int) -> bool: - return boolean(self._method('isOpen', channel)) - - def open(self, channel: int): - return nil(self._method('open', channel)) - - def close(self, channel: int): - return nil(self._method('close', channel)) - - def closeAll(self): - return nil(self._method('closeAll')) - - def transmit(self, channel: int, replyChannel: int, message: Any): - return nil(self._method('transmit', channel, replyChannel, message)) - - def isWireless(self) -> bool: - return boolean(self._method('isWireless')) - - @property - def _side(self): - return self._prepend_params[0] - - def receive(self, channel: int): - from .os import captureEvent - - if self.isOpen(channel): - raise Exception('Channel is busy') - - self.open(channel) - try: - for evt in captureEvent('modem_message'): - if evt[0] != self._side: - continue - if evt[1] != channel: - continue - yield ModemMessage(*evt[2:]) - finally: - self.close(channel) - - -class CCWirelessModem(BasePeripheral, ModemMixin): - pass - - -class CCWiredModem(BasePeripheral, ModemMixin): - def getNameLocal(self) -> Optional[str]: - return option_string(self._method('getNameLocal')) - - def getNamesRemote(self) -> List[str]: - return array_string(self._method('getNamesRemote')) - - def getTypeRemote(self, peripheralName: str) -> Optional[str]: - return option_string(self._method('getTypeRemote', peripheralName)) - - def isPresentRemote(self, peripheralName: str) -> bool: - return boolean(self._method('isPresentRemote', peripheralName)) - - def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]: - # use instead getMethodsRemote and callRemote - # NOTE: you can also use peripheral.wrap(peripheralName) - - ptype = self.getTypeRemote(peripheralName) - if ptype is None: - return None - - return TYPE_MAP[ptype]( - self._lua_method_expr, *self._prepend_params, - 'callRemote', peripheralName, - ) - - # NOTE: for TermTarget use peripheral.get_term_target(peripheralName) - - -class CCPrinter(BasePeripheral): - def newPage(self) -> bool: - return boolean(self._method('newPage')) - - def endPage(self) -> bool: - return boolean(self._method('endPage')) - - def write(self, text: str): - return nil(self._method('write', text)) - - def setCursorPos(self, x: int, y: int): - return nil(self._method('setCursorPos', x, y)) - - def getCursorPos(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getCursorPos')) - - def getPageSize(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getPageSize')) - - def setPageTitle(self, title: str): - return nil(self._method('setPageTitle', title)) - - def getPaperLevel(self) -> int: - return integer(self._method('getPaperLevel')) - - def getInkLevel(self) -> int: - return integer(self._method('getInkLevel')) - - -class CCSpeaker(BasePeripheral): - def playNote(self, instrument: str, volume: int = 1, pitch: int = 1) -> bool: - # instrument: - # https://minecraft.gamepedia.com/Note_Block#Instruments - # bass - # basedrum - # bell - # chime - # flute - # guitar - # hat - # snare - # xylophone - # iron_xylophone - # pling - # banjo - # bit - # didgeridoo - # cow_bell - - # volume 0..3 - # pitch 0..24 - return boolean(self._method('playNote', instrument, volume, pitch)) - - def playSound(self, sound: str, volume: int = 1, pitch: int = 1): - # volume 0..3 - # pitch 0..2 - return boolean(self._method('playSound', sound, volume, pitch)) - - -class CCCommandBlock(BasePeripheral): - def getCommand(self) -> str: - return string(self._method('getCommand')) - - def setCommand(self, command: str): - return nil(self._method('setCommand', command)) - - def runCommand(self): - return flat_try_result(self._method('runCommand')) - - -class CCWorkbench(BasePeripheral): - def craft(self, quantity: int = 64) -> bool: - return craft_result(self._method('craft', quantity)) - - -TYPE_MAP = { - 'drive': CCDrive, - 'monitor': CCMonitor, - 'computer': CCComputer, - 'turtle': CCTurtle, - 'printer': CCPrinter, - 'speaker': CCSpeaker, - 'command': CCCommandBlock, - 'workbench': CCWorkbench, -} - - -method = eval_lua_method_factory('peripheral.') - - -__all__ = ( - 'isPresent', - 'getType', - 'getNames', - 'wrap', - 'get_term_target', -) - - -def isPresent(side: str) -> bool: - return boolean(method('isPresent', side)) - - -def getType(side: str) -> Optional[str]: - return option_string(method('getType', side)) - - -def getNames() -> List[str]: - return array_string(method('getNames')) - - -# use instead getMethods and call -def wrap(side: str) -> Optional[BasePeripheral]: - ptype = getType(side) - if ptype is None: - return None - - m = 'peripheral.call' - - if ptype == 'modem': - if boolean(method('call', side, 'isWireless')): - return CCWirelessModem(m, side) - else: - return CCWiredModem(m, side) - else: - return TYPE_MAP[ptype](m, side) - - -def get_term_target(side: str) -> TermTarget: - return TermTarget('peripheral.wrap({})'.format( - lua_args(side), - )) diff --git a/computercraft/subapis/pocket.py b/computercraft/subapis/pocket.py deleted file mode 100644 index ddcb599..0000000 --- a/computercraft/subapis/pocket.py +++ /dev/null @@ -1,19 +0,0 @@ -from ..rproc import flat_try_result -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('pocket.') - - -__all__ = ( - 'equipBack', - 'unequipBack', -) - - -def equipBack(): - return flat_try_result(method('equipBack')) - - -def unequipBack(): - return flat_try_result(method('unequipBack')) diff --git a/computercraft/subapis/rednet.py b/computercraft/subapis/rednet.py deleted file mode 100644 index 9a038d7..0000000 --- a/computercraft/subapis/rednet.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import Any, List, Optional, Tuple, Union - -from ..lua import LuaNum -from ..rproc import nil, integer, option_string, boolean, array_integer, option_integer, fact_option, fact_tuple -from ..sess import eval_lua_method_factory - - -recv_result = fact_option(fact_tuple( - integer, - lambda v: v, - option_string, - tail_nils=1, -)) -method = eval_lua_method_factory('rednet.') - - -__all__ = ( - 'CHANNEL_REPEAT', - 'CHANNEL_BROADCAST', - 'open', - 'close', - 'send', - 'broadcast', - 'receive', - 'isOpen', - 'host', - 'unhost', - 'lookup', -) - - -CHANNEL_REPEAT = 65533 -CHANNEL_BROADCAST = 65535 - - -def open(side: str): - return nil(method('open', side)) - - -def close(side: str = None): - return nil(method('close', side)) - - -def send(receiverID: int, message: Any, protocol: str = None) -> bool: - return boolean(method('send', receiverID, message, protocol)) - - -def broadcast(message: Any, protocol: str = None): - return nil(method('broadcast', message, protocol)) - - -def receive( - protocolFilter: str = None, timeout: LuaNum = None, -) -> Optional[Tuple[int, Any, Optional[str]]]: - return recv_result(method('receive', protocolFilter, timeout)) - - -def isOpen(side: str = None) -> bool: - return boolean(method('isOpen', side)) - - -def host(protocol: str, hostname: str): - return nil(method('host', protocol, hostname)) - - -def unhost(protocol: str): - return nil(method('unhost', protocol)) - - -def lookup(protocol: str, hostname: str = None) -> Union[Optional[int], List[int]]: - result = method('lookup', protocol, hostname) - if hostname is None: - if result is None: - return [] - if isinstance(result, list): - return array_integer(result) - return [integer(result)] - else: - return option_integer(result) diff --git a/computercraft/subapis/redstone.py b/computercraft/subapis/redstone.py deleted file mode 100644 index ee671d4..0000000 --- a/computercraft/subapis/redstone.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import List - -from ..rproc import boolean, nil, integer, array_string -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('redstone.') - - -__all__ = ( - 'getSides', - 'getInput', - 'setOutput', - 'getOutput', - 'getAnalogInput', - 'setAnalogOutput', - 'getAnalogOutput', - 'getBundledInput', - 'setBundledOutput', - 'getBundledOutput', - 'testBundledInput', -) - - -def getSides() -> List[str]: - return array_string(method('getSides')) - - -def getInput(side: str) -> bool: - return boolean(method('getInput', side)) - - -def setOutput(side: str, value: bool): - return nil(method('setOutput', side, value)) - - -def getOutput(side: str) -> bool: - return boolean(method('getOutput', side)) - - -def getAnalogInput(side: str) -> int: - return integer(method('getAnalogInput', side)) - - -def setAnalogOutput(side: str, strength: int): - return nil(method('setAnalogOutput', side, strength)) - - -def getAnalogOutput(side: str) -> int: - return integer(method('getAnalogOutput', side)) - - -# bundled cables are not available in vanilla - -def getBundledInput(side: str) -> int: - return integer(method('getBundledInput', side)) - - -def setBundledOutput(side: str, colors: int): - return nil(method('setBundledOutput', side, colors)) - - -def getBundledOutput(side: str) -> int: - return integer(method('getBundledOutput', side)) - - -def testBundledInput(side: str, color: int) -> bool: - return boolean(method('testBundledInput', side, color)) diff --git a/computercraft/subapis/settings.py b/computercraft/subapis/settings.py deleted file mode 100644 index 6610514..0000000 --- a/computercraft/subapis/settings.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Any, List - -from ..rproc import nil, boolean, string, array_string, fact_scheme_dict -from ..sess import eval_lua_method_factory - - -setting = fact_scheme_dict({ - 'changed': boolean, -}, { - 'description': string, - 'default': lambda v: v, - 'type': string, - 'value': lambda v: v, -}) -method = eval_lua_method_factory('settings.') - - -__all__ = ( - 'define', - 'undefine', - 'getDetails', - 'set', - 'get', - 'unset', - 'clear', - 'getNames', - 'load', - 'save', -) - - -def define(name: str, description: str = None, default: Any = None, type: str = None): - options = {} - if description is not None: - options['description'] = description - if default is not None: - options['default'] = default - if type is not None: - options['type'] = type - return nil(method('define', name, options)) - - -def undefine(name: str): - return nil(method('undefine', name)) - - -def getDetails(name: str) -> dict: - return setting(method('getDetails', name)) - - -def set(name: str, value: Any): - return nil(method('set', name, value)) - - -def get(name: str, default: Any = None) -> Any: - return method('get', name, default) - - -def unset(name: str): - return nil(method('unset', name)) - - -def clear(): - return nil(method('clear')) - - -def getNames() -> List[str]: - return array_string(method('getNames')) - - -def load(path: str = None) -> bool: - return boolean(method('load', path)) - - -def save(path: str = None) -> bool: - return boolean(method('save', path)) diff --git a/computercraft/subapis/shell.py b/computercraft/subapis/shell.py deleted file mode 100644 index 41156f9..0000000 --- a/computercraft/subapis/shell.py +++ /dev/null @@ -1,112 +0,0 @@ -from typing import List, Dict, Optional - -from ..rproc import nil, string, boolean, integer, array_string, fact_mono_dict, option_string -from ..sess import eval_lua_method_factory - - -map_string_string = fact_mono_dict(string, string) -method = eval_lua_method_factory('shell.') - - -__all__ = ( - 'exit', - 'dir', - 'setDir', - 'path', - 'setPath', - 'resolve', - 'resolveProgram', - 'aliases', - 'setAlias', - 'clearAlias', - 'programs', - 'getRunningProgram', - 'run', - 'execute', - 'openTab', - 'switchTab', - 'complete', - 'completeProgram', -) - - -def exit(): - return nil(method('exit')) - - -def dir() -> str: - return string(method('dir')) - - -def setDir(path: str): - return nil(method('setDir', path)) - - -def path() -> str: - return string(method('path')) - - -def setPath(path: str): - return nil(method('setPath', path)) - - -def resolve(localPath: str) -> str: - return string(method('resolve', localPath)) - - -def resolveProgram(name: str) -> Optional[str]: - return option_string(method('resolveProgram', name)) - - -def aliases() -> Dict[str, str]: - return map_string_string(method('aliases')) - - -def setAlias(alias: str, program: str): - return nil(method('setAlias', alias, program)) - - -def clearAlias(alias: str): - return nil(method('clearAlias', alias)) - - -def programs(showHidden: bool = None) -> List[str]: - return array_string(method('programs', showHidden)) - - -def getRunningProgram() -> str: - return string(method('getRunningProgram')) - - -def run(command: str, *args: str) -> bool: - return boolean(method('run', command, *args)) - - -def execute(command: str, *args: str) -> bool: - return boolean(method('execute', command, *args)) - - -def openTab(command: str, *args: str) -> int: - return integer(method('openTab', command, *args)) - - -def switchTab(tabID: int): - return nil(method('switchTab', tabID)) - - -def complete(prefix: str) -> List[str]: - return array_string(method('complete', prefix)) - - -def completeProgram(prefix: str) -> List[str]: - return array_string(method('completeProgram', prefix)) - -# TODO: ? -# these functions won't be implemented -# it's far better to keep this in lua code - -# setCompletionFunction -# getCompletionInfo - -# we can create callbacks to python code, but this will require -# connection to python, and will break the shell if python disconnects diff --git a/computercraft/subapis/term.py b/computercraft/subapis/term.py deleted file mode 100644 index 33d7119..0000000 --- a/computercraft/subapis/term.py +++ /dev/null @@ -1,81 +0,0 @@ -from contextlib import contextmanager -from typing import Tuple - -from .base import BaseSubAPI -from .mixins import TermMixin, TermTarget -from ..lua import lua_call -from ..rproc import tuple3_number -from ..sess import eval_lua_method_factory, lua_context_object - - -class TermAPI(BaseSubAPI, TermMixin): - pass - - -method = eval_lua_method_factory('term.') -tapi = TermAPI('term') - - -__all__ = ( - 'write', - 'blit', - 'clear', - 'clearLine', - 'getCursorPos', - 'setCursorPos', - 'getCursorBlink', - 'setCursorBlink', - 'isColor', - 'getSize', - 'scroll', - 'setTextColor', - 'getTextColor', - 'setBackgroundColor', - 'getBackgroundColor', - 'getPaletteColor', - 'setPaletteColor', - 'nativePaletteColor', - 'redirect', - 'get_current_target', - 'get_native_target', -) - - -write = tapi.write -blit = tapi.blit -clear = tapi.clear -clearLine = tapi.clearLine -getCursorPos = tapi.getCursorPos -setCursorPos = tapi.setCursorPos -getCursorBlink = tapi.getCursorBlink -setCursorBlink = tapi.setCursorBlink -isColor = tapi.isColor -getSize = tapi.getSize -scroll = tapi.scroll -setTextColor = tapi.setTextColor -getTextColor = tapi.getTextColor -setBackgroundColor = tapi.setBackgroundColor -getBackgroundColor = tapi.getBackgroundColor -getPaletteColor = tapi.getPaletteColor -setPaletteColor = tapi.setPaletteColor - - -def nativePaletteColor(colorID: int) -> Tuple[float, float, float]: - return tuple3_number(method('nativePaletteColor', colorID)) - - -@contextmanager -def redirect(target: TermTarget): - with lua_context_object( - lua_call('term.redirect', target), - 'term.redirect({e})' - ): - yield - - -def get_current_target() -> TermTarget: - return TermTarget('term.current()') - - -def get_native_target() -> TermTarget: - return TermTarget('term.native()') diff --git a/computercraft/subapis/textutils.py b/computercraft/subapis/textutils.py deleted file mode 100644 index 8513764..0000000 --- a/computercraft/subapis/textutils.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import List, Union - -from ..lua import LuaNum -from ..rproc import nil, string, integer -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('textutils.') - - -__all__ = ( - 'slowWrite', - 'slowPrint', - 'formatTime', - 'tabulate', - 'pagedTabulate', - 'pagedPrint', - 'complete', -) - - -def slowWrite(text: str, rate: LuaNum = None): - return nil(method('slowWrite', text, rate)) - - -def slowPrint(text: str, rate: LuaNum = None): - return nil(method('slowPrint', text, rate)) - - -def formatTime(time: LuaNum, twentyFourHour: bool = None) -> str: - return string(method('formatTime', time, twentyFourHour)) - - -def tabulate(*rows_and_colors: Union[list, int]): - return nil(method('tabulate', *rows_and_colors)) - - -def pagedTabulate(*rows_and_colors: Union[list, int]): - return nil(method('pagedTabulate', *rows_and_colors)) - - -def pagedPrint(text: str, freeLines: int = None) -> int: - return integer(method('pagedPrint', text, freeLines)) - - -def complete(partial: str, possible: List[str]) -> List[str]: - return [p[len(partial):] for p in possible if p.startswith(partial)] - - -# Questionable to implement -# serialize -# unserialize - -# Will not implement, use pythonic equivalents -# serializeJSON -# unserializeJSON -# urlEncode -# json_null -# empty_json_array diff --git a/computercraft/subapis/turtle.py b/computercraft/subapis/turtle.py deleted file mode 100644 index 0870979..0000000 --- a/computercraft/subapis/turtle.py +++ /dev/null @@ -1,274 +0,0 @@ -from typing import Optional - -from ..errors import LuaException -from ..rproc import integer, boolean, fact_option, any_dict, flat_try_result -from ..sess import eval_lua_method_factory - - -method = eval_lua_method_factory('turtle.') -option_any_dict = fact_option(any_dict) - - -def inspect_result(r): - assert isinstance(r, list) - assert len(r) == 2 - success, data = r - assert isinstance(success, bool) - if not success: - if data == 'No block to inspect': - return None - raise LuaException(data) - else: - return any_dict(data) - - -def boolean_with_error_exclusion(exclude_msg): - def proc(r): - if r is True: - return True - assert isinstance(r, list) - assert len(r) == 2 - success, msg = r - assert isinstance(success, bool) - if not success: - if msg == exclude_msg: - return False - raise LuaException(msg) - else: - return True - return proc - - -dig_result = boolean_with_error_exclusion('Nothing to dig here') -move_result = boolean_with_error_exclusion('Movement obstructed') -place_result = boolean_with_error_exclusion('Cannot place block here') -suck_result = boolean_with_error_exclusion('No items to take') -drop_result = boolean_with_error_exclusion('No items to drop') -transfer_result = boolean_with_error_exclusion('No space for items') -attack_result = boolean_with_error_exclusion('Nothing to attack here') -craft_result = boolean_with_error_exclusion('No matching recipes') - - -def always_true(r): - assert boolean(r) is True - # return value is useless - return None - - -__all__ = ( - 'craft', - 'forward', - 'back', - 'up', - 'down', - 'turnLeft', - 'turnRight', - 'select', - 'getSelectedSlot', - 'getItemCount', - 'getItemSpace', - 'getItemDetail', - 'equipLeft', - 'equipRight', - 'attack', - 'attackUp', - 'attackDown', - 'dig', - 'digUp', - 'digDown', - 'place', - 'placeUp', - 'placeDown', - 'detect', - 'detectUp', - 'detectDown', - 'inspect', - 'inspectUp', - 'inspectDown', - 'compare', - 'compareUp', - 'compareDown', - 'compareTo', - 'drop', - 'dropUp', - 'dropDown', - 'suck', - 'suckUp', - 'suckDown', - 'refuel', - 'getFuelLevel', - 'getFuelLimit', - 'transferTo', -) - - -def craft(quantity: int = 64) -> bool: - return craft_result(method('craft', quantity)) - - -def forward() -> bool: - return move_result(method('forward')) - - -def back() -> bool: - return move_result(method('back')) - - -def up() -> bool: - return move_result(method('up')) - - -def down() -> bool: - return move_result(method('down')) - - -def turnLeft(): - return always_true(method('turnLeft')) - - -def turnRight(): - return always_true(method('turnRight')) - - -def select(slotNum: int): - return always_true(method('select', slotNum)) - - -def getSelectedSlot() -> int: - return integer(method('getSelectedSlot')) - - -def getItemCount(slotNum: int = None) -> int: - return integer(method('getItemCount', slotNum)) - - -def getItemSpace(slotNum: int = None) -> int: - return integer(method('getItemSpace', slotNum)) - - -def getItemDetail(slotNum: int = None) -> dict: - return option_any_dict(method('getItemDetail', slotNum)) - - -def equipLeft(): - return always_true(method('equipLeft')) - - -def equipRight(): - return always_true(method('equipRight')) - - -def attack() -> bool: - return attack_result(method('attack')) - - -def attackUp() -> bool: - return attack_result(method('attackUp')) - - -def attackDown() -> bool: - return attack_result(method('attackDown')) - - -def dig() -> bool: - return dig_result(method('dig')) - - -def digUp() -> bool: - return dig_result(method('digUp')) - - -def digDown() -> bool: - return dig_result(method('digDown')) - - -def place(signText: str = None) -> bool: - return place_result(method('place', signText)) - - -def placeUp() -> bool: - return place_result(method('placeUp')) - - -def placeDown() -> bool: - return place_result(method('placeDown')) - - -def detect() -> bool: - return boolean(method('detect')) - - -def detectUp() -> bool: - return boolean(method('detectUp')) - - -def detectDown() -> bool: - return boolean(method('detectDown')) - - -def inspect() -> Optional[dict]: - return inspect_result(method('inspect')) - - -def inspectUp() -> Optional[dict]: - return inspect_result(method('inspectUp')) - - -def inspectDown() -> Optional[dict]: - return inspect_result(method('inspectDown')) - - -def compare() -> bool: - return boolean(method('compare')) - - -def compareUp() -> bool: - return boolean(method('compareUp')) - - -def compareDown() -> bool: - return boolean(method('compareDown')) - - -def compareTo(slot: int) -> bool: - return boolean(method('compareTo', slot)) - - -def drop(count: int = None) -> bool: - return drop_result(method('drop', count)) - - -def dropUp(count: int = None) -> bool: - return drop_result(method('dropUp', count)) - - -def dropDown(count: int = None) -> bool: - return drop_result(method('dropDown', count)) - - -def suck(amount: int = None) -> bool: - return suck_result(method('suck', amount)) - - -def suckUp(amount: int = None) -> bool: - return suck_result(method('suckUp', amount)) - - -def suckDown(amount: int = None) -> bool: - return suck_result(method('suckDown', amount)) - - -def refuel(quantity: int = None): - return flat_try_result(method('refuel', quantity)) - - -def getFuelLevel() -> int: - return integer(method('getFuelLevel')) - - -def getFuelLimit() -> int: - return integer(method('getFuelLimit')) - - -def transferTo(slot: int, quantity: int = None) -> bool: - return transfer_result(method('transferTo', slot, quantity)) diff --git a/computercraft/subapis/window.py b/computercraft/subapis/window.py deleted file mode 100644 index 4486898..0000000 --- a/computercraft/subapis/window.py +++ /dev/null @@ -1,49 +0,0 @@ -from contextlib import contextmanager -from typing import Tuple - -from ..lua import lua_call -from ..rproc import nil, tuple2_integer, tuple3_string -from ..sess import eval_lua_method_factory, lua_context_object -from .base import BaseSubAPI -from .mixins import TermMixin, TermTarget - - -class CCWindow(BaseSubAPI, TermMixin): - def setVisible(self, visibility: bool): - return nil(self._method('setVisible', visibility)) - - def redraw(self): - return nil(self._method('redraw')) - - def restoreCursor(self): - return nil(self._method('restoreCursor')) - - def getPosition(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getPosition')) - - def reposition(self, x: int, y: int, width: int = None, height: int = None, parent: TermTarget = None): - return nil(self._method('reposition', x, y, width, height, parent)) - - def getLine(self, y: int) -> Tuple[str, str, str]: - return tuple3_string(self._method('getLine', y)) - - def get_term_target(self) -> TermTarget: - return TermTarget(self.get_expr_code()) - - -method = eval_lua_method_factory('window.') - - -__all__ = ( - 'create', -) - - -@contextmanager -def create( - parentTerm: TermTarget, x: int, y: int, width: int, height: int, visible: bool = None, -) -> CCWindow: - with lua_context_object( - lua_call('window.create', parentTerm, x, y, width, height, visible), - ) as var: - yield CCWindow(var) diff --git a/examples/_lib.py b/examples/_lib.py deleted file mode 100644 index a722fb3..0000000 --- a/examples/_lib.py +++ /dev/null @@ -1,135 +0,0 @@ -from contextlib import contextmanager -from time import monotonic -from types import FunctionType - -from cc import eval_lua - - -@contextmanager -def assert_raises(etype, message=None): - try: - yield - except Exception as e: - assert isinstance(e, etype), repr(e) - if message is not None: - assert e.args == (message, ) - else: - raise AssertionError(f'Exception of type {etype} was not raised') - - -@contextmanager -def assert_takes_time(at_least, at_most): - t = monotonic() - yield - dt = monotonic() - t - # print(at_least, '<=', dt, '<=', at_most) - assert at_least <= dt <= at_most - - -class AnyInstanceOf: - def __init__(self, cls): - self.c = cls - - def __eq__(self, other): - return isinstance(other, self.c) - - -def step(text): - input(f'{text} [enter]') - - -def get_object_table(objname): - r = eval_lua(f""" -local r = {{}} -for k in pairs({objname}) do - local t = type({objname}[k]) - if r[t] == nil then r[t] = {{}} end - if t == 'number' or t == 'boolean' or t == 'string' then - r[t][k] = {objname}[k] - else - r[t][k] = true - end -end -return r""", immediate=True) - assert len(r) == 1 - return r[0] - - -def get_class_table(cls): - items = { - k: v for k, v in vars(cls).items() - if not k.startswith('_') - } - nums = { - k: v for k, v in items.items() - if isinstance(v, (int, float)) - } - methods = { - k: True for k, v in items.items() - if isinstance(v, FunctionType) - } - r = {} - if nums: - r['number'] = nums - if methods: - r['function'] = methods - return r - - -def get_multiclass_table(*cls): - result = {} - for c in cls: - for k, v in get_class_table(c).items(): - result.setdefault(k, {}).update(v) - return result - - -def term_step(text): - from cc import colors, term - - for color in colors.iter_colors(): - r, g, b = term.nativePaletteColor(color) - term.setPaletteColor(color, r, g, b) - term.setBackgroundColor(colors.black) - term.setTextColor(colors.white) - term.clear() - term.setCursorPos(1, 1) - term.setCursorBlink(True) - step(text) - - -def _computer_peri(place_thing, thing): - from cc import peripheral - - side = 'left' - - step( - f'Place {place_thing} on {side} side of computer\n' - "Don't turn it on!", - ) - - c = peripheral.wrap(side) - assert c is not None - - from computercraft.subapis.peripheral import ComputerMixin - tbl = get_object_table(f'peripheral.wrap("{side}")') - assert get_class_table(ComputerMixin) == tbl - - assert c.isOn() is False - assert isinstance(c.getID(), int) - assert c.getLabel() is None - assert c.turnOn() is None - - step(f'{thing.capitalize()} must be turned on now') - - assert c.shutdown() is None - - step(f'{thing.capitalize()} must shutdown') - - step(f'Now turn on {thing} manually and enter some commands') - - assert c.reboot() is None - - step(f'{thing.capitalize()} must reboot') - - print('Test finished successfully') diff --git a/examples/args.py b/examples/args.py new file mode 100644 index 0000000..f24e44a --- /dev/null +++ b/examples/args.py @@ -0,0 +1,15 @@ +import argparse + + +# print(args) + + +parser = argparse.ArgumentParser(prog=__file__, description='Process some integers.') +parser.add_argument('integers', metavar='N', type=int, nargs='+', + help='an integer for the accumulator') +parser.add_argument('--sum', dest='accumulate', action='store_const', + const=sum, default=max, + help='sum the integers (default: find the max)') + +args = parser.parse_args(args=args) +print(args.accumulate(args.integers)) diff --git a/examples/broken.py b/examples/broken.py deleted file mode 100644 index 68263d9..0000000 --- a/examples/broken.py +++ /dev/null @@ -1 +0,0 @@ -raise ValueError diff --git a/examples/hello.py b/examples/hello.py deleted file mode 100644 index 60f08aa..0000000 --- a/examples/hello.py +++ /dev/null @@ -1 +0,0 @@ -print('Hello world!') diff --git a/examples/modem_server.py b/examples/modem_server.py index eab3dc8..7f24447 100644 --- a/examples/modem_server.py +++ b/examples/modem_server.py @@ -7,7 +7,7 @@ m.close(listen_channel) for msg in m.receive(listen_channel): print(repr(msg)) - if msg.content == 'stop': + if msg.content == b'stop': break else: m.transmit(msg.reply_channel, listen_channel, msg.content) diff --git a/examples/test_gps_basic_computer.py b/examples/test_gps_basic_computer.py deleted file mode 100644 index 5685a0d..0000000 --- a/examples/test_gps_basic_computer.py +++ /dev/null @@ -1,18 +0,0 @@ -from cc import import_file, gps - -_lib = import_file('_lib.py', __file__) - - -assert _lib.get_class_table(gps) == _lib.get_object_table('gps') - -assert gps.locate() is None - -_lib.step('Attach wireless modem to computer') - -assert gps.locate() is None - -assert gps.locate(debug=True) is None - -assert gps.locate(timeout=5, debug=True) is None - -print('Test finished successfully') diff --git a/examples/test_gps_command_computer.py b/examples/test_gps_command_computer.py deleted file mode 100644 index 4afc10c..0000000 --- a/examples/test_gps_command_computer.py +++ /dev/null @@ -1,14 +0,0 @@ -from cc import import_file, gps - -_lib = import_file('_lib.py', __file__) - - -assert _lib.get_class_table(gps) == _lib.get_object_table('gps') - -assert gps.locate() == ( - _lib.AnyInstanceOf(int), - _lib.AnyInstanceOf(int), - _lib.AnyInstanceOf(int), -) - -print('Test finished successfully') diff --git a/examples/test_peripheral_computer.py b/examples/test_peripheral_computer.py deleted file mode 100644 index 6623484..0000000 --- a/examples/test_peripheral_computer.py +++ /dev/null @@ -1,5 +0,0 @@ -from cc import import_file - -_lib = import_file('_lib.py', __file__) - -_lib._computer_peri('another computer', 'computer') diff --git a/examples/test_peripheral_modem.py b/examples/test_peripheral_modem.py deleted file mode 100644 index c1dd42c..0000000 --- a/examples/test_peripheral_modem.py +++ /dev/null @@ -1,54 +0,0 @@ -from cc import import_file, parallel, os, peripheral - -_lib = import_file('_lib.py', __file__) - - -# do this test twice: for wired and wireless modems - -side = 'back' - -_lib.step( - f'Attach modem to {side} side of computer\n' - f'Place another computer with similar modem at {side} side\n' - 'In case of wired modems connect them\n' - 'On another computer start py modem_server.py' -) - -m = peripheral.wrap(side) - -remote_channel = 5 -local_channel = 7 -messages = [] - - -def _send(): - for msg in [ - 1, - 'hi', - {'data': 5}, - 'stop', - ]: - os.sleep(1) - m.transmit(remote_channel, local_channel, msg) - - -def _recv(): - assert m.isOpen(local_channel) is False - for msg in m.receive(local_channel): - assert m.isOpen(local_channel) is True - assert msg.reply_channel == remote_channel - assert msg.distance > 0 - messages.append(msg.content) - if len(messages) == 3: - break - - -assert m.closeAll() is None -parallel.waitForAll(_recv, _send) - -assert messages == [1, 'hi', {'data': 5}] -assert m.isOpen(local_channel) is False -assert m.closeAll() is None -assert isinstance(m.isWireless(), bool) - -print('Test finished successfully') diff --git a/examples/test_peripheral_turtle.py b/examples/test_peripheral_turtle.py deleted file mode 100644 index 044835e..0000000 --- a/examples/test_peripheral_turtle.py +++ /dev/null @@ -1,5 +0,0 @@ -from cc import import_file - -_lib = import_file('_lib.py', __file__) - -_lib._computer_peri('turtle', 'turtle') diff --git a/examples/test_reboot.py b/examples/test_reboot.py deleted file mode 100644 index 54820bf..0000000 --- a/examples/test_reboot.py +++ /dev/null @@ -1,5 +0,0 @@ -from cc import os - - -assert os.reboot() is None -print('Test finished successfully') diff --git a/examples/test_redirect_to_local_monitor.py b/examples/test_redirect_to_local_monitor.py deleted file mode 100644 index 10fe60c..0000000 --- a/examples/test_redirect_to_local_monitor.py +++ /dev/null @@ -1,16 +0,0 @@ -from cc import import_file, colors, term, peripheral - -_lib = import_file('_lib.py', __file__) - - -side = 'left' -_lib.step(f'Attach 3x3 color monitor to {side} side of computer') - -with term.redirect(peripheral.get_term_target(side)): - term.setBackgroundColor(colors.green) - term.setTextColor(colors.white) - term.clear() - term.setCursorPos(1, 1) - print('Redirected to monitor') - -print('Test finished successfully') diff --git a/examples/test_redirect_to_window.py b/examples/test_redirect_to_window.py deleted file mode 100644 index 703c761..0000000 --- a/examples/test_redirect_to_window.py +++ /dev/null @@ -1,30 +0,0 @@ -from contextlib import ExitStack - -from cc import colors, term, window - - -w, h = term.getSize() -with ExitStack() as stack: - left = stack.enter_context(window.create( - term.get_current_target(), - 1, 1, w // 2, h, True, - )) - right = stack.enter_context(window.create( - term.get_current_target(), - w // 2 + 1, 1, w // 2, h, True, - )) - with term.redirect(left.get_term_target()): - term.setBackgroundColor(colors.green) - term.setTextColor(colors.white) - term.clear() - term.setCursorPos(1, h // 2) - print('Left part') - with term.redirect(right.get_term_target()): - term.setBackgroundColor(colors.red) - term.setTextColor(colors.yellow) - term.clear() - term.setCursorPos(1, h // 2) - print('Right part') - print('Default terminal restored') - -print('Test finished successfully') diff --git a/examples/test_shutdown.py b/examples/test_shutdown.py deleted file mode 100644 index 8ce2357..0000000 --- a/examples/test_shutdown.py +++ /dev/null @@ -1,5 +0,0 @@ -from cc import os - - -assert os.shutdown() is None -print('Test finished successfully') diff --git a/examples/test_window.py b/examples/test_window.py deleted file mode 100644 index 7c10456..0000000 --- a/examples/test_window.py +++ /dev/null @@ -1,36 +0,0 @@ -from cc import colors, term, os, window - - -with window.create( - term.get_current_target(), - 15, 5, 5, 5, False, -) as win: - assert win.getPosition() == (15, 5) - assert win.getSize() == (5, 5) - - win.setBackgroundColor(colors.red) - win.clear() - win.setVisible(True) - - os.sleep(1) - - win.setVisible(False) - win.setCursorPos(1, 1) - win.setTextColor(colors.yellow) - win.write('*********') - win.setVisible(True) - - os.sleep(1) - - term.clear() - - os.sleep(1) - - win.redraw() - assert win.getLine(1) == ('*****', '44444', 'eeeee') - - # draws immediately - win.reposition(21, 5) - win.reposition(27, 5) - -print('Test finished successfully') diff --git a/setup.cfg b/setup.cfg index 0afb1ad..c9412c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,8 @@ [flake8] max-line-length = 120 ignore = I,C812,N802,N803,N815,N816,W503 + +[tool:pytest] +testpaths = tests +pythonpath = . +addopts = --import-mode=importlib diff --git a/setup.py b/setup.py index 1c8abb8..9c69618 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,8 @@ +from pathlib import Path from setuptools import setup -from sys import argv -def is_register_command(a): - for item in a: - if item.startswith('-'): - continue - return item in ('register', 'bdist_wheel') - return False - - -longdesc = None -if is_register_command(argv[1:]): - import os - with os.popen('pandoc -f markdown_github -t rst README.md') as f: - longdesc = f.read() +longdesc = (Path(__file__).parent / 'README.md').read_text() setup( @@ -22,6 +10,7 @@ def is_register_command(a): version='0.3.0', description='Pythonization of ComputerCraft Minecraft mod. Write Python instead Lua!', long_description=longdesc, + long_description_content_type='text/markdown', url='https://github.com/neumond/python-computer-craft', author='Vitalik Verhovodov', author_email='knifeslaughter@gmail.com', @@ -30,13 +19,18 @@ def is_register_command(a): 'Development Status :: 3 - Alpha', 'Intended Audience :: Education', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Topic :: Games/Entertainment', ], keywords='computercraft minecraft', - packages=['computercraft', 'computercraft.subapis'], + packages=[ + 'computercraft', + 'computercraft.cc', + 'computercraft.cc_peripherals', + 'computercraft.oc', + 'computercraft.oc_peripherals', + ], package_data={'computercraft': ['back.lua']}, install_requires=['aiohttp', 'greenlet'], entry_points={ diff --git a/tests/TDESC.md b/tests/TDESC.md new file mode 100644 index 0000000..debdafa --- /dev/null +++ b/tests/TDESC.md @@ -0,0 +1,38 @@ +`wget http://127.0.0.1:8000 py` + +# replterm + +`py` + +```py +>>> 'wщ' * 3 +'wщwщwщ' +>>> (ctrl+C) +interrupted +``` + +# repl2p2 + +`py` + +```py +>>> 2+2 +4 +>>> (ctrl+D) +``` + +# helloworld + +`py helloworld.py 42 abc` + +# computer + +`py computer.py` + +# component + +`py component.py` + +# term + +`py term.py` diff --git a/tests/TDESC_CC.md b/tests/TDESC_CC.md new file mode 100644 index 0000000..447cf4b --- /dev/null +++ b/tests/TDESC_CC.md @@ -0,0 +1,9 @@ +`wget http://127.0.0.1:8000 py` + +# helloworld + +`py helloworld.py 42 abc` + +# term + +`py term.py` diff --git a/tests/cc_programs/_lib.py b/tests/cc_programs/_lib.py new file mode 100644 index 0000000..f469cfb --- /dev/null +++ b/tests/cc_programs/_lib.py @@ -0,0 +1,49 @@ +from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) diff --git a/examples/test_colors.py b/tests/cc_programs/colors.py similarity index 68% rename from examples/test_colors.py rename to tests/cc_programs/colors.py index 8367281..19e3415 100644 --- a/examples/test_colors.py +++ b/tests/cc_programs/colors.py @@ -1,16 +1,4 @@ -from cc import import_file, colors - -_lib = import_file('_lib.py', __file__) - - -tbl = _lib.get_object_table('colors') - -# use packRGB and unpackRGB -del tbl['function']['rgb8'] - -tbl['function']['iter_colors'] = True - -assert _lib.get_class_table(colors) == tbl +from cc import colors cs = colors.combine( colors.orange, diff --git a/examples/test_commands.py b/tests/cc_programs/commands.py similarity index 72% rename from examples/test_commands.py rename to tests/cc_programs/commands.py index c4a83d2..376eec9 100644 --- a/examples/test_commands.py +++ b/tests/cc_programs/commands.py @@ -1,19 +1,22 @@ -from cc import import_file, commands +from cc import commands -_lib = import_file('_lib.py', __file__) -AnyInstanceOf = _lib.AnyInstanceOf +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls -tbl = _lib.get_object_table('commands.native') -# remove in favor of exec -del tbl['function']['execAsync'] -assert _lib.get_class_table(commands) == tbl + def __eq__(self, other): + return isinstance(other, self.c) -xyz = commands.getBlockPosition() -assert len(xyz) == 3 -for c in xyz: - assert isinstance(c, int) +print('Run this test on command computer') + +xyz = commands.getBlockPosition() +assert xyz == ( + AnyInstanceOf(int), + AnyInstanceOf(int), + AnyInstanceOf(int), +) expected_binfo = { 'state': { @@ -25,11 +28,12 @@ 'x': xyz[0], 'y': xyz[1], 'z': xyz[2], + 'ForgeCaps': {}, 'ComputerId': AnyInstanceOf(int), 'id': 'computercraft:computer_command', 'On': 1, }, - 'tags': {}, + 'tags': {'computercraft:computer': True}, } assert commands.getBlockInfo(*xyz) == expected_binfo diff --git a/examples/test_disk.py b/tests/cc_programs/disk.py similarity index 81% rename from examples/test_disk.py rename to tests/cc_programs/disk.py index 66aa5be..89f70c9 100644 --- a/examples/test_disk.py +++ b/tests/cc_programs/disk.py @@ -1,12 +1,24 @@ -from cc import LuaException, import_file, disk +from contextlib import contextmanager +from cc import LuaException, disk -_lib = import_file('_lib.py', __file__) -step, assert_raises = _lib.step, _lib.assert_raises +def step(text): + input(f'{text} [enter]') -s = 'right' -assert _lib.get_class_table(disk) == _lib.get_object_table('disk') +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +s = 'right' step(f'Make sure there is no disk drive at {s} side') @@ -31,7 +43,7 @@ assert disk.getLabel(s) is None assert disk.getID(s) is None assert disk.hasAudio(s) is False -assert disk.getAudioTitle(s) is False # False instead None! +assert disk.getAudioTitle(s) is None assert disk.playAudio(s) is None assert disk.stopAudio(s) is None assert disk.eject(s) is None diff --git a/examples/test_fs.py b/tests/cc_programs/fs.py similarity index 82% rename from examples/test_fs.py rename to tests/cc_programs/fs.py index 56b806f..7a185ed 100644 --- a/examples/test_fs.py +++ b/tests/cc_programs/fs.py @@ -1,10 +1,26 @@ -from cc import LuaException, import_file, fs +from contextlib import contextmanager +from cc import LuaException, fs -_lib = import_file('_lib.py', __file__) -assert_raises, AnyInstanceOf = _lib.assert_raises, _lib.AnyInstanceOf +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) -assert _lib.get_class_table(fs) == _lib.get_object_table('fs') for name in ('tdir', 'tfile'): if fs.exists(name): @@ -127,13 +143,17 @@ assert fs.attributes('tdir/banana') == { 'created': AnyInstanceOf(int), + 'modified': AnyInstanceOf(int), 'modification': AnyInstanceOf(int), + 'isReadOnly': False, 'isDir': False, 'size': 9, } assert fs.attributes('tdir') == { 'created': AnyInstanceOf(int), + 'modified': AnyInstanceOf(int), 'modification': AnyInstanceOf(int), + 'isReadOnly': False, 'isDir': True, 'size': 0, } @@ -151,50 +171,32 @@ assert fs.getSize('tdir/banana') == 9 with fs.open('tdir/banana', 'r') as f: - assert _lib.get_object_table(f.get_expr_code()) == {'function': { - 'close': True, - 'read': True, - 'readLine': True, - 'readAll': True, - }} assert f.read(4) == 'text' assert f.readLine() == 'line' assert f.read(1) is None assert f.readLine() is None - assert f.readAll() == '' - assert f.readAll() == '' + assert f.readAll() is None + assert f.readAll() is None assert fs.getSize('tdir/banana') == 9 with fs.open('tdir/banana', 'a') as f: - assert _lib.get_object_table(f.get_expr_code()) == {'function': { - 'close': True, - 'write': True, - 'writeLine': True, - 'flush': True, - }} assert f.write('x') is None assert fs.getSize('tdir/banana') == 10 with fs.open('tdir/banana', 'w') as f: pass assert fs.getSize('tdir/banana') == 0 # truncate with fs.open('tdir/banana', 'w') as f: - assert _lib.get_object_table(f.get_expr_code()) == {'function': { - 'close': True, - 'write': True, - 'writeLine': True, - 'flush': True, - }} assert f.write('Bro') is None assert f.writeLine('wn fox jumps') is None - assert fs.getSize('tdir/banana') == 0 # changes are not on a disk + # assert fs.getSize('tdir/banana') == 0 # changes are not on a disk assert f.flush() is None assert fs.getSize('tdir/banana') == len('Brown fox jumps\n') assert f.write('ov') is None assert f.write('er ') is None assert f.write('a lazy') is None - assert f.writeLine(' dog.') is None + assert f.writeLine(' дог.') is None # supports unicode! assert fs.getSize('tdir/banana') > 9 with fs.open('tdir/banana', 'r') as f: - assert f.readAll() == 'Brown fox jumps\nover a lazy dog.' # no newline? + assert f.readAll() == 'Brown fox jumps\nover a lazy дог.\n' with assert_raises(LuaException): with fs.open('tdir/banana', 'rw') as f: pass @@ -202,22 +204,19 @@ assert fs.exists('tdir/banana') is True with fs.open('tdir/binfile', 'wb') as f: - assert f.write('a' * 9) is None + assert f.write(b'a' * 9) is None assert f.seek() == 9 assert f.seek('set', 0) == 0 - assert f.write('b' * 3) is None + assert f.write(b'b' * 3) is None assert f.seek('cur', -1) == 2 - assert f.write('c' * 3) is None + assert f.write(b'c' * 3) is None assert f.seek('end') == 9 - assert f.write('d' * 3) is None + assert f.write(b'd' * 3) is None with assert_raises(LuaException): f.seek('set', -10) with fs.open('tdir/binfile', 'rb') as f: - assert f.readAll() == 'bbcccaaaaddd' - -with fs.open('tdir/binfile', 'rb') as f: - assert isinstance(f.read(), int) + assert f.readAll() == b'bbcccaaaaddd' with fs.open('tdir/binfile', 'r') as f: assert [line for line in f] == ['bbcccaaaaddd'] diff --git a/tests/cc_programs/gps_basic_computer.py b/tests/cc_programs/gps_basic_computer.py new file mode 100644 index 0000000..b0b700b --- /dev/null +++ b/tests/cc_programs/gps_basic_computer.py @@ -0,0 +1,15 @@ +from cc import gps + + +print('It must be impossible to gps locate on basic computer') +print('for this test to complete') + +assert gps.locate() is None + +input('Attach wireless modem to computer [enter]') + +assert gps.locate() is None +assert gps.locate(debug=True) is None +assert gps.locate(timeout=5, debug=True) is None + +print('Test finished successfully') diff --git a/tests/cc_programs/gps_command_computer.py b/tests/cc_programs/gps_command_computer.py new file mode 100644 index 0000000..a53f59f --- /dev/null +++ b/tests/cc_programs/gps_command_computer.py @@ -0,0 +1,10 @@ +from cc import gps + + +print('Run this test on command computer') +pos = gps.locate() +assert isinstance(pos, tuple) +assert len(pos) == 3 +assert all(isinstance(x, int) for x in pos) +print('Position is', pos) +print('Test finished successfully') diff --git a/tests/cc_programs/helloworld.py b/tests/cc_programs/helloworld.py new file mode 100644 index 0000000..22d56ce --- /dev/null +++ b/tests/cc_programs/helloworld.py @@ -0,0 +1,3 @@ +print(args) +for _ in range(2): + print("Hello world!") diff --git a/examples/test_help.py b/tests/cc_programs/help.py similarity index 76% rename from examples/test_help.py rename to tests/cc_programs/help.py index 950b264..0706527 100644 --- a/examples/test_help.py +++ b/tests/cc_programs/help.py @@ -1,9 +1,5 @@ -from cc import import_file, help +from cc import help -_lib = import_file('_lib.py', __file__) - - -assert _lib.get_class_table(help) == _lib.get_object_table('help') help.setPath('/rom/help') @@ -15,7 +11,6 @@ ts = help.topics() assert isinstance(ts, list) assert len(ts) > 2 -# print(ts) assert 'disk' in ts assert help.completeTopic('di') == ['sk'] diff --git a/examples/test_keys.py b/tests/cc_programs/keys.py similarity index 76% rename from examples/test_keys.py rename to tests/cc_programs/keys.py index bdf40cf..ce939c8 100644 --- a/examples/test_keys.py +++ b/tests/cc_programs/keys.py @@ -1,6 +1,5 @@ from cc import keys - a = keys.getCode('a') space = keys.getCode('space') enter = keys.getCode('enter') @@ -14,7 +13,11 @@ assert keys.getName(space) == 'space' assert keys.getName(enter) == 'enter' -# for i in range(255): -# print(i, keys.getName(i)) +ks = [] +for i in range(256): + n = keys.getName(i) + if n is not None: + ks.append(f'{i}:{n}') +print(' '.join(ks)) print('Test finished successfully') diff --git a/examples/test_multishell.py b/tests/cc_programs/multishell.py similarity index 71% rename from examples/test_multishell.py rename to tests/cc_programs/multishell.py index fb26241..9bd5b84 100644 --- a/examples/test_multishell.py +++ b/tests/cc_programs/multishell.py @@ -1,12 +1,6 @@ -import random - from cc import import_file, multishell _lib = import_file('_lib.py', __file__) - - -assert _lib.get_class_table(multishell) == _lib.get_object_table('multishell') - _lib.step('Close all additional shells') assert multishell.getCount() == 1 @@ -14,9 +8,9 @@ assert multishell.getFocus() == 1 assert isinstance(multishell.getTitle(1), str) -title = f'new title {random.randint(1, 1000000)}' -assert multishell.setTitle(1, title) is None -assert multishell.getTitle(1) == title +for title in ['title a', 'title b']: + assert multishell.setTitle(1, title) is None + assert multishell.getTitle(1) == title assert multishell.setFocus(1) is True assert multishell.setFocus(0) is False diff --git a/examples/test_os.py b/tests/cc_programs/os.py similarity index 71% rename from examples/test_os.py rename to tests/cc_programs/os.py index 5765ce7..ecc8a84 100644 --- a/examples/test_os.py +++ b/tests/cc_programs/os.py @@ -1,22 +1,7 @@ from cc import import_file, os -_lib = import_file('_lib.py', __file__) - - -tbl = _lib.get_object_table('os') - -# use methods with get* -del tbl['function']['computerID'] -del tbl['function']['computerLabel'] -# we are in python world, loading lua modules is useless -del tbl['function']['loadAPI'] -del tbl['function']['unloadAPI'] - -# remove complex date formatting function in favor of python stdlib -del tbl['function']['date'] - -assert _lib.get_class_table(os) == tbl +_lib = import_file('_lib.py', __file__) with _lib.assert_takes_time(1.5, 3): @@ -55,6 +40,6 @@ assert isinstance(os.time(), (int, float)) assert isinstance(os.clock(), (int, float)) -assert os.run({}, 'rom/programs/fun/hello.lua') is True +assert os.run({}, '/rom/programs/fun/hello.lua') is True print('Test finished successfully') diff --git a/examples/test_paintutils.py b/tests/cc_programs/paintutils.py similarity index 93% rename from examples/test_paintutils.py rename to tests/cc_programs/paintutils.py index f9c6be9..f5d8750 100644 --- a/examples/test_paintutils.py +++ b/tests/cc_programs/paintutils.py @@ -25,15 +25,13 @@ '''.strip() -assert _lib.get_class_table(paintutils) == _lib.get_object_table('paintutils') - with fs.open('img.nfp', 'w') as f: f.write(pixels) int_pixels = paintutils.loadImage('img.nfp') assert len(int_pixels) > 0 assert len(int_pixels[0]) > 0 -assert paintutils.parseImage(pixels) == int_pixels +assert paintutils.parseImage(pixels.encode('ascii')) == int_pixels assert paintutils.drawImage(int_pixels, 1, 1) is None diff --git a/examples/test_parallel.py b/tests/cc_programs/parallel.py similarity index 100% rename from examples/test_parallel.py rename to tests/cc_programs/parallel.py diff --git a/examples/test_peripheral.py b/tests/cc_programs/peripheral.py similarity index 64% rename from examples/test_peripheral.py rename to tests/cc_programs/peripheral.py index 861a0ff..676309e 100644 --- a/examples/test_peripheral.py +++ b/tests/cc_programs/peripheral.py @@ -2,21 +2,6 @@ _lib = import_file('_lib.py', __file__) - -tbl = _lib.get_object_table('peripheral') - -# use wrap -del tbl['function']['getMethods'] -del tbl['function']['call'] - -# TODO: support these methods -del tbl['function']['getName'] -del tbl['function']['find'] - -tbl['function']['get_term_target'] = True - -assert _lib.get_class_table(peripheral) == tbl - _lib.step('Remove all peripherals') side = 'top' @@ -35,6 +20,4 @@ assert d is not None assert d.isDiskPresent() is False -print('Remove disk drive') - print('Test finished successfully') diff --git a/examples/test_peripheral_commandblock.py b/tests/cc_programs/peripheral_commandblock.py similarity index 80% rename from examples/test_peripheral_commandblock.py rename to tests/cc_programs/peripheral_commandblock.py index aae8e2a..ebcfa5d 100644 --- a/examples/test_peripheral_commandblock.py +++ b/tests/cc_programs/peripheral_commandblock.py @@ -1,18 +1,13 @@ -from computercraft.subapis.peripheral import CCCommandBlock from cc import LuaException, import_file, peripheral _lib = import_file('_lib.py', __file__) - side = 'left' _lib.step(f'Attach command block at {side} side of computer') m = peripheral.wrap(side) -tbl = _lib.get_object_table(f'peripheral.wrap("{side}")') -assert _lib.get_class_table(CCCommandBlock) == tbl - assert m.getCommand() == '' assert m.setCommand('say Hello from python side') is None assert m.getCommand() == 'say Hello from python side' diff --git a/tests/cc_programs/peripheral_computer.py b/tests/cc_programs/peripheral_computer.py new file mode 100644 index 0000000..2b6af52 --- /dev/null +++ b/tests/cc_programs/peripheral_computer.py @@ -0,0 +1,38 @@ +from cc import import_file, peripheral + +_lib = import_file('_lib.py', __file__) + + +def computer_peri(place_thing, thing, finish): + side = 'left' + + _lib.step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + _lib.step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + _lib.step(f'{thing.capitalize()} must shutdown') + + _lib.step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + _lib.step(f'{thing.capitalize()} must reboot') + + print(f'Test {finish} finished successfully') + + +computer_peri('another computer', 'computer', '1/2') +computer_peri('turtle', 'turtle', '2/2') diff --git a/examples/test_peripheral_disk.py b/tests/cc_programs/peripheral_disk.py similarity index 88% rename from examples/test_peripheral_disk.py rename to tests/cc_programs/peripheral_disk.py index c1b7605..d68576a 100644 --- a/examples/test_peripheral_disk.py +++ b/tests/cc_programs/peripheral_disk.py @@ -1,9 +1,7 @@ -from computercraft.subapis.peripheral import CCDrive from cc import LuaException, import_file, peripheral _lib = import_file('_lib.py', __file__) - side = 'left' _lib.step(f'Put empty disk drive on {side} side of computer') @@ -11,9 +9,6 @@ d = peripheral.wrap(side) assert d is not None -tbl = _lib.get_object_table(f'peripheral.wrap("{side}")') -assert _lib.get_class_table(CCDrive) == tbl - assert d.isDiskPresent() is False assert d.hasData() is False assert d.getMountPath() is None @@ -21,7 +16,7 @@ assert d.getDiskLabel() is None assert d.getDiskID() is None assert d.hasAudio() is False -assert d.getAudioTitle() is False # False instead None! +assert d.getAudioTitle() is None assert d.playAudio() is None assert d.stopAudio() is None assert d.ejectDisk() is None diff --git a/tests/cc_programs/peripheral_modem.py b/tests/cc_programs/peripheral_modem.py new file mode 100644 index 0000000..aa60262 --- /dev/null +++ b/tests/cc_programs/peripheral_modem.py @@ -0,0 +1,56 @@ +from cc import import_file, parallel, os, peripheral + +_lib = import_file('_lib.py', __file__) +side = 'back' + + +def do_test(): + m = peripheral.wrap(side) + + remote_channel = 5 + local_channel = 7 + messages = [] + + def _send(): + for msg in [ + 1, + b'hi', + {b'data': 5}, + b'stop', + ]: + os.sleep(1) + m.transmit(remote_channel, local_channel, msg) + + def _recv(): + assert m.isOpen(local_channel) is False + for msg in m.receive(local_channel): + assert m.isOpen(local_channel) is True + assert msg.reply_channel == remote_channel + assert msg.distance > 0 + messages.append(msg.content) + if len(messages) == 3: + break + + assert m.closeAll() is None + parallel.waitForAll(_recv, _send) + + assert messages == [1, b'hi', {b'data': 5}] + assert m.isOpen(local_channel) is False + assert m.closeAll() is None + assert isinstance(m.isWireless(), bool) + + +_lib.step( + f'Attach wired modem to {side} side\n' + f'Place another computer with wired modem on {side} side\n' + 'Connect modems\n' + 'On another computer start py modem_server.py' +) +do_test() +_lib.step( + 'Disconnect and remove wired modems\n' + 'Attach wireless modems\n' + 'Restart modem_server.py on another computer' +) +do_test() +print('Test finished successfully') diff --git a/examples/test_peripheral_monitor.py b/tests/cc_programs/peripheral_monitor.py similarity index 80% rename from examples/test_peripheral_monitor.py rename to tests/cc_programs/peripheral_monitor.py index a7a01a6..5b15c2a 100644 --- a/examples/test_peripheral_monitor.py +++ b/tests/cc_programs/peripheral_monitor.py @@ -1,5 +1,3 @@ -from computercraft.subapis.peripheral import CCMonitor -from computercraft.subapis.mixins import TermMixin from cc import import_file, colors, os, peripheral _lib = import_file('_lib.py', __file__) @@ -14,23 +12,6 @@ m = peripheral.wrap(side) assert m is not None - - -tbl = _lib.get_object_table(f'peripheral.wrap("{side}")') - -# remove British method names to make API lighter -del tbl['function']['getBackgroundColour'] -del tbl['function']['getPaletteColour'] -del tbl['function']['getTextColour'] -del tbl['function']['isColour'] -del tbl['function']['setBackgroundColour'] -del tbl['function']['setPaletteColour'] -del tbl['function']['setTextColour'] -# NOTE: peripheral doesn't have nativePaletteColor method - -assert _lib.get_multiclass_table(TermMixin, CCMonitor) == tbl - - assert m.getSize() == (7, 5) assert m.isColor() is True assert m.setTextColor(colors.white) is None @@ -90,14 +71,14 @@ assert m.setCursorPos(1, 1) is None assert m.blit( 'rainbow', - 'e14d3ba', - 'fffffff', + b'e14d3ba', + b'fffffff', ) is None assert m.setCursorPos(1, 2) is None assert m.blit( 'rainbow', - '0000000', - 'e14d3ba', + b'0000000', + b'e14d3ba', ) is None _lib.step('You must have seen per-letter colored text') @@ -122,20 +103,20 @@ assert m.setCursorPos(1, 1) is None assert m.blit( ' redtex', - '0123456', - '0000000', + b'0123456', + b'0000000', ) is None assert m.setCursorPos(1, 2) is None assert m.blit( 'tappear', - '789abcd', - '0000000', + b'789abcd', + b'0000000', ) is None assert m.setCursorPos(1, 3) is None assert m.blit( 's!', - 'ef', - '00', + b'ef', + b'00', ) is None _lib.step('You must have seen different shades of red made using palettes') diff --git a/examples/test_peripheral_printer.py b/tests/cc_programs/peripheral_printer.py similarity index 94% rename from examples/test_peripheral_printer.py rename to tests/cc_programs/peripheral_printer.py index 7f24e76..31d71c0 100644 --- a/examples/test_peripheral_printer.py +++ b/tests/cc_programs/peripheral_printer.py @@ -1,19 +1,14 @@ -from computercraft.subapis.peripheral import CCPrinter from cc import LuaException, import_file, peripheral _lib = import_file('_lib.py', __file__) assert_raises = _lib.assert_raises - side = 'left' _lib.step(f'Attach empty printer at {side} side of computer') m = peripheral.wrap(side) -tbl = _lib.get_object_table(f'peripheral.wrap("{side}")') -assert _lib.get_class_table(CCPrinter) == tbl - assert m.getPaperLevel() == 0 assert m.getInkLevel() == 0 diff --git a/examples/test_peripheral_remote.py b/tests/cc_programs/peripheral_remote.py similarity index 99% rename from examples/test_peripheral_remote.py rename to tests/cc_programs/peripheral_remote.py index 262daa1..35cedc8 100644 --- a/examples/test_peripheral_remote.py +++ b/tests/cc_programs/peripheral_remote.py @@ -2,7 +2,6 @@ _lib = import_file('_lib.py', __file__) - side = 'back' _lib.step(f'Attach and disable (right-click) wired modem at {side} side') diff --git a/examples/test_peripheral_speaker.py b/tests/cc_programs/peripheral_speaker.py similarity index 80% rename from examples/test_peripheral_speaker.py rename to tests/cc_programs/peripheral_speaker.py index 336450e..1d45f95 100644 --- a/examples/test_peripheral_speaker.py +++ b/tests/cc_programs/peripheral_speaker.py @@ -1,21 +1,16 @@ import random -from computercraft.subapis.peripheral import CCSpeaker from cc import import_file, os, peripheral _lib = import_file('_lib.py', __file__) - +random.seed(598392) side = 'left' _lib.step(f'Attach speaker at {side} side of computer') m = peripheral.wrap(side) - -tbl = _lib.get_object_table(f'peripheral.wrap("{side}")') -assert _lib.get_class_table(CCSpeaker) == tbl - for _ in range(48): assert m.playNote( random.choice([ diff --git a/examples/test_pocket.py b/tests/cc_programs/pocket.py similarity index 88% rename from examples/test_pocket.py rename to tests/cc_programs/pocket.py index 57a7ed2..04c4ad9 100644 --- a/examples/test_pocket.py +++ b/tests/cc_programs/pocket.py @@ -5,9 +5,6 @@ assert peripheral.isPresent('back') is False -tbl = _lib.get_object_table('pocket') -assert _lib.get_class_table(pocket) == tbl - _lib.step('Clean inventory from any pocket upgrades') with _lib.assert_raises(LuaException): diff --git a/tests/cc_programs/redirect_to_local_monitor.py b/tests/cc_programs/redirect_to_local_monitor.py new file mode 100644 index 0000000..301492e --- /dev/null +++ b/tests/cc_programs/redirect_to_local_monitor.py @@ -0,0 +1,14 @@ +from cc import colors, term, peripheral + + +side = 'left' +input(f'Attach 3x3 color monitor to {side} side of computer [enter]') + +with term.redirect(peripheral.wrap(side)): + term.setBackgroundColor(colors.green) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + print('Redirected to monitor') + +print('Test finished successfully') diff --git a/examples/test_redirect_to_remote_monitor.py b/tests/cc_programs/redirect_to_remote_monitor.py similarity index 56% rename from examples/test_redirect_to_remote_monitor.py rename to tests/cc_programs/redirect_to_remote_monitor.py index db9f1b1..a7d7c43 100644 --- a/examples/test_redirect_to_remote_monitor.py +++ b/tests/cc_programs/redirect_to_remote_monitor.py @@ -1,14 +1,12 @@ -from cc import import_file, colors, term, peripheral - -_lib = import_file('_lib.py', __file__) +from cc import colors, term, peripheral side = 'back' -_lib.step(f'Attach wired modem to {side} side of computer') +input(f'Attach wired modem to {side} side of computer [enter]') mod = peripheral.wrap(side) -_lib.step('Connect remote monitor using wires, activate its modem') +input('Connect remote monitor using wires, activate its modem [enter]') for name in mod.getNamesRemote(): if mod.getTypeRemote(name) == 'monitor': @@ -16,7 +14,7 @@ else: assert False -with term.redirect(peripheral.get_term_target(name)): +with term.redirect(peripheral.wrap(name)): term.setBackgroundColor(colors.blue) term.setTextColor(colors.white) term.clear() diff --git a/tests/cc_programs/redirect_to_window.py b/tests/cc_programs/redirect_to_window.py new file mode 100644 index 0000000..7627025 --- /dev/null +++ b/tests/cc_programs/redirect_to_window.py @@ -0,0 +1,25 @@ +from cc import colors, term, window + + +w, h = term.getSize() +left = window.create( + term.current(), + 1, 1, w // 2, h, True) +right = window.create( + term.current(), + w // 2 + 1, 1, w // 2, h, True) +with term.redirect(left): + term.setBackgroundColor(colors.green) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, h // 2) + print('Left part') +with term.redirect(right): + term.setBackgroundColor(colors.red) + term.setTextColor(colors.yellow) + term.clear() + term.setCursorPos(1, h // 2) + print('Right part') +print('Default terminal restored') + +print('Test finished successfully') diff --git a/examples/test_rednet.py b/tests/cc_programs/rednet.py similarity index 77% rename from examples/test_rednet.py rename to tests/cc_programs/rednet.py index bb1457c..2c466db 100644 --- a/examples/test_rednet.py +++ b/tests/cc_programs/rednet.py @@ -3,15 +3,12 @@ _lib = import_file('_lib.py', __file__) step, assert_raises = _lib.step, _lib.assert_raises - -tbl = _lib.get_object_table('rednet') -del tbl['function']['run'] -assert _lib.get_class_table(rednet) == tbl - side = 'back' step(f'Attach modem to {side} side of computer') +assert rednet.close() is None + assert rednet.isOpen(side) is False assert rednet.isOpen() is False @@ -40,18 +37,18 @@ assert rednet.unhost('helloproto') is None -assert rednet.send(cid + 100, 'message', 'anyproto') is True -assert rednet.broadcast('message', 'anyproto') is None +assert rednet.send(cid + 100, b'message', 'anyproto') is True +assert rednet.broadcast(b'message', 'anyproto') is None assert rednet.receive(timeout=1) is None def _send(): - assert rednet.send(cid, 'message') is True + assert rednet.send(cid, b'message') is True def _recv(): - assert rednet.receive(timeout=1) == (cid, 'message', None) + assert rednet.receive(timeout=1) == (cid, b'message', None) parallel.waitForAll(_send, _recv) diff --git a/examples/test_redstone.py b/tests/cc_programs/redstone.py similarity index 84% rename from examples/test_redstone.py rename to tests/cc_programs/redstone.py index 5a8ab47..c78a20e 100644 --- a/examples/test_redstone.py +++ b/tests/cc_programs/redstone.py @@ -2,16 +2,6 @@ _lib = import_file('_lib.py', __file__) - -tbl = _lib.get_object_table('redstone') - -# remove British method names to make API lighter -del tbl['function']['getAnalogueInput'] -del tbl['function']['getAnalogueOutput'] -del tbl['function']['setAnalogueOutput'] - -assert _lib.get_class_table(redstone) == tbl - assert set(redstone.getSides()) == {'top', 'bottom', 'front', 'back', 'left', 'right'} _lib.step('Remove all the redstone from sides of computer') diff --git a/examples/test_settings.py b/tests/cc_programs/settings.py similarity index 83% rename from examples/test_settings.py rename to tests/cc_programs/settings.py index b0197c6..8527f7f 100644 --- a/examples/test_settings.py +++ b/tests/cc_programs/settings.py @@ -3,18 +3,22 @@ _lib = import_file('_lib.py', __file__) step, assert_raises = _lib.step, _lib.assert_raises - -assert _lib.get_class_table(settings) == _lib.get_object_table('settings') - step('Settings will be cleared') +assert settings.undefine('test.a') is None +assert settings.undefine('test.a') is None +assert settings.undefine('test.a') is None +assert settings.undefine('test.b') is None +assert settings.undefine('test.c') is None +assert settings.undefine('test.d') is None + assert settings.clear() is None # names are not empty, there are system settings assert isinstance(settings.getNames(), list) assert settings.define('test.a') is None assert settings.define('test.b', description='b') is None -assert settings.define('test.c', type='string') is None +assert settings.define('test.c', type='string', default='x') is None assert settings.define('test.d', default=42) is None assert settings.getDetails('test.a') == { @@ -27,6 +31,8 @@ assert settings.getDetails('test.c') == { 'changed': False, 'type': 'string', + 'default': 'x', + 'value': 'x', } assert settings.getDetails('test.d') == { 'changed': False, @@ -78,8 +84,8 @@ assert {'test.a', 'test.b', 'test.c', 'test.d'} & set(settings.getNames()) == set() -assert settings.set('test.e', [9, 'text', False]) is None -assert settings.get('test.e') == [9, 'text', False] +assert settings.set('test.e', [9, b'text', False]) is None +assert settings.get('test.e') == {1: 9, 2: b'text', 3: False} assert settings.clear() is None assert settings.get('test.e') is None diff --git a/examples/test_shell.py b/tests/cc_programs/shell.py similarity index 89% rename from examples/test_shell.py rename to tests/cc_programs/shell.py index 1f03bb4..909a23b 100644 --- a/examples/test_shell.py +++ b/tests/cc_programs/shell.py @@ -3,11 +3,6 @@ _lib = import_file('_lib.py', __file__) -tbl = _lib.get_object_table('shell') -del tbl['function']['setCompletionFunction'] -del tbl['function']['getCompletionInfo'] -assert _lib.get_class_table(shell) == tbl - assert shell.complete('ls ro') == ['m/', 'm'] assert shell.completeProgram('lu') == ['a'] diff --git a/tests/cc_programs/shutdown.py b/tests/cc_programs/shutdown.py new file mode 100644 index 0000000..bfd8d95 --- /dev/null +++ b/tests/cc_programs/shutdown.py @@ -0,0 +1,8 @@ +from cc import os + + +if args[-1:] == [b'reboot']: + assert os.reboot() is None +else: + assert os.shutdown() is None +print('Test finished successfully') diff --git a/examples/test_term.py b/tests/cc_programs/term.py similarity index 61% rename from examples/test_term.py rename to tests/cc_programs/term.py index be82b9d..51da4e5 100644 --- a/examples/test_term.py +++ b/tests/cc_programs/term.py @@ -1,30 +1,23 @@ -from cc import import_file, colors, os, term -from computercraft.subapis.mixins import TermMixin +from cc import colors, term, os -_lib = import_file('_lib.py', __file__) +def step(text): + input(f'{text} [enter]') -tbl = _lib.get_object_table('term') -# not defined in TermMixin -del tbl['function']['redirect'] -del tbl['function']['current'] -del tbl['function']['native'] -del tbl['function']['nativePaletteColor'] +def term_step(text): + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) -# remove British method names to make API lighter -del tbl['function']['getBackgroundColour'] -del tbl['function']['getPaletteColour'] -del tbl['function']['getTextColour'] -del tbl['function']['isColour'] -del tbl['function']['nativePaletteColour'] -del tbl['function']['setBackgroundColour'] -del tbl['function']['setPaletteColour'] -del tbl['function']['setTextColour'] -assert _lib.get_class_table(TermMixin) == tbl - -_lib.step( +step( 'Detach all monitors\n' 'Use advanced computer for colors\n' 'Screen will be cleared' @@ -43,7 +36,7 @@ assert term.getCursorBlink() is True os.sleep(2) -_lib.term_step('You must have seen word Alpha with blinking cursor') +term_step('You must have seen word Alpha with blinking cursor') assert term.clear() is None for offs, (tc, bc) in enumerate(( @@ -67,7 +60,7 @@ assert term.scroll(1) is None os.sleep(0.25) -_lib.term_step('You must have seen three texts with different colors scrolling') +term_step('You must have seen three texts with different colors scrolling') assert term.clear() is None for i in range(1, 10): @@ -79,18 +72,18 @@ assert term.clearLine() is None os.sleep(2) -_lib.term_step('You must have seen some lines disappearing') +term_step('You must have seen some lines disappearing') assert term.clear() is None assert term.setCursorPos(1, 1) is None assert term.blit( 'rainbowrainbow', - 'e14d3ba0000000', - 'fffffffe14d3ba', + b'e14d3ba0000000', + b'fffffffe14d3ba', ) is None os.sleep(3) -_lib.term_step('You must have seen per-letter colored text') +term_step('You must have seen per-letter colored text') assert term.setBackgroundColor(colors.white) is None assert term.clear() is None @@ -99,11 +92,11 @@ term.setPaletteColor(color, i / 15, 0, 0) assert term.blit( ' redtextappears!', - '0123456789abcdef', - '0000000000000000', + b'0123456789abcdef', + b'0000000000000000', ) is None os.sleep(3) -_lib.term_step('You must have seen different shades of red made using palettes') +term_step('You must have seen different shades of red made using palettes') print('Test finished successfully') diff --git a/examples/test_textutils.py b/tests/cc_programs/textutils.py similarity index 96% rename from examples/test_textutils.py rename to tests/cc_programs/textutils.py index a05bce0..51e67b7 100644 --- a/examples/test_textutils.py +++ b/tests/cc_programs/textutils.py @@ -6,7 +6,7 @@ assert textutils.slowPrint('print') is None assert textutils.slowPrint('print', 5) is None -assert textutils.formatTime(0) == '0:00 AM' +assert textutils.formatTime(0) == '12:00 AM' assert textutils.formatTime(0, True) == '0:00' table = [ diff --git a/examples/test_turtle.py b/tests/cc_programs/turtle.py similarity index 83% rename from examples/test_turtle.py rename to tests/cc_programs/turtle.py index 9a850a1..a2ce6e9 100644 --- a/examples/test_turtle.py +++ b/tests/cc_programs/turtle.py @@ -3,13 +3,6 @@ _lib = import_file('_lib.py', __file__) assert_raises, step = _lib.assert_raises, _lib.step - -tbl = _lib.get_object_table('turtle') -assert tbl['table'] == {'native': True} -del tbl['table'] -tbl['function'].setdefault('craft', True) -assert _lib.get_class_table(turtle) == tbl - flimit = turtle.getFuelLimit() assert isinstance(flimit, int) assert flimit > 0 @@ -114,18 +107,20 @@ assert turtle.getItemCount() == 3 -assert turtle.forward() is True -assert turtle.back() is True -assert turtle.up() is True -assert turtle.down() is True +assert turtle.forward() is None +assert turtle.back() is None +assert turtle.up() is None +assert turtle.down() is None assert turtle.turnLeft() is None assert turtle.turnRight() is None -assert turtle.place() is True -assert turtle.place() is False -assert turtle.placeUp() is True -assert turtle.placeUp() is False -assert turtle.placeDown() is True +assert turtle.place() is None +with assert_raises(LuaException, 'Cannot place block here'): + turtle.place() +assert turtle.placeUp() is None +with assert_raises(LuaException, 'Cannot place block here'): + turtle.placeUp() +assert turtle.placeDown() is None with assert_raises(LuaException, 'No items to place'): turtle.placeDown() @@ -146,31 +141,32 @@ assert turtle.select(1) is None -assert turtle.transferTo(2, 1) is True +assert turtle.transferTo(2, 1) is None assert turtle.getItemCount(1) == 2 assert turtle.getItemCount(2) == 1 assert turtle.compareTo(2) is True -assert turtle.transferTo(2) is True +assert turtle.transferTo(2) is None assert turtle.getItemCount(1) == 0 assert turtle.getItemCount(2) == 3 assert turtle.compareTo(2) is False assert turtle.select(2) is None -assert turtle.transferTo(1) is True +assert turtle.transferTo(1) is None assert turtle.select(1) is None assert turtle.dig() is True assert turtle.digUp() is True assert turtle.digDown() is True assert turtle.getItemCount() == 6 -assert turtle.drop(1) is True -assert turtle.dropUp(1) is True -assert turtle.dropDown(1) is True +assert turtle.drop(1) is None +assert turtle.dropUp(1) is None +assert turtle.dropDown(1) is None assert turtle.getItemCount() == 3 -assert turtle.drop() is True +assert turtle.drop() is None assert turtle.getItemCount() == 0 -assert turtle.drop() is False +with assert_raises(LuaException, 'No items to drop'): + turtle.drop() step( 'Collect dropped cobblestone\n' @@ -183,7 +179,7 @@ assert turtle.suck() is True assert turtle.getItemCount() == 64 assert turtle.suck() is False -assert turtle.drop() is True +assert turtle.drop() is None assert turtle.getItemCount() == 0 step( @@ -197,7 +193,7 @@ assert turtle.suckDown() is True assert turtle.getItemCount() == 64 assert turtle.suckDown() is False -assert turtle.dropDown() is True +assert turtle.dropDown() is None assert turtle.getItemCount() == 0 step( @@ -211,7 +207,7 @@ assert turtle.suckUp() is True assert turtle.getItemCount() == 64 assert turtle.suckUp() is False -assert turtle.dropUp() is True +assert turtle.dropUp() is None assert turtle.getItemCount() == 0 @@ -235,11 +231,13 @@ def craft2(): ) assert turtle.select(1) is None - assert craft_fn() is False + with assert_raises(LuaException, 'No matching recipes'): + craft_fn() for idx in [2, 3, 5, 7, 9, 10, 11]: - assert turtle.transferTo(idx, 1) - assert craft_fn() is True - assert craft_fn() is False + assert turtle.transferTo(idx, 1) is None + assert craft_fn() is None + with assert_raises(LuaException, 'No matching recipes'): + craft_fn() assert turtle.getItemDetail() == { 'count': 1, 'name': 'minecraft:furnace', diff --git a/examples/test_turtle_attack.py b/tests/cc_programs/turtle_attack.py similarity index 84% rename from examples/test_turtle_attack.py rename to tests/cc_programs/turtle_attack.py index f91e20b..82b3293 100644 --- a/examples/test_turtle_attack.py +++ b/tests/cc_programs/turtle_attack.py @@ -10,7 +10,7 @@ ) assert turtle.attack() is True -assert turtle.attack() is True +assert type(turtle.attack()) is bool assert turtle.attack() is False _lib.step( @@ -19,7 +19,7 @@ ) assert turtle.attackDown() is True -assert turtle.attackDown() is True +assert type(turtle.attackDown()) is bool assert turtle.attackDown() is False _lib.step( @@ -28,7 +28,7 @@ ) assert turtle.attackUp() is True -assert turtle.attackUp() is True +assert type(turtle.attackUp()) is bool assert turtle.attackUp() is False print('Test finished successfully') diff --git a/tests/cc_programs/window.py b/tests/cc_programs/window.py new file mode 100644 index 0000000..2e42c9f --- /dev/null +++ b/tests/cc_programs/window.py @@ -0,0 +1,33 @@ +from cc import colors, term, os, window + + +win = window.create(term.current(), 15, 5, 5, 5, False) +assert win.getPosition() == (15, 5) +assert win.getSize() == (5, 5) + +win.setBackgroundColor(colors.red) +win.clear() +win.setVisible(True) + +os.sleep(1) + +win.setVisible(False) +win.setCursorPos(1, 1) +win.setTextColor(colors.yellow) +win.write('*********') +win.setVisible(True) + +os.sleep(1) + +term.clear() + +os.sleep(1) + +win.redraw() +assert win.getLine(1) == ('*****', b'44444', b'eeeee') + +# draws immediately +win.reposition(21, 5) +win.reposition(27, 5) + +print('Test finished successfully') diff --git a/tests/oc_programs/component.py b/tests/oc_programs/component.py new file mode 100644 index 0000000..38f97eb --- /dev/null +++ b/tests/oc_programs/component.py @@ -0,0 +1,43 @@ +import uuid +from contextlib import contextmanager + +from computercraft.errors import LuaException +from oc import component + + +@contextmanager +def assert_raises(text): + try: + yield + except LuaException as e: + assert text in e.message, \ + 'message mismatch {} != {}'.format(e.message, text) + else: + assert False, 'must raise an exception' + + +assert component.isAvailable('gpu') is True +assert component.isAvailable('nonexistent') is False +cs = component.list() + +inv_addr = uuid.UUID(int=0) +assert inv_addr not in cs + +for addr, ctype in cs.items(): + assert component.type(addr) == ctype + slot = component.slot(addr) + assert isinstance(slot, int) + print('{} {}'.format(slot, ctype)) + +with assert_raises('no such component'): + component.type(inv_addr) +with assert_raises('no such component'): + component.slot(inv_addr) + +assert isinstance(component.getPrimaryAddress('gpu'), uuid.UUID) +with assert_raises("no primary 'nonexistent' available"): + component.getPrimaryAddress('nonexistent') + +gpu = component.getPrimary('gpu') +assert 1 <= gpu.getDepth() <= 256 +print(gpu) diff --git a/tests/oc_programs/computer.py b/tests/oc_programs/computer.py new file mode 100644 index 0000000..c3eac98 --- /dev/null +++ b/tests/oc_programs/computer.py @@ -0,0 +1,11 @@ +from oc import computer + +print(computer.address()) +print(computer.tmpAddress()) +print(computer.freeMemory()) +print(computer.totalMemory()) +print(computer.energy()) +print(computer.maxEnergy()) +print(computer.uptime()) +print(computer.getBootAddress()) +print(computer.runlevel()) diff --git a/tests/oc_programs/enums_kbd.py b/tests/oc_programs/enums_kbd.py new file mode 100644 index 0000000..33e9dff --- /dev/null +++ b/tests/oc_programs/enums_kbd.py @@ -0,0 +1,33 @@ +from oc import sides, colors, keyboard + + +def check_iterable(m, mlen): + n = 0 + for i, v in m: + assert m[i] == v + assert getattr(m, v) == i + n += 1 + assert len(m) == n == mlen > 0 + + +assert sides.left == 5 +assert sides.forward == 3 +assert sides[2] == 'back' +check_iterable(sides, 6) + +assert colors.yellow == 4 +assert colors[14] == 'red' +check_iterable(colors, 16) + +assert keyboard.keys.a == 0x1E +assert keyboard.keys[0x40] == 'f6' +check_iterable(keyboard.keys, len(keyboard.keys)) + +assert keyboard.isAltDown() is False +assert keyboard.isControlDown() is False +assert keyboard.isShiftDown() is False +assert keyboard.isKeyDown('a') is False +assert keyboard.isKeyDown(keyboard.keys.b) is False + +assert keyboard.isControl(0) is True +assert keyboard.isControl(keyboard.keys.c) is False diff --git a/tests/oc_programs/filesystem.py b/tests/oc_programs/filesystem.py new file mode 100644 index 0000000..6d002d0 --- /dev/null +++ b/tests/oc_programs/filesystem.py @@ -0,0 +1,133 @@ +from contextlib import contextmanager + +from computercraft.errors import LuaException +from oc import filesystem + + +@contextmanager +def assert_raises(text): + try: + yield + except LuaException as e: + assert text in e.message, \ + 'message mismatch {} != {}'.format(e.message, text) + else: + assert False, 'must raise an exception' + + +val = filesystem.isAutorunEnabled() +filesystem.setAutorunEnabled(False) +filesystem.setAutorunEnabled(True) +filesystem.setAutorunEnabled(val) + +assert filesystem.canonical('a/../b/../c') == 'c' +assert filesystem.canonical('/a/../b') == '/b' +assert filesystem.segments('/a/b/c') == ['a', 'b', 'c'] +assert filesystem.concat('/a', 'b', '..', 'c') == '/a/c' +assert filesystem.path('/a/b/c/d') == '/a/b/c/' +assert filesystem.name('/a/b/c/d') == 'd' + +target = '/testtemp' + +if filesystem.isDirectory(target): + assert filesystem.remove(target) is True + +assert filesystem.exists(target) is False +assert filesystem.isDirectory(target) is False + +assert filesystem.makeDirectory(target) is True +with assert_raises('already exists'): + filesystem.makeDirectory(target) + +assert filesystem.exists(target) is True +assert filesystem.isDirectory(target) is True + +file_a = filesystem.concat(target, 'a.txt') +with filesystem.open(file_a, 'w') as f: + assert f.write(b'12345') is True + assert f.seek('set') == 0 + assert f.write(b'67890') is True + assert f.seek('cur') == 5 + +with filesystem.open(file_a, 'r') as f: + assert f.read(10) == b'67890' + assert f.read(10) is None + +file_x = filesystem.concat(target, 'x.txt') +for mdp in ('', 'b'): + with filesystem.open(file_x, 'w' + mdp) as f: + assert f.write(bytes(range(256))) is True + with filesystem.open(file_x, 'r' + mdp) as f: + assert f.read(300) == bytes(range(256)) + with filesystem.open(file_x, 'w' + mdp) as f: + assert f.write('привет') is True + with filesystem.open(file_x, 'r' + mdp) as f: + assert f.read(300) == 'привет'.encode('utf-8') + +file_b = filesystem.concat(target, 'b.txt') +file_c = filesystem.concat(target, 'c.txt') + +assert filesystem.copy(file_a + '.notexists', file_b) is False +assert filesystem.copy(file_a, file_b) is True +assert filesystem.copy(file_a, file_b) is True + +assert filesystem.rename(file_b + '.notexists', file_c) is False +assert filesystem.rename(file_b, file_c) is True + +with assert_raises('no such file'): + filesystem.remove(file_b + '.notexists') +assert filesystem.remove(file_c) is True + +assert filesystem.exists(file_a) is True +assert filesystem.exists(file_b) is False +assert filesystem.exists(file_c) is False +assert filesystem.exists(target) is True + +assert filesystem.isDirectory(file_a) is False +assert filesystem.isDirectory(file_b) is False +assert filesystem.isDirectory(target) is True + +assert filesystem.isLink(file_a) == (False, None) +assert filesystem.isLink(file_b) == (False, None) +assert filesystem.isLink(target) == (False, None) + +assert filesystem.link(file_a, file_b) is True +with assert_raises('already exists'): + filesystem.link(file_a, file_b) + +assert filesystem.isLink(file_b) == (True, file_a) +assert filesystem.exists(file_b) is True +assert filesystem.isDirectory(file_b) is False + +assert filesystem.link(target, file_c) is True +assert filesystem.exists(file_c) is True +assert filesystem.isDirectory(file_c) is True + +assert filesystem.size(file_a) == 5 +assert filesystem.size(file_b) == 5 +assert filesystem.size(file_c) == 0 +assert filesystem.size(file_a + '.notexists') == 0 + +lm = filesystem.lastModified(file_a) +assert lm > 0 +assert filesystem.lastModified(file_b) == lm +assert filesystem.lastModified(file_c) == 0 +assert filesystem.lastModified(file_a + '.notexists') == 0 + +assert sorted(filesystem.list(target)) == ['a.txt', 'b.txt', 'c.txt', 'x.txt'] + +root_fs = filesystem.get('/')[0] +mpoint = filesystem.concat(target, 'mpoint') +assert filesystem.list(mpoint) == [] +assert filesystem.get(mpoint + '.notexists')[0] == root_fs + +for fs, p in filesystem.mounts(): + if fs != root_fs: + assert filesystem.get(p) == (fs, p) + assert filesystem.mount(fs, mpoint) is True + assert filesystem.get(mpoint) == (fs, mpoint) + assert filesystem.umount(mpoint) is True + break + +assert filesystem.umount(mpoint) is False +assert filesystem.remove(target) is True diff --git a/tests/oc_programs/helloworld.py b/tests/oc_programs/helloworld.py new file mode 100644 index 0000000..542f0a4 --- /dev/null +++ b/tests/oc_programs/helloworld.py @@ -0,0 +1,4 @@ +print(args) +for _ in range(2): + print("Hello world!") +print("Привет мир!") diff --git a/tests/oc_programs/os.py b/tests/oc_programs/os.py new file mode 100644 index 0000000..130a7eb --- /dev/null +++ b/tests/oc_programs/os.py @@ -0,0 +1,16 @@ +from oc import os + + +print(os.date()) +c = os.clock() +assert c >= 0 +t = os.time() +assert t >= 0 + +assert os.sleep(2) is None + +assert os.clock() >= c +assert os.time() >= t + +os.exit() +print('unreachable') diff --git a/tests/oc_programs/term.py b/tests/oc_programs/term.py new file mode 100644 index 0000000..eef92e1 --- /dev/null +++ b/tests/oc_programs/term.py @@ -0,0 +1,33 @@ +from oc import term + + +assert term.isAvailable() is True +term.clear() +term.setCursor(1, 1) +assert term.getCursor() == (1, 1) + +blink = term.getCursorBlink() +term.setCursorBlink(False) +term.setCursorBlink(True) +term.setCursorBlink(blink) +assert term.getCursorBlink() == blink +print('Screen', term.screen()) +print('Keyboard', term.keyboard()) + +width, height, xoff, yoff, relx, rely = term.getViewport() +print('Width={} Height={}'.format(width, height)) +print('Offset X={} Y={}'.format(xoff, yoff)) +print('Relative X={} Y={}'.format(relx, rely)) + +term.write('Write text no wrap') +term.clearLine() +term.write('Write text with wrap. ' * 7 + '\n', True) + +term.write('Input "qwerty" twice:\n') +assert term.read(dobreak=False) == 'qwerty\n' +term.clearLine() +assert term.read(pwchar='*') == 'qwerty\n' +term.write('\nInput "привет":\n') +assert term.read() == 'привет\n' +term.write('Press ctrl+D in this input:\n') +assert term.read() is None diff --git a/tests/proto/m_1.12.2/oc_1.8.2/component.txt b/tests/proto/m_1.12.2/oc_1.8.2/component.txt new file mode 100644 index 0000000..5158245 --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/component.txt @@ -0,0 +1,241 @@ +R1175:0[5]{:[1]<12>component.py}<18>/home/component.py<1121>import uuid +from contextlib import contextmanager + +from computercraft.errors import LuaException +from oc import component + + +@contextmanager +def assert_raises(text): + try: + yield + except LuaException as e: + assert text in e.message, \ + 'message mismatch {} != {}'.format(e.message, text) + else: + assert False, 'must raise an exception' + + +assert component.isAvailable('gpu') is True +assert component.isAvailable('nonexistent') is False +cs = component.list() + +inv_addr = uuid.UUID(int=0) +assert inv_addr not in cs + +for addr, ctype in cs.items(): + assert component.type(addr) == ctype + slot = component.slot(addr) + assert isinstance(slot, int) + print('{} {}'.format(slot, ctype)) + +with assert_raises('no such component'): + component.type(inv_addr) +with assert_raises('no such component'): + component.slot(inv_addr) + +assert isinstance(component.getPrimaryAddress('gpu'), uuid.UUID) +with assert_raises("no primary 'nonexistent' available"): + component.getPrimaryAddress('nonexistent') + +gpu = component.getPrimary('gpu') +assert 1 <= gpu.getDepth() <= 256 +print(gpu) + +S46:T<1>1<25>R:component:M:isAvailable{:[1]<3>gpu} +R21:T<1>1<12>{:[1]T:[2]T} +S55:T<1>1<25>R:component:M:isAvailable{:[1]<11>nonexistent} +R21:T<1>1<12>{:[1]T:[2]F} +S29:T<1>1<18>R:component:M:list{} +R1032:T<1>1<1021>{:[1]T:[2]{:<36>99e51b75-be78-437a-ad1c-34109fc2c1ce<3>gpu:<36>2cd01791-1463-4fa8-a3c1-4277dbd0acfd<8>internet:<36>aea7d223-6e47-4fd7-b567-081d6dec0777<10>filesystem:<36>70985d49-ac20-4354-96d7-83fc467ac16c<7>trading:<36>a5b1411e-60dc-4cfa-881c-c1e0550ab7bd<10>experience:<36>248e7560-b8dc-4516-bba9-0de3b5b043bf<8>geolyzer:<36>28125799-6b35-40a7-8b37-47e526e24edd<6>screen:<36>9ad13475-5e59-4801-a461-03d8450b8dcb<10>filesystem:<36>79a9b8eb-7189-4eb7-a5d6-587c597e22d7<20>inventory_controller:<36>8ba0d546-1274-471e-8177-3795b7456994<15>tank_controller:<36>7c9b319c-6be4-47d7-8e8b-ea42149e9a67<6>eeprom:<36>9c0f1134-d55d-4793-b8b2-d97655b80209<8>crafting:<36>373ddc8c-8986-4481-a4d2-4c4598d38ec9<8>keyboard:<36>7a1c769f-4f18-4f7e-9854-6b44b5898b8c<5>modem:<36>bfa2ec7b-fcd9-46bd-b5f0-50836c23757f<10>filesystem:<36>a2d51bff-a6a8-435f-89e5-cecb49259434<8>redstone:<36>479f3d10-ca60-41d6-86b1-b8e472dc4d2b<5>robot:<36>2f821d20-bc83-4ae3-95c1-a171439850ff<10>filesystem:<36>021442c9-1bb5-4037-95fc-6cafc9733b7d<8>computer}} +S73:T<1>1<18>R:component:M:type{:[1]<36>99e51b75-be78-437a-ad1c-34109fc2c1ce} +R26:T<1>1<17>{:[1]T:[2]<3>gpu} +S73:T<1>1<18>R:component:M:slot{:[1]<36>99e51b75-be78-437a-ad1c-34109fc2c1ce} +R24:T<1>1<15>{:[1]T:[2][83]} +S37:T<1>1<13>io.write(...){:[1]<6>83 gpu} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>2cd01791-1463-4fa8-a3c1-4277dbd0acfd} +R31:T<1>1<22>{:[1]T:[2]<8>internet} +S73:T<1>1<18>R:component:M:slot{:[1]<36>2cd01791-1463-4fa8-a3c1-4277dbd0acfd} +R24:T<1>1<15>{:[1]T:[2][86]} +S43:T<1>1<13>io.write(...){:[1]<11>86 internet} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>aea7d223-6e47-4fd7-b567-081d6dec0777} +R34:T<1>1<25>{:[1]T:[2]<10>filesystem} +S73:T<1>1<18>R:component:M:slot{:[1]<36>aea7d223-6e47-4fd7-b567-081d6dec0777} +R24:T<1>1<15>{:[1]T:[2][91]} +S45:T<1>1<13>io.write(...){:[1]<13>91 filesystem} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>70985d49-ac20-4354-96d7-83fc467ac16c} +R30:T<1>1<21>{:[1]T:[2]<7>trading} +S73:T<1>1<18>R:component:M:slot{:[1]<36>70985d49-ac20-4354-96d7-83fc467ac16c} +R24:T<1>1<15>{:[1]T:[2][81]} +S42:T<1>1<13>io.write(...){:[1]<10>81 trading} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>a5b1411e-60dc-4cfa-881c-c1e0550ab7bd} +R34:T<1>1<25>{:[1]T:[2]<10>experience} +S73:T<1>1<18>R:component:M:slot{:[1]<36>a5b1411e-60dc-4cfa-881c-c1e0550ab7bd} +R24:T<1>1<15>{:[1]T:[2][82]} +S45:T<1>1<13>io.write(...){:[1]<13>82 experience} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>248e7560-b8dc-4516-bba9-0de3b5b043bf} +R31:T<1>1<22>{:[1]T:[2]<8>geolyzer} +S73:T<1>1<18>R:component:M:slot{:[1]<36>248e7560-b8dc-4516-bba9-0de3b5b043bf} +R24:T<1>1<15>{:[1]T:[2][70]} +S43:T<1>1<13>io.write(...){:[1]<11>70 geolyzer} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>28125799-6b35-40a7-8b37-47e526e24edd} +R29:T<1>1<20>{:[1]T:[2]<6>screen} +S73:T<1>1<18>R:component:M:slot{:[1]<36>28125799-6b35-40a7-8b37-47e526e24edd} +R24:T<1>1<15>{:[1]T:[2][68]} +S40:T<1>1<13>io.write(...){:[1]<9>68 screen} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>9ad13475-5e59-4801-a461-03d8450b8dcb} +R34:T<1>1<25>{:[1]T:[2]<10>filesystem} +S73:T<1>1<18>R:component:M:slot{:[1]<36>9ad13475-5e59-4801-a461-03d8450b8dcb} +R24:T<1>1<15>{:[1]T:[2][-1]} +S45:T<1>1<13>io.write(...){:[1]<13>-1 filesystem} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>79a9b8eb-7189-4eb7-a5d6-587c597e22d7} +R44:T<1>1<35>{:[1]T:[2]<20>inventory_controller} +S73:T<1>1<18>R:component:M:slot{:[1]<36>79a9b8eb-7189-4eb7-a5d6-587c597e22d7} +R24:T<1>1<15>{:[1]T:[2][75]} +S55:T<1>1<13>io.write(...){:[1]<23>75 inventory_controller} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>8ba0d546-1274-471e-8177-3795b7456994} +R39:T<1>1<30>{:[1]T:[2]<15>tank_controller} +S73:T<1>1<18>R:component:M:slot{:[1]<36>8ba0d546-1274-471e-8177-3795b7456994} +R24:T<1>1<15>{:[1]T:[2][77]} +S50:T<1>1<13>io.write(...){:[1]<18>77 tank_controller} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>7c9b319c-6be4-47d7-8e8b-ea42149e9a67} +R29:T<1>1<20>{:[1]T:[2]<6>eeprom} +S73:T<1>1<18>R:component:M:slot{:[1]<36>7c9b319c-6be4-47d7-8e8b-ea42149e9a67} +R24:T<1>1<15>{:[1]T:[2][90]} +S40:T<1>1<13>io.write(...){:[1]<9>90 eeprom} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>9c0f1134-d55d-4793-b8b2-d97655b80209} +R31:T<1>1<22>{:[1]T:[2]<8>crafting} +S73:T<1>1<18>R:component:M:slot{:[1]<36>9c0f1134-d55d-4793-b8b2-d97655b80209} +R24:T<1>1<15>{:[1]T:[2][78]} +S43:T<1>1<13>io.write(...){:[1]<11>78 crafting} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>373ddc8c-8986-4481-a4d2-4c4598d38ec9} +R31:T<1>1<22>{:[1]T:[2]<8>keyboard} +S73:T<1>1<18>R:component:M:slot{:[1]<36>373ddc8c-8986-4481-a4d2-4c4598d38ec9} +R24:T<1>1<15>{:[1]T:[2][69]} +S43:T<1>1<13>io.write(...){:[1]<11>69 keyboard} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>7a1c769f-4f18-4f7e-9854-6b44b5898b8c} +R28:T<1>1<19>{:[1]T:[2]<5>modem} +S73:T<1>1<18>R:component:M:slot{:[1]<36>7a1c769f-4f18-4f7e-9854-6b44b5898b8c} +R24:T<1>1<15>{:[1]T:[2][85]} +S39:T<1>1<13>io.write(...){:[1]<8>85 modem} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>bfa2ec7b-fcd9-46bd-b5f0-50836c23757f} +R34:T<1>1<25>{:[1]T:[2]<10>filesystem} +S73:T<1>1<18>R:component:M:slot{:[1]<36>bfa2ec7b-fcd9-46bd-b5f0-50836c23757f} +R24:T<1>1<15>{:[1]T:[2][92]} +S45:T<1>1<13>io.write(...){:[1]<13>92 filesystem} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>a2d51bff-a6a8-435f-89e5-cecb49259434} +R31:T<1>1<22>{:[1]T:[2]<8>redstone} +S73:T<1>1<18>R:component:M:slot{:[1]<36>a2d51bff-a6a8-435f-89e5-cecb49259434} +R24:T<1>1<15>{:[1]T:[2][84]} +S43:T<1>1<13>io.write(...){:[1]<11>84 redstone} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>479f3d10-ca60-41d6-86b1-b8e472dc4d2b} +R28:T<1>1<19>{:[1]T:[2]<5>robot} +S73:T<1>1<18>R:component:M:slot{:[1]<36>479f3d10-ca60-41d6-86b1-b8e472dc4d2b} +R24:T<1>1<15>{:[1]T:[2][-1]} +S39:T<1>1<13>io.write(...){:[1]<8>-1 robot} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>2f821d20-bc83-4ae3-95c1-a171439850ff} +R34:T<1>1<25>{:[1]T:[2]<10>filesystem} +S73:T<1>1<18>R:component:M:slot{:[1]<36>2f821d20-bc83-4ae3-95c1-a171439850ff} +R24:T<1>1<15>{:[1]T:[2][-1]} +S45:T<1>1<13>io.write(...){:[1]<13>-1 filesystem} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>021442c9-1bb5-4037-95fc-6cafc9733b7d} +R31:T<1>1<22>{:[1]T:[2]<8>computer} +S73:T<1>1<18>R:component:M:slot{:[1]<36>021442c9-1bb5-4037-95fc-6cafc9733b7d} +R24:T<1>1<15>{:[1]T:[2][-1]} +S43:T<1>1<13>io.write(...){:[1]<11>-1 computer} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<18>R:component:M:type{:[1]<36>00000000-0000-0000-0000-000000000000} +R41:T<1>1<32>{:[1]T:[3]<17>no such component} +S73:T<1>1<18>R:component:M:slot{:[1]<36>00000000-0000-0000-0000-000000000000} +R41:T<1>1<32>{:[1]T:[3]<17>no such component} +S76:T<1>1<55>R:component:return _m.component.getPrimary(...).address{:[1]<3>gpu} +R60:T<1>1<51>{:[1]T:[2]<36>99e51b75-be78-437a-ad1c-34109fc2c1ce} +S85:T<1>1<55>R:component:return _m.component.getPrimary(...).address{:[1]<11>nonexistent} +R84:T<1>1<75>{:[1]F:[2]<60>boot/04_component.lua:68: no primary 'nonexistent' available} +S76:T<1>1<55>R:component:return _m.component.getPrimary(...).address{:[1]<3>gpu} +R60:T<1>1<51>{:[1]T:[2]<36>99e51b75-be78-437a-ad1c-34109fc2c1ce} +S73:T<1>1<18>R:component:M:type{:[1]<36>99e51b75-be78-437a-ad1c-34109fc2c1ce} +R26:T<1>1<17>{:[1]T:[2]<3>gpu} +S90:T<1>1<20>R:component:M:invoke{:[1]<36>99e51b75-be78-437a-ad1c-34109fc2c1ce:[2]<8>getDepth} +R23:T<1>1<14>{:[1]T:[2][1]} +S84:T<1>1<13>io.write(...){:[1]<52>} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/computer.txt b/tests/proto/m_1.12.2/oc_1.8.2/computer.txt new file mode 100644 index 0000000..1bd90e6 --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/computer.txt @@ -0,0 +1,76 @@ +R328:0[5]{:[1]<11>computer.py}<17>/home/computer.py<277>from oc import computer + +print(computer.address()) +print(computer.tmpAddress()) +print(computer.freeMemory()) +print(computer.totalMemory()) +print(computer.energy()) +print(computer.maxEnergy()) +print(computer.uptime()) +print(computer.getBootAddress()) +print(computer.runlevel()) + +S31:T<1>1<20>R:computer:M:address{} +R60:T<1>1<51>{:[1]T:[2]<36>021442c9-1bb5-4037-95fc-6cafc9733b7d} +S68:T<1>1<13>io.write(...){:[1]<36>021442c9-1bb5-4037-95fc-6cafc9733b7d} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>R:computer:M:tmpAddress{} +R60:T<1>1<51>{:[1]T:[2]<36>9ad13475-5e59-4801-a461-03d8450b8dcb} +S68:T<1>1<13>io.write(...){:[1]<36>9ad13475-5e59-4801-a461-03d8450b8dcb} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>R:computer:M:freeMemory{} +R29:T<1>1<20>{:[1]T:[2][1872967]} +S38:T<1>1<13>io.write(...){:[1]<7>1872967} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<24>R:computer:M:totalMemory{} +R29:T<1>1<20>{:[1]T:[2][2097152]} +S38:T<1>1<13>io.write(...){:[1]<7>2097152} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<19>R:computer:M:energy{} +R34:T<1>1<25>{:[1]T:[2][20497.259375]} +S44:T<1>1<13>io.write(...){:[1]<12>20497.259375} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<22>R:computer:M:maxEnergy{} +R29:T<1>1<20>{:[1]T:[2][20500.0]} +S36:T<1>1<13>io.write(...){:[1]<5>20500} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<19>R:computer:M:uptime{} +R28:T<1>1<19>{:[1]T:[2][122.25]} +S37:T<1>1<13>io.write(...){:[1]<6>122.25} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S38:T<1>1<27>R:computer:M:getBootAddress{} +R60:T<1>1<51>{:[1]T:[2]<36>bfa2ec7b-fcd9-46bd-b5f0-50836c23757f} +S68:T<1>1<13>io.write(...){:[1]<36>bfa2ec7b-fcd9-46bd-b5f0-50836c23757f} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>R:computer:M:runlevel{} +R23:T<1>1<14>{:[1]T:[2][1]} +S32:T<1>1<13>io.write(...){:[1]<1>1} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/enums_kbd.txt b/tests/proto/m_1.12.2/oc_1.8.2/enums_kbd.txt new file mode 100644 index 0000000..883fbaf --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/enums_kbd.txt @@ -0,0 +1,49 @@ +R861:0[5]{:[1]<12>enums_kbd.py}<18>/home/enums_kbd.py<808>from oc import sides, colors, keyboard + + +def check_iterable(m, mlen): + n = 0 + for i, v in m: + assert m[i] == v + assert getattr(m, v) == i + n += 1 + assert len(m) == n == mlen > 0 + + +assert sides.left == 5 +assert sides.forward == 3 +assert sides[2] == 'back' +check_iterable(sides, 6) + +assert colors.yellow == 4 +assert colors[14] == 'red' +check_iterable(colors, 16) + +assert keyboard.keys.a == 0x1E +assert keyboard.keys[0x40] == 'f6' +check_iterable(keyboard.keys, len(keyboard.keys)) + +assert keyboard.isAltDown() is False +assert keyboard.isControlDown() is False +assert keyboard.isShiftDown() is False +assert keyboard.isKeyDown('a') is False +assert keyboard.isKeyDown(keyboard.keys.b) is False + +assert keyboard.isControl(0) is True +assert keyboard.isControl(keyboard.keys.c) is False + +S33:T<1>1<22>R:keyboard:M:isAltDown{} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<26>R:keyboard:M:isControlDown{} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<24>R:keyboard:M:isShiftDown{} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<22>R:keyboard:M:isKeyDown{:[1]<1>a} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<22>R:keyboard:M:isKeyDown{:[1][48]} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<22>R:keyboard:M:isControl{:[1][0]} +R21:T<1>1<12>{:[1]T:[2]T} +S41:T<1>1<22>R:keyboard:M:isControl{:[1][46]} +R21:T<1>1<12>{:[1]T:[2]F} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/filesystem.txt b/tests/proto/m_1.12.2/oc_1.8.2/filesystem.txt new file mode 100644 index 0000000..55d0e38 Binary files /dev/null and b/tests/proto/m_1.12.2/oc_1.8.2/filesystem.txt differ diff --git a/tests/proto/m_1.12.2/oc_1.8.2/helloworld.txt b/tests/proto/m_1.12.2/oc_1.8.2/helloworld.txt new file mode 100644 index 0000000..25fff5c --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/helloworld.txt @@ -0,0 +1,26 @@ +R160:0[5]{:[1]<13>helloworld.py:[2]<2>42:[3]<3>abc}<19>/home/helloworld.py<87>print(args) +for _ in range(2): + print("Hello world!") +print("Привет мир!") + +S65:T<1>1<13>io.write(...){:[1]<33>[b'helloworld.py', b'42', b'abc']} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>io.write(...){:[1]<12>Hello world!} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>io.write(...){:[1]<12>Hello world!} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S52:T<1>1<13>io.write(...){:[1]<20>Привет мир!} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/os.txt b/tests/proto/m_1.12.2/oc_1.8.2/os.txt new file mode 100644 index 0000000..5087d18 --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/os.txt @@ -0,0 +1,35 @@ +R238:0[5]{:[1]<5>os.py}<11>/home/os.py<200>from oc import os + + +print(os.date()) +c = os.clock() +assert c >= 0 +t = os.time() +assert t >= 0 + +assert os.sleep(2) is None + +assert os.clock() >= c +assert os.time() >= t + +os.exit() +print('unreachable') + +S22:T<1>1<11>R:os:M:date{} +R41:T<1>1<32>{:[1]T:[2]<17>01/01/70 08:34:33} +S49:T<1>1<13>io.write(...){:[1]<17>01/01/70 08:34:33} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S23:T<1>1<12>R:os:M:clock{} +R35:T<1>1<26>{:[1]T:[2][224.519711718]} +S22:T<1>1<11>R:os:M:time{} +R29:T<1>1<20>{:[1]T:[2][27273.6]} +S23:T<1>1<12>R:os:M:sleep{} +R15:T<1>1<7>{:[1]T} +S23:T<1>1<12>R:os:M:clock{} +R35:T<1>1<26>{:[1]T:[2][224.527750133]} +S22:T<1>1<11>R:os:M:time{} +R29:T<1>1<20>{:[1]T:[2][27273.6]} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/repl2p2.txt b/tests/proto/m_1.12.2/oc_1.8.2/repl2p2.txt new file mode 100644 index 0000000..0d7e810 --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/repl2p2.txt @@ -0,0 +1,21 @@ +R6:0[5]{} +S56:T<1>1<20>io.stderr:write(...){:[1]<17>Python +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<13>io.write(...){:[1]<4>>>> } +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R26:T<1>1<17>{:[1]T:[2]<3>2+2} +S32:T<1>1<13>io.write(...){:[1]<1>4} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<13>io.write(...){:[1]<4>>>> } +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<20>io.stderr:write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/replterm.txt b/tests/proto/m_1.12.2/oc_1.8.2/replterm.txt new file mode 100644 index 0000000..088dfa4 --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/replterm.txt @@ -0,0 +1,23 @@ +R6:0[5]{} +S56:T<1>1<20>io.stderr:write(...){:[1]<17>Python +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<13>io.write(...){:[1]<4>>>> } +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R32:T<1>1<23>{:[1]T:[2]<9>'wщ' * 3} +S43:T<1>1<13>io.write(...){:[1]<11>'wщwщwщ'} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<13>io.write(...){:[1]<4>>>> } +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R40:T<1>1<31>{:[1]T:[2]F:[3]<11>interrupted} +S50:I<1>1<20>io.stderr:write(...){:[1]<11>interrupted} +R10:T<1>1<2>{} +S39:T<1>1<20>io.stderr:write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.12.2/oc_1.8.2/term.txt b/tests/proto/m_1.12.2/oc_1.8.2/term.txt new file mode 100644 index 0000000..4594d4c --- /dev/null +++ b/tests/proto/m_1.12.2/oc_1.8.2/term.txt @@ -0,0 +1,122 @@ +R983:0[5]{:[1]<7>term.py}<13>/home/term.py<941>from oc import term + + +assert term.isAvailable() is True +term.clear() +term.setCursor(1, 1) +assert term.getCursor() == (1, 1) + +blink = term.getCursorBlink() +term.setCursorBlink(False) +term.setCursorBlink(True) +term.setCursorBlink(blink) +assert term.getCursorBlink() == blink +print('Screen', term.screen()) +print('Keyboard', term.keyboard()) + +width, height, xoff, yoff, relx, rely = term.getViewport() +print('Width={} Height={}'.format(width, height)) +print('Offset X={} Y={}'.format(xoff, yoff)) +print('Relative X={} Y={}'.format(relx, rely)) + +term.write('Write text no wrap') +term.clearLine() +term.write('Write text with wrap. ' * 7 + '\n', True) + +term.write('Input "qwerty" twice:\n') +assert term.read(dobreak=False) == 'qwerty\n' +term.clearLine() +assert term.read(pwchar='*') == 'qwerty\n' +term.write('\nInput "привет":\n') +assert term.read() == 'привет\n' +term.write('Press ctrl+D in this input:\n') +assert term.read() is None + +S31:T<1>1<20>R:term:M:isAvailable{} +R21:T<1>1<12>{:[1]T:[2]T} +S25:T<1>1<14>R:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S43:T<1>1<18>R:term:M:setCursor{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>R:term:M:getCursor{} +R30:T<1>1<21>{:[1]T:[2][1]:[3][1]} +S34:T<1>1<23>R:term:M:getCursorBlink{} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>R:term:M:setCursorBlink{:[1]F} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>R:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>R:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>R:term:M:getCursorBlink{} +R21:T<1>1<12>{:[1]T:[2]T} +S26:T<1>1<15>R:term:M:screen{} +R60:T<1>1<51>{:[1]T:[2]<36>28125799-6b35-40a7-8b37-47e526e24edd} +S37:T<1>1<13>io.write(...){:[1]<6>Screen} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S68:T<1>1<13>io.write(...){:[1]<36>28125799-6b35-40a7-8b37-47e526e24edd} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S28:T<1>1<17>R:term:M:keyboard{} +R60:T<1>1<51>{:[1]T:[2]<36>373ddc8c-8986-4481-a4d2-4c4598d38ec9} +S39:T<1>1<13>io.write(...){:[1]<8>Keyboard} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S68:T<1>1<13>io.write(...){:[1]<36>373ddc8c-8986-4481-a4d2-4c4598d38ec9} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S31:T<1>1<20>R:term:M:getViewport{} +R60:T<1>1<51>{:[1]T:[2][50]:[3][16]:[4][0]:[5][0]:[6][1]:[7][3]} +S50:T<1>1<13>io.write(...){:[1]<18>Width=50 Height=16} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<13>io.write(...){:[1]<14>Offset X=0 Y=0} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<13>io.write(...){:[1]<16>Relative X=1 Y=3} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S56:T<1>1<14>R:term:M:write{:[1]<18>Write text no wrap:[2]F} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>R:term:M:clearLine{} +R15:T<1>1<7>{:[1]T} +S194:T<1>1<14>R:term:M:write{:[1]<155>Write text with wrap. Write text with wrap. Write text with wrap. Write text with wrap. Write text with wrap. Write text with wrap. Write text with wrap. +:[2]T} +R15:T<1>1<7>{:[1]T} +S60:T<1>1<14>R:term:M:write{:[1]<22>Input "qwerty" twice: +:[2]F} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>R:term:M:read{:[1]N:[2]F:[3]N:[4]N} +R30:T<1>1<21>{:[1]T:[2]<7>qwerty +} +S29:T<1>1<18>R:term:M:clearLine{} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<13>R:term:M:read{:[1]N:[2]T:[3]N:[4]<1>*} +R30:T<1>1<21>{:[1]T:[2]<7>qwerty +} +S61:T<1>1<14>R:term:M:write{:[1]<23> +Input "привет": +:[2]F} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>R:term:M:read{:[1]N:[2]T:[3]N:[4]N} +R37:T<1>1<28>{:[1]T:[2]<13>привет +} +S66:T<1>1<14>R:term:M:write{:[1]<28>Press ctrl+D in this input: +:[2]F} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>R:term:M:read{:[1]N:[2]T:[3]N:[4]N} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/colors.txt b/tests/proto/m_1.20.1/cc_1.108.3/colors.txt new file mode 100644 index 0000000..5b68c72 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/colors.txt @@ -0,0 +1,46 @@ +R636:0[5]{:[1]<9>colors.py:[0]<2>py}<9>colors.py<588>from cc import colors + +cs = colors.combine( + colors.orange, + colors.cyan, + colors.pink, + colors.brown, +) +assert isinstance(cs, int) +cs = colors.subtract(cs, colors.brown, colors.green) +assert isinstance(cs, int) +assert cs == colors.combine( + colors.orange, + colors.cyan, + colors.pink, +) +assert colors.test(cs, colors.red) is False +assert colors.test(cs, colors.cyan) is True + +assert colors.packRGB(0.7, 0.2, 0.6) == 0xb23399 +r, g, b = colors.unpackRGB(0xb23399) +assert 0.68 < r < 0.72 +assert 0.18 < g < 0.22 +assert 0.58 < b < 0.62 + +print('Test finished successfully') +S63:T<1>1<18>G:colors:M:combine{:[1][2]:[2][512]:[3][64]:[4][4096]} +R26:T<1>1<17>{:[1]T:[2][4674]} +S60:T<1>1<19>G:colors:M:subtract{:[1][4674]:[2][4096]:[3][8192]} +R25:T<1>1<16>{:[1]T:[2][578]} +S53:T<1>1<18>G:colors:M:combine{:[1][2]:[2][512]:[3][64]} +R25:T<1>1<16>{:[1]T:[2][578]} +S46:T<1>1<15>G:colors:M:test{:[1][578]:[2][16384]} +R21:T<1>1<12>{:[1]T:[2]F} +S44:T<1>1<15>G:colors:M:test{:[1][578]:[2][512]} +R21:T<1>1<12>{:[1]T:[2]T} +S56:T<1>1<18>G:colors:M:packRGB{:[1][0.7]:[2][0.2]:[3][0.6]} +R30:T<1>1<21>{:[1]T:[2][11678617]} +S45:T<1>1<20>G:colors:M:unpackRGB{:[1][11678617]} +R56:T<1>1<47>{:[1]T:[2][0.69803921568627]:[3][0.2]:[4][0.6]} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/commands.txt b/tests/proto/m_1.20.1/cc_1.108.3/commands.txt new file mode 100644 index 0000000..0da5e02 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/commands.txt @@ -0,0 +1,84 @@ +R1381:0[5]{:[1]<11>commands.py:[0]<2>py}<11>commands.py<1326>from cc import commands + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +print('Run this test on command computer') + +xyz = commands.getBlockPosition() +assert xyz == ( + AnyInstanceOf(int), + AnyInstanceOf(int), + AnyInstanceOf(int), +) + +expected_binfo = { + 'state': { + 'state': AnyInstanceOf(str), + 'facing': AnyInstanceOf(str), + }, + 'name': 'computercraft:computer_command', + 'nbt': { + 'x': xyz[0], + 'y': xyz[1], + 'z': xyz[2], + 'ForgeCaps': {}, + 'ComputerId': AnyInstanceOf(int), + 'id': 'computercraft:computer_command', + 'On': 1, + }, + 'tags': {'computercraft:computer': True}, +} + +assert commands.getBlockInfo(*xyz) == expected_binfo +assert commands.getBlockInfos(*xyz, *xyz) == [expected_binfo] + +cmdlist = commands.list() + +assert len(cmdlist) > 0 +for c in cmdlist: + assert isinstance(c, str) + +assert commands.exec('say Hello!') == (True, [], AnyInstanceOf(int)) + +d = commands.exec('tp hajejndlasksdkelefsns fjeklaskslekffjslas') +assert d[0] is False + +d = commands.exec('difficulty') +assert d[0] is True +assert len(d[1]) == 1 +assert d[1][0].startswith('The difficulty is ') +assert isinstance(d[2], int) + +print('Test finished successfully') +S65:T<1>1<13>io.write(...){:[1]<33>Run this test on command computer} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<29>G:commands:M:getBlockPosition{} +R42:T<1>1<33>{:[1]T:[2][-12]:[3][70]:[4][-19]} +S62:T<1>1<25>G:commands:M:getBlockInfo{:[1][-12]:[2][70]:[3][-19]} +R272:T<1>1<262>{:[1]T:[2]{:<5>state{:<5>state<8>blinking:<6>facing<5>south}:<3>nbt{:<1>y[70]:<1>x[-12]:<10>ComputerId[5]:<9>ForgeCaps{}:<2>id<30>computercraft:computer_command:<2>On[1]:<1>z[-19]}:<4>name<30>computercraft:computer_command:<4>tags{:<22>computercraft:computerT}}} +S89:T<1>1<26>G:commands:M:getBlockInfos{:[1][-12]:[2][70]:[3][-19]:[4][-12]:[5][70]:[6][-19]} +R272:T<1>1<262>{:[1]T:[2]{:[1]{:<5>state{:<5>state<2>on:<6>facing<5>south}:<3>nbt{:<1>y[70]:<1>x[-12]:<10>ComputerId[5]:<9>ForgeCaps{}:<2>id<30>computercraft:computer_command:<2>On[1]:<1>z[-19]}:<4>name<30>computercraft:computer_command:<4>tags{:<22>computercraft:computerT}}}} +S28:T<1>1<17>G:commands:M:list{} +R1021:T<1>1<1010>{:[1]T:[2]{:[1]<11>advancement:[2]<9>attribute:[3]<7>execute:[4]<7>bossbar:[5]<5>clear:[6]<5>clone:[7]<6>damage:[8]<4>data:[9]<8>datapack:[10]<5>debug:[11]<15>defaultgamemode:[12]<10>difficulty:[13]<6>effect:[14]<2>me:[15]<7>enchant:[16]<10>experience:[17]<2>xp:[18]<4>fill:[19]<9>fillbiome:[20]<9>forceload:[21]<8>function:[22]<8>gamemode:[23]<8>gamerule:[24]<4>give:[25]<4>help:[26]<4>item:[27]<4>kick:[28]<4>kill:[29]<4>list:[30]<6>locate:[31]<4>loot:[32]<3>msg:[33]<4>tell:[34]<1>w:[35]<8>particle:[36]<5>place:[37]<9>playsound:[38]<6>reload:[39]<6>recipe:[40]<6>return:[41]<4>ride:[42]<3>say:[43]<8>schedule:[44]<10>scoreboard:[45]<4>seed:[46]<8>setblock:[47]<10>spawnpoint:[48]<13>setworldspawn:[49]<8>spectate:[50]<13>spreadplayers:[51]<9>stopsound:[52]<6>summon:[53]<3>tag:[54]<4>team:[55]<7>teammsg:[56]<2>tm:[57]<8>teleport:[58]<2>tp:[59]<7>tellraw:[60]<4>time:[61]<5>title:[62]<7>trigger:[63]<7>weather:[64]<11>worldborder:[65]<3>jfr:[66]<7>publish:[67]<13>computercraft:[68]<5>forge:[69]<6>config}} +S46:T<1>1<17>G:commands:M:exec{:[1]<10>say Hello!} +R34:T<1>1<25>{:[1]T:[2]T:[3]{}:[4][1]} +S80:T<1>1<17>G:commands:M:exec{:[1]<44>tp hajejndlasksdkelefsns fjeklaskslekffjslas} +R134:T<1>1<124>{:[1]T:[2]F:[3]{:[1]<30>Incorrect argument for command:[2]<53>tp hajejndlasksdkelefsns fjeklaskslekffjslas<--[HERE]}:[4][0]} +S46:T<1>1<17>G:commands:M:exec{:[1]<10>difficulty} +R66:T<1>1<57>{:[1]T:[2]T:[3]{:[1]<24>The difficulty is Normal}:[4][2]} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/disk.txt b/tests/proto/m_1.20.1/cc_1.108.3/disk.txt new file mode 100644 index 0000000..dbecc8f --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/disk.txt @@ -0,0 +1,227 @@ +R2585:0[5]{:[1]<7>disk.py:[0]<2>py}<7>disk.py<2540>from contextlib import contextmanager +from cc import LuaException, disk + + +def step(text): + input(f'{text} [enter]') + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +s = 'right' + +step(f'Make sure there is no disk drive at {s} side') + +assert disk.isPresent(s) is False +assert disk.hasData(s) is False +assert disk.getMountPath(s) is None +assert disk.setLabel(s, 'text') is None +assert disk.getLabel(s) is None +assert disk.getID(s) is None +assert disk.hasAudio(s) is False +assert disk.getAudioTitle(s) is None +assert disk.playAudio(s) is None +assert disk.stopAudio(s) is None +assert disk.eject(s) is None + +step(f'Place empty disk drive at {s} side') + +assert disk.isPresent(s) is False +assert disk.hasData(s) is False +assert disk.getMountPath(s) is None +assert disk.setLabel(s, 'text') is None +assert disk.getLabel(s) is None +assert disk.getID(s) is None +assert disk.hasAudio(s) is False +assert disk.getAudioTitle(s) is None +assert disk.playAudio(s) is None +assert disk.stopAudio(s) is None +assert disk.eject(s) is None + +step('Put new CC diskette into disk drive') + +assert disk.isPresent(s) is True +assert disk.hasData(s) is True +assert isinstance(disk.getMountPath(s), str) +assert isinstance(disk.getID(s), int) + +assert disk.getLabel(s) is None +assert disk.setLabel(s, 'label') is None +assert disk.getLabel(s) == 'label' +assert disk.setLabel(s, None) is None +assert disk.getLabel(s) is None + +assert disk.hasAudio(s) is False +assert disk.getAudioTitle(s) is None +assert disk.playAudio(s) is None +assert disk.stopAudio(s) is None + +assert disk.eject(s) is None + +step('Put any audio disk into disk drive') + +assert disk.isPresent(s) is True +assert disk.hasData(s) is False +assert disk.getMountPath(s) is None +assert disk.getID(s) is None +assert disk.hasAudio(s) is True + +label = disk.getAudioTitle(s) +assert isinstance(label, str) +assert label != 'label' +print(f'Label is {label}') +assert disk.getLabel(s) == label +with assert_raises(LuaException): + assert disk.setLabel(s, 'label') is None +with assert_raises(LuaException): + assert disk.setLabel(s, None) is None +# no effect +assert disk.getLabel(s) == label + +assert disk.playAudio(s) is None + +step('Audio must be playing now') + +assert disk.stopAudio(s) is None +assert disk.eject(s) is None + +print('Test finished successfully') +S86:T<1>1<13>io.write(...){:[1]<54>Make sure there is no disk drive at right side [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S41:T<1>1<18>G:disk:M:isPresent{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S39:T<1>1<16>G:disk:M:hasData{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S44:T<1>1<21>G:disk:M:getMountPath{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S51:T<1>1<17>G:disk:M:setLabel{:[1]<5>right:[2]<4>text} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:getID{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:hasAudio{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S45:T<1>1<22>G:disk:M:getAudioTitle{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:disk:M:playAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:disk:M:stopAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:eject{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<13>io.write(...){:[1]<44>Place empty disk drive at right side [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S41:T<1>1<18>G:disk:M:isPresent{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S39:T<1>1<16>G:disk:M:hasData{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S44:T<1>1<21>G:disk:M:getMountPath{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S51:T<1>1<17>G:disk:M:setLabel{:[1]<5>right:[2]<4>text} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:getID{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:hasAudio{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S45:T<1>1<22>G:disk:M:getAudioTitle{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:disk:M:playAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:disk:M:stopAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:eject{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<13>io.write(...){:[1]<43>Put new CC diskette into disk drive [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S41:T<1>1<18>G:disk:M:isPresent{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<16>G:disk:M:hasData{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]T} +S44:T<1>1<21>G:disk:M:getMountPath{:[1]<5>right} +R27:T<1>1<18>{:[1]T:[2]<4>disk} +S37:T<1>1<14>G:disk:M:getID{:[1]<5>right} +R23:T<1>1<14>{:[1]T:[2][0]} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S52:T<1>1<17>G:disk:M:setLabel{:[1]<5>right:[2]<5>label} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R28:T<1>1<19>{:[1]T:[2]<5>label} +S45:T<1>1<17>G:disk:M:setLabel{:[1]<5>right:[2]N} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:hasAudio{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S45:T<1>1<22>G:disk:M:getAudioTitle{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:disk:M:playAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:disk:M:stopAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:eject{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S74:T<1>1<13>io.write(...){:[1]<42>Put any audio disk into disk drive [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S41:T<1>1<18>G:disk:M:isPresent{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<16>G:disk:M:hasData{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]F} +S44:T<1>1<21>G:disk:M:getMountPath{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:getID{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:hasAudio{:[1]<5>right} +R21:T<1>1<12>{:[1]T:[2]T} +S45:T<1>1<22>G:disk:M:getAudioTitle{:[1]<5>right} +R36:T<1>1<27>{:[1]T:[2]<12>C418 - strad} +S53:T<1>1<13>io.write(...){:[1]<21>Label is C418 - strad} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R36:T<1>1<27>{:[1]T:[2]<12>C418 - strad} +S52:T<1>1<17>G:disk:M:setLabel{:[1]<5>right:[2]<5>label} +R75:T<1>1<66>{:[1]F:[2]<51>/rom/apis/disk.lua:62: Disk label cannot be changed} +S45:T<1>1<17>G:disk:M:setLabel{:[1]<5>right:[2]N} +R75:T<1>1<66>{:[1]F:[2]<51>/rom/apis/disk.lua:62: Disk label cannot be changed} +S40:T<1>1<17>G:disk:M:getLabel{:[1]<5>right} +R36:T<1>1<27>{:[1]T:[2]<12>C418 - strad} +S41:T<1>1<18>G:disk:M:playAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<13>io.write(...){:[1]<33>Audio must be playing now [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S41:T<1>1<18>G:disk:M:stopAudio{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:disk:M:eject{:[1]<5>right} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/fs.txt b/tests/proto/m_1.20.1/cc_1.108.3/fs.txt new file mode 100644 index 0000000..ce6750a --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/fs.txt @@ -0,0 +1,531 @@ +R6975:0[5]{:[1]<5>fs.py:[0]<2>py}<5>fs.py<6934>from contextlib import contextmanager +from cc import LuaException, fs + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +for name in ('tdir', 'tfile'): + if fs.exists(name): + fs.delete(name) + +assert fs.makeDir('tdir') is None +with fs.open('tfile', 'w') as f: + f.writeLine('textline') + +dlist = set(fs.list('.')) + +assert {'tdir', 'tfile', 'rom'}.issubset(dlist) +assert fs.list('tdir') == [] + +capacity = fs.getCapacity('.') +free = fs.getFreeSpace('.') +assert isinstance(capacity, int) +assert isinstance(free, int) +assert free < capacity +assert free > 0 +assert capacity > 0 + +assert fs.exists('tdir') is True +assert fs.exists('tfile') is True +assert fs.exists('doesnotexist') is False + +assert fs.isDir('tdir') is True +assert fs.isDir('tfile') is False +assert fs.isDir('doesnotexist') is False + +assert fs.isReadOnly('rom') is True +assert fs.isReadOnly('tdir') is False +assert fs.isReadOnly('tfile') is False +assert fs.isReadOnly('doesnotexist') is False + +assert fs.getDrive('rom') == 'rom' +assert fs.getDrive('tdir') == 'hdd' +assert fs.getDrive('tfile') == 'hdd' +assert fs.getDrive('doesnotexist') is None + +assert fs.isDriveRoot('/') is True +assert fs.isDriveRoot('rom') is True +assert fs.isDriveRoot('tdir') is False +assert fs.isDriveRoot('tfile') is False +assert fs.isDriveRoot('doesnotexist') is True # wtf? + +assert fs.getName('a/b/c/d') == 'd' +assert fs.getName('a/b/c/') == 'c' +assert fs.getName('/a/b/c/d') == 'd' +assert fs.getName('///a/b/c/d') == 'd' +assert fs.getName('') == 'root' # wtf? +assert fs.getName('/') == 'root' +assert fs.getName('///') == 'root' +assert fs.getName('.') == 'root' +assert fs.getName('..') == '..' +assert fs.getName('../../..') == '..' + +assert fs.getDir('a/b/c/d') == 'a/b/c' +assert fs.getDir('a/b/c/') == 'a/b' +assert fs.getDir('/a/b/c/d') == 'a/b/c' +assert fs.getDir('///a/b/c/d') == 'a/b/c' +assert fs.getDir('') == '..' +assert fs.getDir('/') == '..' +assert fs.getDir('///') == '..' +assert fs.getDir('.') == '..' +assert fs.getDir('..') == '' +assert fs.getDir('../../..') == '../..' + +assert fs.combine('a', 'b') == 'a/b' +assert fs.combine('a/', 'b') == 'a/b' +assert fs.combine('a//', 'b') == 'a/b' +assert fs.combine('a/', '/b') == 'a/b' +assert fs.combine('a/b/c', '..') == 'a/b' +assert fs.combine('a/b/c', '../..') == 'a' +assert fs.combine('a/b/c', '../../..') == '' +assert fs.combine('a/b/c', '../../../..') == '..' +assert fs.combine('a/b/c', '../../../../..') == '../..' +assert fs.combine('/a/b/c', '../../../../..') == '../..' +assert fs.combine('a/b/c', '////') == 'a/b/c' +assert fs.combine('a/b/c', '.') == 'a/b/c' +assert fs.combine('a/b/c', './.') == 'a/b/c' +assert fs.combine('a/b/c', './../.') == 'a/b' + +assert fs.getSize('tfile') == 9 +assert fs.getSize('tdir') == 0 +with assert_raises(LuaException): + fs.getSize('doesnotexist') + +assert fs.move('tfile', 'tdir/apple') is None +assert fs.list('tdir') == ['apple'] +assert fs.copy('tdir/apple', 'tdir/banana') is None +assert fs.list('tdir/') == ['apple', 'banana'] +assert fs.copy('tdir/apple', 'tdir/cherry') is None + +assert fs.getSize('tdir') == 0 + +dlist = set(fs.find('*')) +assert 'tdir' in dlist +assert 'rom' in dlist +assert 'tfile' not in dlist +assert 'tdir/apple' not in dlist + +dlist = set(fs.find('tdir/*')) +assert dlist == {'tdir/apple', 'tdir/banana', 'tdir/cherry'} + +dlist = set(fs.find('tdir/*a*')) +assert dlist == {'tdir/apple', 'tdir/banana'} + +dlist = set(fs.find('**')) +assert 'tdir' in dlist +assert 'tdir/apple' not in dlist # not recursive + +dlist = set(fs.list('')) +assert 'tfile' not in dlist +assert 'tdir' in dlist +assert 'rom' in dlist + +dlist = set(fs.list('tdir')) +assert dlist == {'apple', 'banana', 'cherry'} + +assert fs.attributes('tdir/banana') == { + 'created': AnyInstanceOf(int), + 'modified': AnyInstanceOf(int), + 'modification': AnyInstanceOf(int), + 'isReadOnly': False, + 'isDir': False, + 'size': 9, +} +assert fs.attributes('tdir') == { + 'created': AnyInstanceOf(int), + 'modified': AnyInstanceOf(int), + 'modification': AnyInstanceOf(int), + 'isReadOnly': False, + 'isDir': True, + 'size': 0, +} +with assert_raises(LuaException): + fs.attributes('doesnotexist') + +assert fs.complete('ba', 'tdir') == ['nana'] +assert fs.complete('ap', 'tdir') == ['ple'] +assert fs.complete('c', 'tdir') == ['herry'] +assert fs.complete('td', '') == ['ir/', 'ir'] +assert fs.complete('td', '', includeDirs=True) == ['ir/', 'ir'] +assert fs.complete('td', '', includeDirs=False) == ['ir/'] # wtf? +assert fs.complete('ap', 'tdir', includeFiles=True) == ['ple'] +assert fs.complete('ap', 'tdir', includeFiles=False) == [] + +assert fs.getSize('tdir/banana') == 9 +with fs.open('tdir/banana', 'r') as f: + assert f.read(4) == 'text' + assert f.readLine() == 'line' + assert f.read(1) is None + assert f.readLine() is None + assert f.readAll() is None + assert f.readAll() is None +assert fs.getSize('tdir/banana') == 9 +with fs.open('tdir/banana', 'a') as f: + assert f.write('x') is None +assert fs.getSize('tdir/banana') == 10 +with fs.open('tdir/banana', 'w') as f: + pass +assert fs.getSize('tdir/banana') == 0 # truncate +with fs.open('tdir/banana', 'w') as f: + assert f.write('Bro') is None + assert f.writeLine('wn fox jumps') is None + # assert fs.getSize('tdir/banana') == 0 # changes are not on a disk + assert f.flush() is None + assert fs.getSize('tdir/banana') == len('Brown fox jumps\n') + assert f.write('ov') is None + assert f.write('er ') is None + assert f.write('a lazy') is None + assert f.writeLine(' ???.') is None # supports unicode! +assert fs.getSize('tdir/banana') > 9 +with fs.open('tdir/banana', 'r') as f: + assert f.readAll() == 'Brown fox jumps\nover a lazy ???.\n' +with assert_raises(LuaException): + with fs.open('tdir/banana', 'rw') as f: + pass + +assert fs.exists('tdir/banana') is True + +with fs.open('tdir/binfile', 'wb') as f: + assert f.write(b'a' * 9) is None + assert f.seek() == 9 + assert f.seek('set', 0) == 0 + assert f.write(b'b' * 3) is None + assert f.seek('cur', -1) == 2 + assert f.write(b'c' * 3) is None + assert f.seek('end') == 9 + assert f.write(b'd' * 3) is None + with assert_raises(LuaException): + f.seek('set', -10) + +with fs.open('tdir/binfile', 'rb') as f: + assert f.readAll() == b'bbcccaaaaddd' + +with fs.open('tdir/binfile', 'r') as f: + assert [line for line in f] == ['bbcccaaaaddd'] + +assert fs.delete('tdir') is None +assert fs.delete('tfile') is None +assert fs.delete('doesnotexist') is None + +assert fs.exists('tdir/banana') is False + +print('Test finished successfully') +S35:T<1>1<13>G:fs:M:exists{:[1]<4>tdir} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<13>G:fs:M:delete{:[1]<4>tdir} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<13>G:fs:M:exists{:[1]<5>tfile} +R21:T<1>1<12>{:[1]T:[2]F} +S36:T<1>1<14>G:fs:M:makeDir{:[1]<4>tdir} +R15:T<1>1<7>{:[1]T} +S142:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>2:[2]<5>tfile:[3]<2>wb} +R21:T<1>1<12>{:[1]T:[2]T} +S92:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>2:[2]<9>textline +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>2} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<11>G:fs:M:list{:[1]<1>.} +R157:T<1>1<147>{:[1]T:[2]{:[1]<9>colors.py:[2]<7>disk.py:[3]<5>fs.py:[4]<13>helloworld.py:[5]<7>keys.py:[6]<2>py:[7]<3>rom:[8]<4>tdir:[9]<7>term.py:[10]<5>tfile}} +S33:T<1>1<11>G:fs:M:list{:[1]<4>tdir} +R22:T<1>1<13>{:[1]T:[2]{}} +S37:T<1>1<18>G:fs:M:getCapacity{:[1]<1>.} +R29:T<1>1<20>{:[1]T:[2][1000000]} +S38:T<1>1<19>G:fs:M:getFreeSpace{:[1]<1>.} +R28:T<1>1<19>{:[1]T:[2][979148]} +S35:T<1>1<13>G:fs:M:exists{:[1]<4>tdir} +R21:T<1>1<12>{:[1]T:[2]T} +S36:T<1>1<13>G:fs:M:exists{:[1]<5>tfile} +R21:T<1>1<12>{:[1]T:[2]T} +S44:T<1>1<13>G:fs:M:exists{:[1]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]F} +S34:T<1>1<12>G:fs:M:isDir{:[1]<4>tdir} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<12>G:fs:M:isDir{:[1]<5>tfile} +R21:T<1>1<12>{:[1]T:[2]F} +S43:T<1>1<12>G:fs:M:isDir{:[1]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]F} +S38:T<1>1<17>G:fs:M:isReadOnly{:[1]<3>rom} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<17>G:fs:M:isReadOnly{:[1]<4>tdir} +R21:T<1>1<12>{:[1]T:[2]F} +S40:T<1>1<17>G:fs:M:isReadOnly{:[1]<5>tfile} +R21:T<1>1<12>{:[1]T:[2]F} +S48:T<1>1<17>G:fs:M:isReadOnly{:[1]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]F} +S36:T<1>1<15>G:fs:M:getDrive{:[1]<3>rom} +R26:T<1>1<17>{:[1]T:[2]<3>rom} +S37:T<1>1<15>G:fs:M:getDrive{:[1]<4>tdir} +R26:T<1>1<17>{:[1]T:[2]<3>hdd} +S38:T<1>1<15>G:fs:M:getDrive{:[1]<5>tfile} +R26:T<1>1<17>{:[1]T:[2]<3>hdd} +S46:T<1>1<15>G:fs:M:getDrive{:[1]<12>doesnotexist} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<18>G:fs:M:isDriveRoot{:[1]<1>/} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<18>G:fs:M:isDriveRoot{:[1]<3>rom} +R21:T<1>1<12>{:[1]T:[2]T} +S40:T<1>1<18>G:fs:M:isDriveRoot{:[1]<4>tdir} +R21:T<1>1<12>{:[1]T:[2]F} +S41:T<1>1<18>G:fs:M:isDriveRoot{:[1]<5>tfile} +R21:T<1>1<12>{:[1]T:[2]F} +S49:T<1>1<18>G:fs:M:isDriveRoot{:[1]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<14>G:fs:M:getName{:[1]<7>a/b/c/d} +R24:T<1>1<15>{:[1]T:[2]<1>d} +S38:T<1>1<14>G:fs:M:getName{:[1]<6>a/b/c/} +R24:T<1>1<15>{:[1]T:[2]<1>c} +S40:T<1>1<14>G:fs:M:getName{:[1]<8>/a/b/c/d} +R24:T<1>1<15>{:[1]T:[2]<1>d} +S43:T<1>1<14>G:fs:M:getName{:[1]<10>///a/b/c/d} +R24:T<1>1<15>{:[1]T:[2]<1>d} +S32:T<1>1<14>G:fs:M:getName{:[1]<0>} +R27:T<1>1<18>{:[1]T:[2]<4>root} +S33:T<1>1<14>G:fs:M:getName{:[1]<1>/} +R27:T<1>1<18>{:[1]T:[2]<4>root} +S35:T<1>1<14>G:fs:M:getName{:[1]<3>///} +R27:T<1>1<18>{:[1]T:[2]<4>root} +S33:T<1>1<14>G:fs:M:getName{:[1]<1>.} +R27:T<1>1<18>{:[1]T:[2]<4>root} +S34:T<1>1<14>G:fs:M:getName{:[1]<2>..} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S40:T<1>1<14>G:fs:M:getName{:[1]<8>../../..} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S38:T<1>1<13>G:fs:M:getDir{:[1]<7>a/b/c/d} +R28:T<1>1<19>{:[1]T:[2]<5>a/b/c} +S37:T<1>1<13>G:fs:M:getDir{:[1]<6>a/b/c/} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S39:T<1>1<13>G:fs:M:getDir{:[1]<8>/a/b/c/d} +R28:T<1>1<19>{:[1]T:[2]<5>a/b/c} +S42:T<1>1<13>G:fs:M:getDir{:[1]<10>///a/b/c/d} +R28:T<1>1<19>{:[1]T:[2]<5>a/b/c} +S31:T<1>1<13>G:fs:M:getDir{:[1]<0>} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S32:T<1>1<13>G:fs:M:getDir{:[1]<1>/} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S34:T<1>1<13>G:fs:M:getDir{:[1]<3>///} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S32:T<1>1<13>G:fs:M:getDir{:[1]<1>.} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S33:T<1>1<13>G:fs:M:getDir{:[1]<2>..} +R23:T<1>1<14>{:[1]T:[2]<0>} +S39:T<1>1<13>G:fs:M:getDir{:[1]<8>../../..} +R28:T<1>1<19>{:[1]T:[2]<5>../..} +S41:T<1>1<14>G:fs:M:combine{:[1]<1>a:[2]<1>b} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S42:T<1>1<14>G:fs:M:combine{:[1]<2>a/:[2]<1>b} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S43:T<1>1<14>G:fs:M:combine{:[1]<3>a//:[2]<1>b} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S43:T<1>1<14>G:fs:M:combine{:[1]<2>a/:[2]<2>/b} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S46:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<2>..} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S49:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<5>../..} +R24:T<1>1<15>{:[1]T:[2]<1>a} +S52:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<8>../../..} +R23:T<1>1<14>{:[1]T:[2]<0>} +S56:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<11>../../../..} +R25:T<1>1<16>{:[1]T:[2]<2>..} +S59:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<14>../../../../..} +R28:T<1>1<19>{:[1]T:[2]<5>../..} +S60:T<1>1<14>G:fs:M:combine{:[1]<6>/a/b/c:[2]<14>../../../../..} +R28:T<1>1<19>{:[1]T:[2]<5>../..} +S48:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<4>////} +R28:T<1>1<19>{:[1]T:[2]<5>a/b/c} +S45:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<1>.} +R28:T<1>1<19>{:[1]T:[2]<5>a/b/c} +S47:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<3>./.} +R28:T<1>1<19>{:[1]T:[2]<5>a/b/c} +S50:T<1>1<14>G:fs:M:combine{:[1]<5>a/b/c:[2]<6>./../.} +R26:T<1>1<17>{:[1]T:[2]<3>a/b} +S37:T<1>1<14>G:fs:M:getSize{:[1]<5>tfile} +R23:T<1>1<14>{:[1]T:[2][9]} +S36:T<1>1<14>G:fs:M:getSize{:[1]<4>tdir} +R23:T<1>1<14>{:[1]T:[2][0]} +S45:T<1>1<14>G:fs:M:getSize{:[1]<12>doesnotexist} +R51:T<1>1<42>{:[1]F:[2]<27>/doesnotexist: No such file} +S52:T<1>1<11>G:fs:M:move{:[1]<5>tfile:[2]<10>tdir/apple} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<11>G:fs:M:list{:[1]<4>tdir} +R34:T<1>1<25>{:[1]T:[2]{:[1]<5>apple}} +S59:T<1>1<11>G:fs:M:copy{:[1]<10>tdir/apple:[2]<11>tdir/banana} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<11>G:fs:M:list{:[1]<5>tdir/} +R47:T<1>1<38>{:[1]T:[2]{:[1]<5>apple:[2]<6>banana}} +S59:T<1>1<11>G:fs:M:copy{:[1]<10>tdir/apple:[2]<11>tdir/cherry} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<14>G:fs:M:getSize{:[1]<4>tdir} +R23:T<1>1<14>{:[1]T:[2][0]} +S30:T<1>1<11>G:fs:M:find{:[1]<1>*} +R144:T<1>1<134>{:[1]T:[2]{:[1]<9>colors.py:[2]<7>disk.py:[3]<5>fs.py:[4]<13>helloworld.py:[5]<7>keys.py:[6]<2>py:[7]<3>rom:[8]<4>tdir:[9]<7>term.py}} +S35:T<1>1<11>G:fs:M:find{:[1]<6>tdir/*} +R78:T<1>1<69>{:[1]T:[2]{:[1]<10>tdir/apple:[2]<11>tdir/banana:[3]<11>tdir/cherry}} +S37:T<1>1<11>G:fs:M:find{:[1]<8>tdir/*a*} +R59:T<1>1<50>{:[1]T:[2]{:[1]<10>tdir/apple:[2]<11>tdir/banana}} +S31:T<1>1<11>G:fs:M:find{:[1]<2>**} +R144:T<1>1<134>{:[1]T:[2]{:[1]<9>colors.py:[2]<7>disk.py:[3]<5>fs.py:[4]<13>helloworld.py:[5]<7>keys.py:[6]<2>py:[7]<3>rom:[8]<4>tdir:[9]<7>term.py}} +S29:T<1>1<11>G:fs:M:list{:[1]<0>} +R144:T<1>1<134>{:[1]T:[2]{:[1]<9>colors.py:[2]<7>disk.py:[3]<5>fs.py:[4]<13>helloworld.py:[5]<7>keys.py:[6]<2>py:[7]<3>rom:[8]<4>tdir:[9]<7>term.py}} +S33:T<1>1<11>G:fs:M:list{:[1]<4>tdir} +R60:T<1>1<51>{:[1]T:[2]{:[1]<5>apple:[2]<6>banana:[3]<6>cherry}} +S47:T<1>1<17>G:fs:M:attributes{:[1]<11>tdir/banana} +R145:T<1>1<135>{:[1]T:[2]{:<7>created[1699216918044]:<10>isReadOnlyF:<8>modified[1699216918044]:<12>modification[1699216918044]:<5>isDirF:<4>size[9]}} +S39:T<1>1<17>G:fs:M:attributes{:[1]<4>tdir} +R145:T<1>1<135>{:[1]T:[2]{:<7>created[1699216918048]:<10>isReadOnlyF:<8>modified[1699216918048]:<12>modification[1699216918048]:<5>isDirT:<4>size[0]}} +S48:T<1>1<17>G:fs:M:attributes{:[1]<12>doesnotexist} +R51:T<1>1<42>{:[1]F:[2]<27>/doesnotexist: No such file} +S56:T<1>1<15>G:fs:M:complete{:[1]<2>ba:[2]<4>tdir:[3]N:[4]N} +R33:T<1>1<24>{:[1]T:[2]{:[1]<4>nana}} +S56:T<1>1<15>G:fs:M:complete{:[1]<2>ap:[2]<4>tdir:[3]N:[4]N} +R32:T<1>1<23>{:[1]T:[2]{:[1]<3>ple}} +S55:T<1>1<15>G:fs:M:complete{:[1]<1>c:[2]<4>tdir:[3]N:[4]N} +R34:T<1>1<25>{:[1]T:[2]{:[1]<5>herry}} +S52:T<1>1<15>G:fs:M:complete{:[1]<2>td:[2]<0>:[3]N:[4]N} +R41:T<1>1<32>{:[1]T:[2]{:[1]<3>ir/:[2]<2>ir}} +S52:T<1>1<15>G:fs:M:complete{:[1]<2>td:[2]<0>:[3]N:[4]T} +R41:T<1>1<32>{:[1]T:[2]{:[1]<3>ir/:[2]<2>ir}} +S52:T<1>1<15>G:fs:M:complete{:[1]<2>td:[2]<0>:[3]N:[4]F} +R32:T<1>1<23>{:[1]T:[2]{:[1]<3>ir/}} +S56:T<1>1<15>G:fs:M:complete{:[1]<2>ap:[2]<4>tdir:[3]T:[4]N} +R32:T<1>1<23>{:[1]T:[2]{:[1]<3>ple}} +S56:T<1>1<15>G:fs:M:complete{:[1]<2>ap:[2]<4>tdir:[3]F:[4]N} +R22:T<1>1<13>{:[1]T:[2]{}} +S44:T<1>1<14>G:fs:M:getSize{:[1]<11>tdir/banana} +R23:T<1>1<14>{:[1]T:[2][9]} +S149:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>3:[2]<11>tdir/banana:[3]<2>rb} +R21:T<1>1<12>{:[1]T:[2]T} +S82:T<1>1<56>return(function(n,...)return temp[n].read(...);end)(...){:[1]<1>3:[2][4]} +R27:T<1>1<18>{:[1]T:[2]<4>text} +S84:T<1>1<60>return(function(n,...)return temp[n].readLine(...);end)(...){:[1]<1>3:[2]F} +R27:T<1>1<18>{:[1]T:[2]<4>line} +S82:T<1>1<56>return(function(n,...)return temp[n].read(...);end)(...){:[1]<1>3:[2][1]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<60>return(function(n,...)return temp[n].readLine(...);end)(...){:[1]<1>3:[2]F} +R15:T<1>1<7>{:[1]T} +S78:T<1>1<59>return(function(n,...)return temp[n].readAll(...);end)(...){:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S78:T<1>1<59>return(function(n,...)return temp[n].readAll(...);end)(...){:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<14>G:fs:M:getSize{:[1]<11>tdir/banana} +R23:T<1>1<14>{:[1]T:[2][9]} +S149:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>4:[2]<11>tdir/banana:[3]<2>ab} +R21:T<1>1<12>{:[1]T:[2]T} +S84:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>4:[2]<1>x} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>4} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<14>G:fs:M:getSize{:[1]<11>tdir/banana} +R24:T<1>1<15>{:[1]T:[2][10]} +S149:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>5:[2]<11>tdir/banana:[3]<2>wb} +R21:T<1>1<12>{:[1]T:[2]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>5} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<14>G:fs:M:getSize{:[1]<11>tdir/banana} +R23:T<1>1<14>{:[1]T:[2][0]} +S149:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>6:[2]<11>tdir/banana:[3]<2>wb} +R21:T<1>1<12>{:[1]T:[2]T} +S86:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>6:[2]<3>Bro} +R15:T<1>1<7>{:[1]T} +S97:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>6:[2]<13>wn fox jumps +} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<57>return(function(n,...)return temp[n].flush(...);end)(...){:[1]<1>6} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<14>G:fs:M:getSize{:[1]<11>tdir/banana} +R24:T<1>1<15>{:[1]T:[2][16]} +S85:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>6:[2]<2>ov} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>6:[2]<3>er } +R15:T<1>1<7>{:[1]T} +S89:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>6:[2]<6>a lazy} +R15:T<1>1<7>{:[1]T} +S89:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>6:[2]<6> ???. +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>6} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<14>G:fs:M:getSize{:[1]<11>tdir/banana} +R24:T<1>1<15>{:[1]T:[2][33]} +S149:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>7:[2]<11>tdir/banana:[3]<2>rb} +R21:T<1>1<12>{:[1]T:[2]T} +S78:T<1>1<59>return(function(n,...)return temp[n].readAll(...);end)(...){:[1]<1>7} +R57:T<1>1<48>{:[1]T:[2]<33>Brown fox jumps +over a lazy ???. +} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>7} +R15:T<1>1<7>{:[1]T} +S150:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>8:[2]<11>tdir/banana:[3]<3>rwb} +R71:T<1>1<62>{:[1]F:[2]<47>[string "return(functi..."]:1: Unsupported mode} +S43:T<1>1<13>G:fs:M:exists{:[1]<11>tdir/banana} +R21:T<1>1<12>{:[1]T:[2]T} +S150:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>9:[2]<12>tdir/binfile:[3]<2>wb} +R21:T<1>1<12>{:[1]T:[2]T} +S92:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>9:[2]<9>aaaaaaaaa} +R15:T<1>1<7>{:[1]T} +S85:T<1>1<56>return(function(n,...)return temp[n].seek(...);end)(...){:[1]<1>9:[2]N:[3]N} +R23:T<1>1<14>{:[1]T:[2][9]} +S92:T<1>1<56>return(function(n,...)return temp[n].seek(...);end)(...){:[1]<1>9:[2]<3>set:[3][0]} +R23:T<1>1<14>{:[1]T:[2][0]} +S86:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>9:[2]<3>bbb} +R15:T<1>1<7>{:[1]T} +S93:T<1>1<56>return(function(n,...)return temp[n].seek(...);end)(...){:[1]<1>9:[2]<3>cur:[3][-1]} +R23:T<1>1<14>{:[1]T:[2][2]} +S86:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>9:[2]<3>ccc} +R15:T<1>1<7>{:[1]T} +S90:T<1>1<56>return(function(n,...)return temp[n].seek(...);end)(...){:[1]<1>9:[2]<3>end:[3]N} +R23:T<1>1<14>{:[1]T:[2][9]} +S86:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>9:[2]<3>ddd} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<56>return(function(n,...)return temp[n].seek(...);end)(...){:[1]<1>9:[2]<3>set:[3][-10]} +R44:T<1>1<35>{:[1]T:[3]<20>Position is negative} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>9} +R15:T<1>1<7>{:[1]T} +S150:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>a:[2]<12>tdir/binfile:[3]<2>rb} +R21:T<1>1<12>{:[1]T:[2]T} +S78:T<1>1<59>return(function(n,...)return temp[n].readAll(...);end)(...){:[1]<1>a} +R36:T<1>1<27>{:[1]T:[2]<12>bbcccaaaaddd} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>a} +R15:T<1>1<7>{:[1]T} +S150:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>b:[2]<12>tdir/binfile:[3]<2>rb} +R21:T<1>1<12>{:[1]T:[2]T} +S84:T<1>1<60>return(function(n,...)return temp[n].readLine(...);end)(...){:[1]<1>b:[2]F} +R36:T<1>1<27>{:[1]T:[2]<12>bbcccaaaaddd} +S84:T<1>1<60>return(function(n,...)return temp[n].readLine(...);end)(...){:[1]<1>b:[2]F} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>b} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<13>G:fs:M:delete{:[1]<4>tdir} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<13>G:fs:M:delete{:[1]<5>tfile} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>G:fs:M:delete{:[1]<12>doesnotexist} +R15:T<1>1<7>{:[1]T} +S43:T<1>1<13>G:fs:M:exists{:[1]<11>tdir/banana} +R21:T<1>1<12>{:[1]T:[2]F} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/gps_basic_computer.txt b/tests/proto/m_1.20.1/cc_1.108.3/gps_basic_computer.txt new file mode 100644 index 0000000..9cfd5e9 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/gps_basic_computer.txt @@ -0,0 +1,43 @@ +R426:0[5]{:[1]<21>gps_basic_computer.py:[0]<2>py}<21>gps_basic_computer.py<352>from cc import gps + + +print('It must be impossible to gps locate on basic computer') +print('for this test to complete') + +assert gps.locate() is None + +input('Attach wireless modem to computer [enter]') + +assert gps.locate() is None +assert gps.locate(debug=True) is None +assert gps.locate(timeout=5, debug=True) is None + +print('Test finished successfully') +S85:T<1>1<13>io.write(...){:[1]<53>It must be impossible to gps locate on basic computer} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<13>io.write(...){:[1]<25>for this test to complete} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<14>G:gps:M:locate{:[1]N:[2]N} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<13>io.write(...){:[1]<41>Attach wireless modem to computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S35:T<1>1<14>G:gps:M:locate{:[1]N:[2]N} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<14>G:gps:M:locate{:[1]N:[2]T} +R15:T<1>1<7>{:[1]T} +S37:T<1>1<14>G:gps:M:locate{:[1][5]:[2]T} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/gps_command_computer.txt b/tests/proto/m_1.20.1/cc_1.108.3/gps_command_computer.txt new file mode 100644 index 0000000..2a4e24c --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/gps_command_computer.txt @@ -0,0 +1,32 @@ +R317:0[5]{:[1]<23>gps_command_computer.py:[0]<2>py}<23>gps_command_computer.py<239>from cc import gps + + +print('Run this test on command computer') +pos = gps.locate() +assert isinstance(pos, tuple) +assert len(pos) == 3 +assert all(isinstance(x, int) for x in pos) +print('Position is', pos) +print('Test finished successfully') +S65:T<1>1<13>io.write(...){:[1]<33>Run this test on command computer} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<14>G:gps:M:locate{:[1]N:[2]N} +R42:T<1>1<33>{:[1]T:[2][-12]:[3][70]:[4][-19]} +S43:T<1>1<13>io.write(...){:[1]<11>Position is} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<13>io.write(...){:[1]<14>(-12, 70, -19)} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/helloworld.txt b/tests/proto/m_1.20.1/cc_1.108.3/helloworld.txt new file mode 100644 index 0000000..63e5fe2 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/helloworld.txt @@ -0,0 +1,19 @@ +R132:0[5]{:[1]<13>helloworld.py:[2]<2>42:[3]<3>abc:[0]<2>py}<13>helloworld.py<56>print(args) +for _ in range(2): + print("Hello world!") +S72:T<1>1<13>io.write(...){:[1]<40>[b'py', b'helloworld.py', b'42', b'abc']} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>io.write(...){:[1]<12>Hello world!} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S44:T<1>1<13>io.write(...){:[1]<12>Hello world!} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/keys.txt b/tests/proto/m_1.20.1/cc_1.108.3/keys.txt new file mode 100644 index 0000000..49e1135 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/keys.txt @@ -0,0 +1,587 @@ +R585:0[5]{:[1]<7>keys.py:[0]<2>py}<7>keys.py<541>from cc import keys + +a = keys.getCode('a') +space = keys.getCode('space') +enter = keys.getCode('enter') +assert keys.getCode('doesnotexist') is None +assert keys.getCode('getName') is None +assert isinstance(a, int) +assert isinstance(space, int) +assert isinstance(enter, int) + +assert keys.getName(a) == 'a' +assert keys.getName(space) == 'space' +assert keys.getName(enter) == 'enter' + +ks = [] +for i in range(256): + n = keys.getName(i) + if n is not None: + ks.append(f'{i}:{n}') +print(' '.join(ks)) + +print('Test finished successfully') +S101:T<1>1<82> +local k = ... +if type(keys[k]) == 'number' then + return keys[k] +end +return nil{:[1]<1>a} +R24:T<1>1<15>{:[1]T:[2][65]} +S105:T<1>1<82> +local k = ... +if type(keys[k]) == 'number' then + return keys[k] +end +return nil{:[1]<5>space} +R24:T<1>1<15>{:[1]T:[2][32]} +S105:T<1>1<82> +local k = ... +if type(keys[k]) == 'number' then + return keys[k] +end +return nil{:[1]<5>enter} +R25:T<1>1<16>{:[1]T:[2][257]} +S113:T<1>1<82> +local k = ... +if type(keys[k]) == 'number' then + return keys[k] +end +return nil{:[1]<12>doesnotexist} +R15:T<1>1<7>{:[1]T} +S107:T<1>1<82> +local k = ... +if type(keys[k]) == 'number' then + return keys[k] +end +return nil{:[1]<7>getName} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][65]} +R24:T<1>1<15>{:[1]T:[2]<1>a} +S35:T<1>1<16>G:keys:M:getName{:[1][32]} +R28:T<1>1<19>{:[1]T:[2]<5>space} +S36:T<1>1<16>G:keys:M:getName{:[1][257]} +R28:T<1>1<19>{:[1]T:[2]<5>enter} +S34:T<1>1<16>G:keys:M:getName{:[1][0]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][1]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][2]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][3]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][4]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][5]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][6]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][7]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][8]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<16>G:keys:M:getName{:[1][9]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][10]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][11]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][12]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][13]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][14]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][15]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][16]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][17]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][18]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][19]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][20]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][21]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][22]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][23]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][24]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][25]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][26]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][27]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][28]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][29]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][30]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][31]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][32]} +R28:T<1>1<19>{:[1]T:[2]<5>space} +S35:T<1>1<16>G:keys:M:getName{:[1][33]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][34]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][35]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][36]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][37]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][38]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][39]} +R34:T<1>1<25>{:[1]T:[2]<10>apostrophe} +S35:T<1>1<16>G:keys:M:getName{:[1][40]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][41]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][42]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][43]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][44]} +R28:T<1>1<19>{:[1]T:[2]<5>comma} +S35:T<1>1<16>G:keys:M:getName{:[1][45]} +R28:T<1>1<19>{:[1]T:[2]<5>minus} +S35:T<1>1<16>G:keys:M:getName{:[1][46]} +R29:T<1>1<20>{:[1]T:[2]<6>period} +S35:T<1>1<16>G:keys:M:getName{:[1][47]} +R28:T<1>1<19>{:[1]T:[2]<5>slash} +S35:T<1>1<16>G:keys:M:getName{:[1][48]} +R27:T<1>1<18>{:[1]T:[2]<4>zero} +S35:T<1>1<16>G:keys:M:getName{:[1][49]} +R26:T<1>1<17>{:[1]T:[2]<3>one} +S35:T<1>1<16>G:keys:M:getName{:[1][50]} +R26:T<1>1<17>{:[1]T:[2]<3>two} +S35:T<1>1<16>G:keys:M:getName{:[1][51]} +R28:T<1>1<19>{:[1]T:[2]<5>three} +S35:T<1>1<16>G:keys:M:getName{:[1][52]} +R27:T<1>1<18>{:[1]T:[2]<4>four} +S35:T<1>1<16>G:keys:M:getName{:[1][53]} +R27:T<1>1<18>{:[1]T:[2]<4>five} +S35:T<1>1<16>G:keys:M:getName{:[1][54]} +R26:T<1>1<17>{:[1]T:[2]<3>six} +S35:T<1>1<16>G:keys:M:getName{:[1][55]} +R28:T<1>1<19>{:[1]T:[2]<5>seven} +S35:T<1>1<16>G:keys:M:getName{:[1][56]} +R28:T<1>1<19>{:[1]T:[2]<5>eight} +S35:T<1>1<16>G:keys:M:getName{:[1][57]} +R27:T<1>1<18>{:[1]T:[2]<4>nine} +S35:T<1>1<16>G:keys:M:getName{:[1][58]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][59]} +R32:T<1>1<23>{:[1]T:[2]<9>semicolon} +S35:T<1>1<16>G:keys:M:getName{:[1][60]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][61]} +R29:T<1>1<20>{:[1]T:[2]<6>equals} +S35:T<1>1<16>G:keys:M:getName{:[1][62]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][63]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][64]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][65]} +R24:T<1>1<15>{:[1]T:[2]<1>a} +S35:T<1>1<16>G:keys:M:getName{:[1][66]} +R24:T<1>1<15>{:[1]T:[2]<1>b} +S35:T<1>1<16>G:keys:M:getName{:[1][67]} +R24:T<1>1<15>{:[1]T:[2]<1>c} +S35:T<1>1<16>G:keys:M:getName{:[1][68]} +R24:T<1>1<15>{:[1]T:[2]<1>d} +S35:T<1>1<16>G:keys:M:getName{:[1][69]} +R24:T<1>1<15>{:[1]T:[2]<1>e} +S35:T<1>1<16>G:keys:M:getName{:[1][70]} +R24:T<1>1<15>{:[1]T:[2]<1>f} +S35:T<1>1<16>G:keys:M:getName{:[1][71]} +R24:T<1>1<15>{:[1]T:[2]<1>g} +S35:T<1>1<16>G:keys:M:getName{:[1][72]} +R24:T<1>1<15>{:[1]T:[2]<1>h} +S35:T<1>1<16>G:keys:M:getName{:[1][73]} +R24:T<1>1<15>{:[1]T:[2]<1>i} +S35:T<1>1<16>G:keys:M:getName{:[1][74]} +R24:T<1>1<15>{:[1]T:[2]<1>j} +S35:T<1>1<16>G:keys:M:getName{:[1][75]} +R24:T<1>1<15>{:[1]T:[2]<1>k} +S35:T<1>1<16>G:keys:M:getName{:[1][76]} +R24:T<1>1<15>{:[1]T:[2]<1>l} +S35:T<1>1<16>G:keys:M:getName{:[1][77]} +R24:T<1>1<15>{:[1]T:[2]<1>m} +S35:T<1>1<16>G:keys:M:getName{:[1][78]} +R24:T<1>1<15>{:[1]T:[2]<1>n} +S35:T<1>1<16>G:keys:M:getName{:[1][79]} +R24:T<1>1<15>{:[1]T:[2]<1>o} +S35:T<1>1<16>G:keys:M:getName{:[1][80]} +R24:T<1>1<15>{:[1]T:[2]<1>p} +S35:T<1>1<16>G:keys:M:getName{:[1][81]} +R24:T<1>1<15>{:[1]T:[2]<1>q} +S35:T<1>1<16>G:keys:M:getName{:[1][82]} +R24:T<1>1<15>{:[1]T:[2]<1>r} +S35:T<1>1<16>G:keys:M:getName{:[1][83]} +R24:T<1>1<15>{:[1]T:[2]<1>s} +S35:T<1>1<16>G:keys:M:getName{:[1][84]} +R24:T<1>1<15>{:[1]T:[2]<1>t} +S35:T<1>1<16>G:keys:M:getName{:[1][85]} +R24:T<1>1<15>{:[1]T:[2]<1>u} +S35:T<1>1<16>G:keys:M:getName{:[1][86]} +R24:T<1>1<15>{:[1]T:[2]<1>v} +S35:T<1>1<16>G:keys:M:getName{:[1][87]} +R24:T<1>1<15>{:[1]T:[2]<1>w} +S35:T<1>1<16>G:keys:M:getName{:[1][88]} +R24:T<1>1<15>{:[1]T:[2]<1>x} +S35:T<1>1<16>G:keys:M:getName{:[1][89]} +R24:T<1>1<15>{:[1]T:[2]<1>y} +S35:T<1>1<16>G:keys:M:getName{:[1][90]} +R24:T<1>1<15>{:[1]T:[2]<1>z} +S35:T<1>1<16>G:keys:M:getName{:[1][91]} +R35:T<1>1<26>{:[1]T:[2]<11>leftBracket} +S35:T<1>1<16>G:keys:M:getName{:[1][92]} +R32:T<1>1<23>{:[1]T:[2]<9>backslash} +S35:T<1>1<16>G:keys:M:getName{:[1][93]} +R36:T<1>1<27>{:[1]T:[2]<12>rightBracket} +S35:T<1>1<16>G:keys:M:getName{:[1][94]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][95]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][96]} +R28:T<1>1<19>{:[1]T:[2]<5>grave} +S35:T<1>1<16>G:keys:M:getName{:[1][97]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][98]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<16>G:keys:M:getName{:[1][99]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][100]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][101]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][102]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][103]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][104]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][105]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][106]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][107]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][108]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][109]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][110]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][111]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][112]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][113]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][114]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][115]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][116]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][117]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][118]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][119]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][120]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][121]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][122]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][123]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][124]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][125]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][126]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][127]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][128]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][129]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][130]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][131]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][132]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][133]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][134]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][135]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][136]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][137]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][138]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][139]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][140]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][141]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][142]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][143]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][144]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][145]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][146]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][147]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][148]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][149]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][150]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][151]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][152]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][153]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][154]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][155]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][156]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][157]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][158]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][159]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][160]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][161]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][162]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][163]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][164]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][165]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][166]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][167]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][168]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][169]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][170]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][171]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][172]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][173]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][174]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][175]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][176]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][177]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][178]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][179]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][180]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][181]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][182]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][183]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][184]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][185]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][186]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][187]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][188]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][189]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][190]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][191]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][192]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][193]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][194]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][195]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][196]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][197]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][198]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][199]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][200]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][201]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][202]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][203]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][204]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][205]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][206]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][207]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][208]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][209]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][210]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][211]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][212]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][213]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][214]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][215]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][216]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][217]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][218]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][219]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][220]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][221]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][222]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][223]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][224]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][225]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][226]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][227]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][228]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][229]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][230]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][231]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][232]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][233]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][234]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][235]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][236]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][237]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][238]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][239]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][240]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][241]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][242]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][243]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][244]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][245]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][246]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][247]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][248]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][249]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][250]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][251]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][252]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][253]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][254]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<16>G:keys:M:getName{:[1][255]} +R15:T<1>1<7>{:[1]T} +S378:T<1>1<13>io.write(...){:[1]<345>32:space 39:apostrophe 44:comma 45:minus 46:period 47:slash 48:zero 49:one 50:two 51:three 52:four 53:five 54:six 55:seven 56:eight 57:nine 59:semicolon 61:equals 65:a 66:b 67:c 68:d 69:e 70:f 71:g 72:h 73:i 74:j 75:k 76:l 77:m 78:n 79:o 80:p 81:q 82:r 83:s 84:t 85:u 86:v 87:w 88:x 89:y 90:z 91:leftBracket 92:backslash 93:rightBracket 96:grave} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/multishell.txt b/tests/proto/m_1.20.1/cc_1.108.3/multishell.txt new file mode 100644 index 0000000..0720eac --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/multishell.txt @@ -0,0 +1,154 @@ +R765:0[5]{:[1]<13>multishell.py:[0]<2>py}<13>multishell.py<707>from cc import import_file, multishell + +_lib = import_file('_lib.py', __file__) +_lib.step('Close all additional shells') + +assert multishell.getCount() == 1 +assert multishell.getCurrent() == 1 +assert multishell.getFocus() == 1 +assert isinstance(multishell.getTitle(1), str) + +for title in ['title a', 'title b']: + assert multishell.setTitle(1, title) is None + assert multishell.getTitle(1) == title + +assert multishell.setFocus(1) is True +assert multishell.setFocus(0) is False +assert multishell.setFocus(2) is False + +assert multishell.getTitle(2) is None + +assert multishell.launch({}, 'rom/programs/fun/hello.lua') == 2 +assert isinstance(multishell.getTitle(2), str) + +print('Test finished successfully') +S262:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<13>multishell.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S67:T<1>1<13>io.write(...){:[1]<35>Close all additional shells [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S34:T<1>1<23>G:multishell:M:getCount{} +R23:T<1>1<14>{:[1]T:[2][1]} +S36:T<1>1<25>G:multishell:M:getCurrent{} +R23:T<1>1<14>{:[1]T:[2][1]} +S34:T<1>1<23>G:multishell:M:getFocus{} +R23:T<1>1<14>{:[1]T:[2][1]} +S41:T<1>1<23>G:multishell:M:getTitle{:[1][1]} +R25:T<1>1<16>{:[1]T:[2]<2>py} +S55:T<1>1<23>G:multishell:M:setTitle{:[1][1]:[2]<7>title a} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<23>G:multishell:M:getTitle{:[1][1]} +R30:T<1>1<21>{:[1]T:[2]<7>title a} +S55:T<1>1<23>G:multishell:M:setTitle{:[1][1]:[2]<7>title b} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<23>G:multishell:M:getTitle{:[1][1]} +R30:T<1>1<21>{:[1]T:[2]<7>title b} +S41:T<1>1<23>G:multishell:M:setFocus{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S41:T<1>1<23>G:multishell:M:setFocus{:[1][0]} +R21:T<1>1<12>{:[1]T:[2]F} +S41:T<1>1<23>G:multishell:M:setFocus{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]F} +S41:T<1>1<23>G:multishell:M:getTitle{:[1][2]} +R15:T<1>1<7>{:[1]T} +S72:T<1>1<21>G:multishell:M:launch{:[1]{}:[2]<26>rom/programs/fun/hello.lua} +R23:T<1>1<14>{:[1]T:[2][2]} +S41:T<1>1<23>G:multishell:M:getTitle{:[1][2]} +R32:T<1>1<23>{:[1]T:[2]<9>hello.lua} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/os.txt b/tests/proto/m_1.20.1/cc_1.108.3/os.txt new file mode 100644 index 0000000..45a3822 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/os.txt @@ -0,0 +1,201 @@ +R1243:0[5]{:[1]<5>os.py:[0]<2>py}<5>os.py<1202>from cc import import_file, os + + +_lib = import_file('_lib.py', __file__) + + +with _lib.assert_takes_time(1.5, 3): + timer_id = os.startTimer(2) + for e in os.captureEvent('timer'): + if e[0] == timer_id: + print('Timer reached') + break + + +timer_id = os.startTimer(20) +assert isinstance(timer_id, int) +assert os.cancelTimer(timer_id) is None +assert os.cancelTimer(timer_id) is None + +alarm_id = os.setAlarm(0.0) +assert isinstance(alarm_id, int) +assert os.cancelAlarm(alarm_id) is None +assert os.cancelAlarm(alarm_id) is None + +with _lib.assert_takes_time(1.5, 3): + assert os.sleep(2) is None + +assert (os.version()).startswith('CraftOS ') +assert isinstance(os.getComputerID(), int) + +assert os.setComputerLabel(None) is None +assert os.getComputerLabel() is None +assert os.setComputerLabel('altair') is None +assert os.getComputerLabel() == 'altair' +assert os.setComputerLabel(None) is None +assert os.getComputerLabel() is None + +assert isinstance(os.epoch(), int) +assert isinstance(os.day(), int) +assert isinstance(os.time(), (int, float)) +assert isinstance(os.clock(), (int, float)) + +assert os.run({}, '/rom/programs/fun/hello.lua') is True + +print('Test finished successfully') +S253:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<5>os.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699223414263]} +S35:T<1>1<17>G:os:M:startTimer{:[1][2]} +R25:T<1>1<16>{:[1]T:[2][258]} +S9:S<5>timer +R20:E<5>timer{:[1][258]} +S45:T<1>1<13>io.write(...){:[1]<13>Timer reached} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S9:U<5>timer +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699223416241]} +S36:T<1>1<17>G:os:M:startTimer{:[1][20]} +R25:T<1>1<16>{:[1]T:[2][259]} +S38:T<1>1<18>G:os:M:cancelTimer{:[1][259]} +R15:T<1>1<7>{:[1]T} +S38:T<1>1<18>G:os:M:cancelTimer{:[1][259]} +R15:T<1>1<7>{:[1]T} +S35:T<1>1<15>G:os:M:setAlarm{:[1][0.0]} +R23:T<1>1<14>{:[1]T:[2][6]} +S36:T<1>1<18>G:os:M:cancelAlarm{:[1][6]} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<18>G:os:M:cancelAlarm{:[1][6]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699223416252]} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699223418239]} +S25:T<1>1<14>G:os:M:version{} +R35:T<1>1<26>{:[1]T:[2]<11>CraftOS 1.8} +S31:T<1>1<20>G:os:M:getComputerID{} +R23:T<1>1<14>{:[1]T:[2][1]} +S39:T<1>1<23>G:os:M:setComputerLabel{:[1]N} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>G:os:M:getComputerLabel{} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<23>G:os:M:setComputerLabel{:[1]<6>altair} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>G:os:M:getComputerLabel{} +R29:T<1>1<20>{:[1]T:[2]<6>altair} +S39:T<1>1<23>G:os:M:setComputerLabel{:[1]N} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>G:os:M:getComputerLabel{} +R15:T<1>1<7>{:[1]T} +S36:T<1>1<12>G:os:M:epoch{:[1]<6>ingame} +R31:T<1>1<22>{:[1]T:[2][109393200]} +S34:T<1>1<10>G:os:M:day{:[1]<6>ingame} +R23:T<1>1<14>{:[1]T:[2][1]} +S35:T<1>1<11>G:os:M:time{:[1]<6>ingame} +R27:T<1>1<18>{:[1]T:[2][6.387]} +S23:T<1>1<12>G:os:M:clock{} +R27:T<1>1<18>{:[1]T:[2][654.7]} +S62:T<1>1<10>G:os:M:run{:[1]{}:[2]<27>/rom/programs/fun/hello.lua} +R21:T<1>1<12>{:[1]T:[2]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/paintutils.txt b/tests/proto/m_1.20.1/cc_1.108.3/paintutils.txt new file mode 100644 index 0000000..70b8949 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/paintutils.txt @@ -0,0 +1,244 @@ +R2146:0[5]{:[1]<13>paintutils.py:[0]<2>py}<13>paintutils.py<2087>from cc import import_file, fs, os, term, colors, paintutils + +_lib = import_file('_lib.py', __file__) + + +pixels = ''' +0000000030030033333333330000000003000000000000000 +0333300000000033333333300000000000333333000000330 +0803000000803033333333000000000000880330300003000 +0800800030330333333333000300883000888880000033000 +3333000000003333333333300080038880000080000888003 +33333ddd3333333333333333300000333330000000000d033 +333dddddd3333333333333333333333333333333333ddd333 +3333ccdd333333333333344444444333333333333dddddd33 +333cc33d3333333333334444444444333333333335d3cc33d +5ddc33333333333333344444444444433333333333333cd55 +dddc555d3333333333344444444444433333333333d5dc5dd +d5dd5dd4bbbbbbbbb999b00b00300b3bb9999bbbb4ddddddd +ddd55444bb999993bbb33390b030bb9999bbbbbbb444ddddd +55dd44bbbbbbbbbbbbb9bb3003003bbb339bbbbbbbb44444d +dd444bbbbbbbbbbb99933bbb0030b999bbbbbbbbbbbbbbb44 +444bbbbbbbbbbbbbbb9bbb33b309933bbbbbbbbbbbbbbbbbb +bbbbbbbbbbbbbbbbbbbb9bbbb3bbbb99bbbbbbbbbbbbbbbbb +bbbbbbbbbbbbbbbbbbbbbb399399bbbbbbbbbbbbbbbbbbbbb +'''.strip() + + +with fs.open('img.nfp', 'w') as f: + f.write(pixels) + +int_pixels = paintutils.loadImage('img.nfp') +assert len(int_pixels) > 0 +assert len(int_pixels[0]) > 0 +assert paintutils.parseImage(pixels.encode('ascii')) == int_pixels + +assert paintutils.drawImage(int_pixels, 1, 1) is None + +os.sleep(2) + +term.setTextColor(colors.white) +term.setBackgroundColor(colors.black) +term.clear() +term.setBackgroundColor(colors.green) + +by = 3 +bx = 3 + +assert paintutils.drawPixel(bx, by) is None +assert paintutils.drawPixel(bx + 1, by, colors.red) is None + +bx += 5 + +assert paintutils.drawLine(bx, by, bx + 3, by + 3) is None +assert paintutils.drawLine(bx + 3, by, bx, by + 3, colors.red) is None + +bx += 5 +assert paintutils.drawBox(bx, by, bx + 3, by + 3) is None +bx += 5 +assert paintutils.drawBox(bx, by, bx + 3, by + 3, colors.red) is None + +bx += 5 +assert paintutils.drawFilledBox(bx, by, bx + 3, by + 3) is None +bx += 5 +assert paintutils.drawFilledBox(bx, by, bx + 3, by + 3, colors.red) is None + +term.setCursorPos(1, by + 6) + +os.sleep(2) + +print('Test finished successfully') +S262:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<13>paintutils.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S144:T<1>1<101>return(function(n,...)local o,e=fs.open(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>2:[2]<7>img.nfp:[3]<2>wb} +R21:T<1>1<12>{:[1]T:[2]T} +S984:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>2:[2]<899>0000000030030033333333330000000003000000000000000 +0333300000000033333333300000000000333333000000330 +0803000000803033333333000000000000880330300003000 +0800800030330333333333000300883000888880000033000 +3333000000003333333333300080038880000080000888003 +33333ddd3333333333333333300000333330000000000d033 +333dddddd3333333333333333333333333333333333ddd333 +3333ccdd333333333333344444444333333333333dddddd33 +333cc33d3333333333334444444444333333333335d3cc33d +5ddc33333333333333344444444444433333333333333cd55 +dddc555d3333333333344444444444433333333333d5dc5dd +d5dd5dd4bbbbbbbbb999b00b00300b3bb9999bbbb4ddddddd +ddd55444bb999993bbb33390b030bb9999bbbbbbb444ddddd +55dd44bbbbbbbbbbbbb9bb3003003bbb339bbbbbbbb44444d +dd444bbbbbbbbbbb99933bbb0030b999bbbbbbbbbbbbbbb44 +444bbbbbbbbbbbbbbb9bbb33b309933bbbbbbbbbbbbbbbbbb +bbbbbbbbbbbbbbbbbbbb9bbbb3bbbb99bbbbbbbbbbbbbbbbb +bbbbbbbbbbbbbbbbbbbbbb399399bbbbbbbbbbbbbbbbbbbbb} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<39>local n=...;temp[n].close();temp[n]=nil{:[1]<1>2} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<24>G:paintutils:M:loadImage{:[1]<7>img.nfp} +R8079:T<1>1<8068>{:[1]T:[2]{:[1]{:[1][1]:[2][1]:[3][1]:[4][1]:[5][1]:[6][1]:[7][1]:[8][1]:[9][8]:[10][1]:[11][1]:[12][8]:[13][1]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][8]:[35][1]:[36][1]:[37][1]:[38][1]:[39][1]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][1]:[47][1]:[48][1]:[49][1]}:[2]{:[1][1]:[2][8]:[3][8]:[4][8]:[5][8]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][1]:[12][1]:[13][1]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][1]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][1]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][1]:[47][8]:[48][8]:[49][1]}:[3]{:[1][1]:[2][256]:[3][1]:[4][8]:[5][1]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][256]:[12][1]:[13][8]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][1]:[24][1]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][1]:[35][256]:[36][256]:[37][1]:[38][8]:[39][8]:[40][1]:[41][8]:[42][1]:[43][1]:[44][1]:[45][1]:[46][8]:[47][1]:[48][1]:[49][1]}:[4]{:[1][1]:[2][256]:[3][1]:[4][1]:[5][256]:[6][1]:[7][1]:[8][1]:[9][8]:[10][1]:[11][8]:[12][8]:[13][1]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][1]:[24][1]:[25][1]:[26][8]:[27][1]:[28][1]:[29][256]:[30][256]:[31][8]:[32][1]:[33][1]:[34][1]:[35][256]:[36][256]:[37][256]:[38][256]:[39][256]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][8]:[46][8]:[47][1]:[48][1]:[49][1]}:[5]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][1]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][1]:[12][1]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][1]:[25][1]:[26][1]:[27][256]:[28][1]:[29][1]:[30][8]:[31][256]:[32][256]:[33][256]:[34][1]:[35][1]:[36][1]:[37][1]:[38][1]:[39][256]:[40][1]:[41][1]:[42][1]:[43][1]:[44][256]:[45][256]:[46][256]:[47][1]:[48][1]:[49][8]}:[6]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][8]:[6][8192]:[7][8192]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][8]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][1]:[37][1]:[38][1]:[39][1]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][8192]:[47][1]:[48][8]:[49][8]}:[7]{:[1][8]:[2][8]:[3][8]:[4][8192]:[5][8192]:[6][8192]:[7][8192]:[8][8192]:[9][8192]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][8]:[26][8]:[27][8]:[28][8]:[29][8]:[30][8]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8]:[44][8192]:[45][8192]:[46][8192]:[47][8]:[48][8]:[49][8]}:[8]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][4096]:[6][4096]:[7][8192]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][8]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8192]:[43][8192]:[44][8192]:[45][8192]:[46][8192]:[47][8192]:[48][8]:[49][8]}:[9]{:[1][8]:[2][8]:[3][8]:[4][4096]:[5][4096]:[6][8]:[7][8]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][32]:[43][8192]:[44][8]:[45][4096]:[46][4096]:[47][8]:[48][8]:[49][8192]}:[10]{:[1][32]:[2][8192]:[3][8192]:[4][4096]:[5][8]:[6][8]:[7][8]:[8][8]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][16]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][16]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8]:[44][8]:[45][8]:[46][4096]:[47][8192]:[48][32]:[49][32]}:[11]{:[1][8192]:[2][8192]:[3][8192]:[4][4096]:[5][32]:[6][32]:[7][32]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][16]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][16]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8192]:[44][32]:[45][8192]:[46][4096]:[47][32]:[48][8192]:[49][8192]}:[12]{:[1][8192]:[2][32]:[3][8192]:[4][8192]:[5][32]:[6][8192]:[7][8192]:[8][16]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][512]:[19][512]:[20][512]:[21][2048]:[22][1]:[23][1]:[24][2048]:[25][1]:[26][1]:[27][8]:[28][1]:[29][1]:[30][2048]:[31][8]:[32][2048]:[33][2048]:[34][512]:[35][512]:[36][512]:[37][512]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][16]:[43][8192]:[44][8192]:[45][8192]:[46][8192]:[47][8192]:[48][8192]:[49][8192]}:[13]{:[1][8192]:[2][8192]:[3][8192]:[4][32]:[5][32]:[6][16]:[7][16]:[8][16]:[9][2048]:[10][2048]:[11][512]:[12][512]:[13][512]:[14][512]:[15][512]:[16][8]:[17][2048]:[18][2048]:[19][2048]:[20][8]:[21][8]:[22][8]:[23][512]:[24][1]:[25][2048]:[26][1]:[27][8]:[28][1]:[29][2048]:[30][2048]:[31][512]:[32][512]:[33][512]:[34][512]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][16]:[43][16]:[44][16]:[45][8192]:[46][8192]:[47][8192]:[48][8192]:[49][8192]}:[14]{:[1][32]:[2][32]:[3][8192]:[4][8192]:[5][16]:[6][16]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][512]:[21][2048]:[22][2048]:[23][8]:[24][1]:[25][1]:[26][8]:[27][1]:[28][1]:[29][8]:[30][2048]:[31][2048]:[32][2048]:[33][8]:[34][8]:[35][512]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][16]:[45][16]:[46][16]:[47][16]:[48][16]:[49][8192]}:[15]{:[1][8192]:[2][8192]:[3][16]:[4][16]:[5][16]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][512]:[18][512]:[19][512]:[20][8]:[21][8]:[22][2048]:[23][2048]:[24][2048]:[25][1]:[26][1]:[27][8]:[28][1]:[29][2048]:[30][512]:[31][512]:[32][512]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][16]:[49][16]}:[16]{:[1][16]:[2][16]:[3][16]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][512]:[20][2048]:[21][2048]:[22][2048]:[23][8]:[24][8]:[25][2048]:[26][8]:[27][1]:[28][512]:[29][512]:[30][8]:[31][8]:[32][2048]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}:[17]{:[1][2048]:[2][2048]:[3][2048]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][2048]:[21][512]:[22][2048]:[23][2048]:[24][2048]:[25][2048]:[26][8]:[27][2048]:[28][2048]:[29][2048]:[30][2048]:[31][512]:[32][512]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}:[18]{:[1][2048]:[2][2048]:[3][2048]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][2048]:[21][2048]:[22][2048]:[23][8]:[24][512]:[25][512]:[26][8]:[27][512]:[28][512]:[29][2048]:[30][2048]:[31][2048]:[32][2048]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}}} +S944:T<1>1<25>G:paintutils:M:parseImage{:[1]<899>0000000030030033333333330000000003000000000000000 +0333300000000033333333300000000000333333000000330 +0803000000803033333333000000000000880330300003000 +0800800030330333333333000300883000888880000033000 +3333000000003333333333300080038880000080000888003 +33333ddd3333333333333333300000333330000000000d033 +333dddddd3333333333333333333333333333333333ddd333 +3333ccdd333333333333344444444333333333333dddddd33 +333cc33d3333333333334444444444333333333335d3cc33d +5ddc33333333333333344444444444433333333333333cd55 +dddc555d3333333333344444444444433333333333d5dc5dd +d5dd5dd4bbbbbbbbb999b00b00300b3bb9999bbbb4ddddddd +ddd55444bb999993bbb33390b030bb9999bbbbbbb444ddddd +55dd44bbbbbbbbbbbbb9bb3003003bbb339bbbbbbbb44444d +dd444bbbbbbbbbbb99933bbb0030b999bbbbbbbbbbbbbbb44 +444bbbbbbbbbbbbbbb9bbb33b309933bbbbbbbbbbbbbbbbbb +bbbbbbbbbbbbbbbbbbbb9bbbb3bbbb99bbbbbbbbbbbbbbbbb +bbbbbbbbbbbbbbbbbbbbbb399399bbbbbbbbbbbbbbbbbbbbb} +R8079:T<1>1<8068>{:[1]T:[2]{:[1]{:[1][1]:[2][1]:[3][1]:[4][1]:[5][1]:[6][1]:[7][1]:[8][1]:[9][8]:[10][1]:[11][1]:[12][8]:[13][1]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][8]:[35][1]:[36][1]:[37][1]:[38][1]:[39][1]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][1]:[47][1]:[48][1]:[49][1]}:[2]{:[1][1]:[2][8]:[3][8]:[4][8]:[5][8]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][1]:[12][1]:[13][1]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][1]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][1]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][1]:[47][8]:[48][8]:[49][1]}:[3]{:[1][1]:[2][256]:[3][1]:[4][8]:[5][1]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][256]:[12][1]:[13][8]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][1]:[24][1]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][1]:[35][256]:[36][256]:[37][1]:[38][8]:[39][8]:[40][1]:[41][8]:[42][1]:[43][1]:[44][1]:[45][1]:[46][8]:[47][1]:[48][1]:[49][1]}:[4]{:[1][1]:[2][256]:[3][1]:[4][1]:[5][256]:[6][1]:[7][1]:[8][1]:[9][8]:[10][1]:[11][8]:[12][8]:[13][1]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][1]:[24][1]:[25][1]:[26][8]:[27][1]:[28][1]:[29][256]:[30][256]:[31][8]:[32][1]:[33][1]:[34][1]:[35][256]:[36][256]:[37][256]:[38][256]:[39][256]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][8]:[46][8]:[47][1]:[48][1]:[49][1]}:[5]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][1]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][1]:[12][1]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][1]:[25][1]:[26][1]:[27][256]:[28][1]:[29][1]:[30][8]:[31][256]:[32][256]:[33][256]:[34][1]:[35][1]:[36][1]:[37][1]:[38][1]:[39][256]:[40][1]:[41][1]:[42][1]:[43][1]:[44][256]:[45][256]:[46][256]:[47][1]:[48][1]:[49][8]}:[6]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][8]:[6][8192]:[7][8192]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][8]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][1]:[37][1]:[38][1]:[39][1]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][8192]:[47][1]:[48][8]:[49][8]}:[7]{:[1][8]:[2][8]:[3][8]:[4][8192]:[5][8192]:[6][8192]:[7][8192]:[8][8192]:[9][8192]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][8]:[26][8]:[27][8]:[28][8]:[29][8]:[30][8]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8]:[44][8192]:[45][8192]:[46][8192]:[47][8]:[48][8]:[49][8]}:[8]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][4096]:[6][4096]:[7][8192]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][8]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8192]:[43][8192]:[44][8192]:[45][8192]:[46][8192]:[47][8192]:[48][8]:[49][8]}:[9]{:[1][8]:[2][8]:[3][8]:[4][4096]:[5][4096]:[6][8]:[7][8]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][32]:[43][8192]:[44][8]:[45][4096]:[46][4096]:[47][8]:[48][8]:[49][8192]}:[10]{:[1][32]:[2][8192]:[3][8192]:[4][4096]:[5][8]:[6][8]:[7][8]:[8][8]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][16]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][16]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8]:[44][8]:[45][8]:[46][4096]:[47][8192]:[48][32]:[49][32]}:[11]{:[1][8192]:[2][8192]:[3][8192]:[4][4096]:[5][32]:[6][32]:[7][32]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][16]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][16]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8192]:[44][32]:[45][8192]:[46][4096]:[47][32]:[48][8192]:[49][8192]}:[12]{:[1][8192]:[2][32]:[3][8192]:[4][8192]:[5][32]:[6][8192]:[7][8192]:[8][16]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][512]:[19][512]:[20][512]:[21][2048]:[22][1]:[23][1]:[24][2048]:[25][1]:[26][1]:[27][8]:[28][1]:[29][1]:[30][2048]:[31][8]:[32][2048]:[33][2048]:[34][512]:[35][512]:[36][512]:[37][512]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][16]:[43][8192]:[44][8192]:[45][8192]:[46][8192]:[47][8192]:[48][8192]:[49][8192]}:[13]{:[1][8192]:[2][8192]:[3][8192]:[4][32]:[5][32]:[6][16]:[7][16]:[8][16]:[9][2048]:[10][2048]:[11][512]:[12][512]:[13][512]:[14][512]:[15][512]:[16][8]:[17][2048]:[18][2048]:[19][2048]:[20][8]:[21][8]:[22][8]:[23][512]:[24][1]:[25][2048]:[26][1]:[27][8]:[28][1]:[29][2048]:[30][2048]:[31][512]:[32][512]:[33][512]:[34][512]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][16]:[43][16]:[44][16]:[45][8192]:[46][8192]:[47][8192]:[48][8192]:[49][8192]}:[14]{:[1][32]:[2][32]:[3][8192]:[4][8192]:[5][16]:[6][16]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][512]:[21][2048]:[22][2048]:[23][8]:[24][1]:[25][1]:[26][8]:[27][1]:[28][1]:[29][8]:[30][2048]:[31][2048]:[32][2048]:[33][8]:[34][8]:[35][512]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][16]:[45][16]:[46][16]:[47][16]:[48][16]:[49][8192]}:[15]{:[1][8192]:[2][8192]:[3][16]:[4][16]:[5][16]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][512]:[18][512]:[19][512]:[20][8]:[21][8]:[22][2048]:[23][2048]:[24][2048]:[25][1]:[26][1]:[27][8]:[28][1]:[29][2048]:[30][512]:[31][512]:[32][512]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][16]:[49][16]}:[16]{:[1][16]:[2][16]:[3][16]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][512]:[20][2048]:[21][2048]:[22][2048]:[23][8]:[24][8]:[25][2048]:[26][8]:[27][1]:[28][512]:[29][512]:[30][8]:[31][8]:[32][2048]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}:[17]{:[1][2048]:[2][2048]:[3][2048]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][2048]:[21][512]:[22][2048]:[23][2048]:[24][2048]:[25][2048]:[26][8]:[27][2048]:[28][2048]:[29][2048]:[30][2048]:[31][512]:[32][512]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}:[18]{:[1][2048]:[2][2048]:[3][2048]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][2048]:[21][2048]:[22][2048]:[23][8]:[24][512]:[25][512]:[26][8]:[27][512]:[28][512]:[29][2048]:[30][2048]:[31][2048]:[32][2048]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}}} +S8110:T<1>1<24>G:paintutils:M:drawImage{:[1]{:[1]{:[1][1]:[2][1]:[3][1]:[4][1]:[5][1]:[6][1]:[7][1]:[8][1]:[9][8]:[10][1]:[11][1]:[12][8]:[13][1]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][8]:[35][1]:[36][1]:[37][1]:[38][1]:[39][1]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][1]:[47][1]:[48][1]:[49][1]}:[2]{:[1][1]:[2][8]:[3][8]:[4][8]:[5][8]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][1]:[12][1]:[13][1]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][1]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][1]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][1]:[47][8]:[48][8]:[49][1]}:[3]{:[1][1]:[2][256]:[3][1]:[4][8]:[5][1]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][256]:[12][1]:[13][8]:[14][1]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][1]:[24][1]:[25][1]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][1]:[32][1]:[33][1]:[34][1]:[35][256]:[36][256]:[37][1]:[38][8]:[39][8]:[40][1]:[41][8]:[42][1]:[43][1]:[44][1]:[45][1]:[46][8]:[47][1]:[48][1]:[49][1]}:[4]{:[1][1]:[2][256]:[3][1]:[4][1]:[5][256]:[6][1]:[7][1]:[8][1]:[9][8]:[10][1]:[11][8]:[12][8]:[13][1]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][1]:[24][1]:[25][1]:[26][8]:[27][1]:[28][1]:[29][256]:[30][256]:[31][8]:[32][1]:[33][1]:[34][1]:[35][256]:[36][256]:[37][256]:[38][256]:[39][256]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][8]:[46][8]:[47][1]:[48][1]:[49][1]}:[5]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][1]:[6][1]:[7][1]:[8][1]:[9][1]:[10][1]:[11][1]:[12][1]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][1]:[25][1]:[26][1]:[27][256]:[28][1]:[29][1]:[30][8]:[31][256]:[32][256]:[33][256]:[34][1]:[35][1]:[36][1]:[37][1]:[38][1]:[39][256]:[40][1]:[41][1]:[42][1]:[43][1]:[44][256]:[45][256]:[46][256]:[47][1]:[48][1]:[49][8]}:[6]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][8]:[6][8192]:[7][8192]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][8]:[26][1]:[27][1]:[28][1]:[29][1]:[30][1]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][1]:[37][1]:[38][1]:[39][1]:[40][1]:[41][1]:[42][1]:[43][1]:[44][1]:[45][1]:[46][8192]:[47][1]:[48][8]:[49][8]}:[7]{:[1][8]:[2][8]:[3][8]:[4][8192]:[5][8192]:[6][8192]:[7][8192]:[8][8192]:[9][8192]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][8]:[23][8]:[24][8]:[25][8]:[26][8]:[27][8]:[28][8]:[29][8]:[30][8]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8]:[44][8192]:[45][8192]:[46][8192]:[47][8]:[48][8]:[49][8]}:[8]{:[1][8]:[2][8]:[3][8]:[4][8]:[5][4096]:[6][4096]:[7][8192]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][8]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][8]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8192]:[43][8192]:[44][8192]:[45][8192]:[46][8192]:[47][8192]:[48][8]:[49][8]}:[9]{:[1][8]:[2][8]:[3][8]:[4][4096]:[5][4096]:[6][8]:[7][8]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][8]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][8]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][32]:[43][8192]:[44][8]:[45][4096]:[46][4096]:[47][8]:[48][8]:[49][8192]}:[10]{:[1][32]:[2][8192]:[3][8192]:[4][4096]:[5][8]:[6][8]:[7][8]:[8][8]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][16]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][16]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8]:[44][8]:[45][8]:[46][4096]:[47][8192]:[48][32]:[49][32]}:[11]{:[1][8192]:[2][8192]:[3][8192]:[4][4096]:[5][32]:[6][32]:[7][32]:[8][8192]:[9][8]:[10][8]:[11][8]:[12][8]:[13][8]:[14][8]:[15][8]:[16][8]:[17][8]:[18][8]:[19][8]:[20][16]:[21][16]:[22][16]:[23][16]:[24][16]:[25][16]:[26][16]:[27][16]:[28][16]:[29][16]:[30][16]:[31][16]:[32][8]:[33][8]:[34][8]:[35][8]:[36][8]:[37][8]:[38][8]:[39][8]:[40][8]:[41][8]:[42][8]:[43][8192]:[44][32]:[45][8192]:[46][4096]:[47][32]:[48][8192]:[49][8192]}:[12]{:[1][8192]:[2][32]:[3][8192]:[4][8192]:[5][32]:[6][8192]:[7][8192]:[8][16]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][512]:[19][512]:[20][512]:[21][2048]:[22][1]:[23][1]:[24][2048]:[25][1]:[26][1]:[27][8]:[28][1]:[29][1]:[30][2048]:[31][8]:[32][2048]:[33][2048]:[34][512]:[35][512]:[36][512]:[37][512]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][16]:[43][8192]:[44][8192]:[45][8192]:[46][8192]:[47][8192]:[48][8192]:[49][8192]}:[13]{:[1][8192]:[2][8192]:[3][8192]:[4][32]:[5][32]:[6][16]:[7][16]:[8][16]:[9][2048]:[10][2048]:[11][512]:[12][512]:[13][512]:[14][512]:[15][512]:[16][8]:[17][2048]:[18][2048]:[19][2048]:[20][8]:[21][8]:[22][8]:[23][512]:[24][1]:[25][2048]:[26][1]:[27][8]:[28][1]:[29][2048]:[30][2048]:[31][512]:[32][512]:[33][512]:[34][512]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][16]:[43][16]:[44][16]:[45][8192]:[46][8192]:[47][8192]:[48][8192]:[49][8192]}:[14]{:[1][32]:[2][32]:[3][8192]:[4][8192]:[5][16]:[6][16]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][512]:[21][2048]:[22][2048]:[23][8]:[24][1]:[25][1]:[26][8]:[27][1]:[28][1]:[29][8]:[30][2048]:[31][2048]:[32][2048]:[33][8]:[34][8]:[35][512]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][16]:[45][16]:[46][16]:[47][16]:[48][16]:[49][8192]}:[15]{:[1][8192]:[2][8192]:[3][16]:[4][16]:[5][16]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][512]:[18][512]:[19][512]:[20][8]:[21][8]:[22][2048]:[23][2048]:[24][2048]:[25][1]:[26][1]:[27][8]:[28][1]:[29][2048]:[30][512]:[31][512]:[32][512]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][16]:[49][16]}:[16]{:[1][16]:[2][16]:[3][16]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][512]:[20][2048]:[21][2048]:[22][2048]:[23][8]:[24][8]:[25][2048]:[26][8]:[27][1]:[28][512]:[29][512]:[30][8]:[31][8]:[32][2048]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}:[17]{:[1][2048]:[2][2048]:[3][2048]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][2048]:[21][512]:[22][2048]:[23][2048]:[24][2048]:[25][2048]:[26][8]:[27][2048]:[28][2048]:[29][2048]:[30][2048]:[31][512]:[32][512]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}:[18]{:[1][2048]:[2][2048]:[3][2048]:[4][2048]:[5][2048]:[6][2048]:[7][2048]:[8][2048]:[9][2048]:[10][2048]:[11][2048]:[12][2048]:[13][2048]:[14][2048]:[15][2048]:[16][2048]:[17][2048]:[18][2048]:[19][2048]:[20][2048]:[21][2048]:[22][2048]:[23][8]:[24][512]:[25][512]:[26][8]:[27][512]:[28][512]:[29][2048]:[30][2048]:[31][2048]:[32][2048]:[33][2048]:[34][2048]:[35][2048]:[36][2048]:[37][2048]:[38][2048]:[39][2048]:[40][2048]:[41][2048]:[42][2048]:[43][2048]:[44][2048]:[45][2048]:[46][2048]:[47][2048]:[48][2048]:[49][2048]}}:[2][1]:[3][1]} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:setBackgroundColor{:[1][8192]} +R15:T<1>1<7>{:[1]T} +S54:T<1>1<24>G:paintutils:M:drawPixel{:[1][3]:[2][3]:[3]N} +R15:T<1>1<7>{:[1]T} +S60:T<1>1<24>G:paintutils:M:drawPixel{:[1][4]:[2][3]:[3][16384]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<23>G:paintutils:M:drawLine{:[1][8]:[2][3]:[3][11]:[4][6]:[5]N} +R15:T<1>1<7>{:[1]T} +S74:T<1>1<23>G:paintutils:M:drawLine{:[1][11]:[2][3]:[3][8]:[4][6]:[5][16384]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<22>G:paintutils:M:drawBox{:[1][13]:[2][3]:[3][16]:[4][6]:[5]N} +R15:T<1>1<7>{:[1]T} +S74:T<1>1<22>G:paintutils:M:drawBox{:[1][18]:[2][3]:[3][21]:[4][6]:[5][16384]} +R15:T<1>1<7>{:[1]T} +S74:T<1>1<28>G:paintutils:M:drawFilledBox{:[1][23]:[2][3]:[3][26]:[4][6]:[5]N} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<28>G:paintutils:M:drawFilledBox{:[1][28]:[2][3]:[3][31]:[4][6]:[5][16384]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][9]} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/parallel.txt b/tests/proto/m_1.20.1/cc_1.108.3/parallel.txt new file mode 100644 index 0000000..2f7de16 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/parallel.txt @@ -0,0 +1,437 @@ +R3323:0[5]{:[1]<11>parallel.py:[0]<2>py}<11>parallel.py<3268>from cc import import_file, os, parallel + +_lib = import_file('_lib.py', __file__) +assert_takes_time, assert_raises = _lib.assert_takes_time, _lib.assert_raises + + +tags = set() + + +def partial(tag, fn, *args): + def wrap(): + tags.add(tag) + return fn(*args) + return wrap + + +all_parallels = [ + ('waitForAll', parallel.waitForAll), + ('waitForAny', parallel.waitForAny), +] + + +for name, fn in all_parallels: + tags.clear() + with assert_takes_time(1.5, 3): + # Since os.sleep is mostly waiting for events, it doesn't block execution of parallel threads + # and this snippet takes approximately 2 seconds to complete. + fn( + partial('a', os.sleep, 2), + partial('b', os.sleep, 2), + partial('c', os.sleep, 2), + ) + assert tags == {'a', 'b', 'c'} + print(name, 'OK') + + +for name, fn in all_parallels: + tags.clear() + tts = (0, 1) if name == 'waitForAny' else (1.5, 3) + with assert_takes_time(*tts): + fn( + partial('fast', os.version), + partial('s1', os.sleep, 2), + partial('s2', os.sleep, 2), + ) + assert tags == {'fast', 's1', 's2'} + print(name, 'fast OK') + + +def breaks_fast(etype): + os.sleep(0.5) + raise etype + + +def breaks_slow(etype): + os.sleep(3) + raise etype + + +tags.clear() +with assert_takes_time(0, 1): + parallel.waitForAny( + partial('fast', os.version), + partial('bomb', breaks_slow, IndexError), + ) +assert tags == {'fast', 'bomb'} +print('waitForAny fast success OK') + + +tags.clear() +with assert_takes_time(2.5, 3.8): + with assert_raises(IndexError): + parallel.waitForAll( + partial('fast', os.version), + partial('bomb', breaks_slow, IndexError), + ) +assert tags == {'fast', 'bomb'} +print('waitForAll waits for bomb OK') + + +for name, fn in all_parallels: + tags.clear() + with assert_takes_time(0.4, 1.2): + with assert_raises(ValueError): + fn( + partial('v', breaks_fast, ValueError), + partial('s', os.sleep, 2), + partial('i', breaks_slow, IndexError), + ) + os.sleep(4) + assert tags == {'v', 's', 'i'} + print(name + ' handles error OK') + + +for name, fn in all_parallels: + tags.clear() + with assert_takes_time(1.5, 3): + fn( + partial('1_s', os.sleep, 2), + partial( + '1_p', + fn, + partial('2_s', os.sleep, 2), + partial( + '2_p', + fn, + partial('3_s', os.sleep, 2), + ), + ), + ) + assert tags == {'1_s', '1_p', '2_s', '2_p', '3_s'} + print('Nested', name, 'OK') + + +def nested_err(): + parallel.waitForAll( + partial('n_v', breaks_fast, ValueError), + partial('n_s', os.sleep, 2), + partial('n_i', breaks_slow, IndexError), + ) + + +tags.clear() +with assert_takes_time(0.4, 1.2): + with assert_raises(ValueError): + parallel.waitForAll( + nested_err, + partial('s', os.sleep, 2), + partial('i', breaks_slow, IndexError), + ) +assert tags == {'s', 'i', 'n_v', 'n_s', 'n_i'} +print('Nested errors OK') + + +print('Test finished successfully') +S260:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<11>parallel.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276481478]} +S30:T<1>2<12>G:os:M:sleep{:[1][2]} +S30:T<1>3<12>G:os:M:sleep{:[1][2]} +S30:T<1>4<12>G:os:M:sleep{:[1][2]} +R15:T<1>2<7>{:[1]T} +R15:T<1>4<7>{:[1]T} +R15:T<1>3<7>{:[1]T} +S5:D<1>3 +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276483456]} +S42:T<1>1<13>io.write(...){:[1]<10>waitForAll} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S33:T<1>1<13>io.write(...){:[1]<2>OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276483462]} +S30:T<1>5<12>G:os:M:sleep{:[1][2]} +S30:T<1>6<12>G:os:M:sleep{:[1][2]} +S30:T<1>7<12>G:os:M:sleep{:[1][2]} +R15:T<1>5<7>{:[1]T} +S13:D<1>5<1>6<1>7 +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R15:T<1>6<7>{:[1]T} +R15:T<1>7<7>{:[1]T} +R35:T<1>1<26>{:[1]T:[2][1699276485456]} +S42:T<1>1<13>io.write(...){:[1]<10>waitForAny} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S33:T<1>1<13>io.write(...){:[1]<2>OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276485465]} +S25:T<1>8<14>G:os:M:version{} +S30:T<1>9<12>G:os:M:sleep{:[1][2]} +S30:T<1>a<12>G:os:M:sleep{:[1][2]} +R35:T<1>8<26>{:[1]T:[2]<11>CraftOS 1.8} +R15:T<1>a<7>{:[1]T} +R15:T<1>9<7>{:[1]T} +S5:D<1>9 +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276487456]} +S42:T<1>1<13>io.write(...){:[1]<10>waitForAll} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S38:T<1>1<13>io.write(...){:[1]<7>fast OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276487464]} +S25:T<1>b<14>G:os:M:version{} +S30:T<1>c<12>G:os:M:sleep{:[1][2]} +S30:T<1>d<12>G:os:M:sleep{:[1][2]} +R35:T<1>b<26>{:[1]T:[2]<11>CraftOS 1.8} +S13:D<1>b<1>c<1>d +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276487467]} +S42:T<1>1<13>io.write(...){:[1]<10>waitForAny} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S38:T<1>1<13>io.write(...){:[1]<7>fast OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276487473]} +S25:T<1>e<14>G:os:M:version{} +S30:T<1>f<12>G:os:M:sleep{:[1][3]} +R35:T<1>e<26>{:[1]T:[2]<11>CraftOS 1.8} +S9:D<1>e<1>f +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276487476]} +S58:T<1>1<13>io.write(...){:[1]<26>waitForAny fast success OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276487480]} +S25:T<1>g<14>G:os:M:version{} +S30:T<1>h<12>G:os:M:sleep{:[1][3]} +R35:T<1>g<26>{:[1]T:[2]<11>CraftOS 1.8} +R15:T<1>h<7>{:[1]T} +S5:D<1>h +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276490457]} +S60:T<1>1<13>io.write(...){:[1]<28>waitForAll waits for bomb OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276490460]} +S32:T<1>i<12>G:os:M:sleep{:[1][0.5]} +S30:T<1>j<12>G:os:M:sleep{:[1][2]} +S30:T<1>k<12>G:os:M:sleep{:[1][3]} +R15:T<1>i<7>{:[1]T} +S13:D<1>i<1>j<1>k +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276490955]} +S30:T<1>1<12>G:os:M:sleep{:[1][4]} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<13>io.write(...){:[1]<27>waitForAll handles error OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276494958]} +S32:T<1>l<12>G:os:M:sleep{:[1][0.5]} +S30:T<1>m<12>G:os:M:sleep{:[1][2]} +S30:T<1>n<12>G:os:M:sleep{:[1][3]} +R15:T<1>l<7>{:[1]T} +S13:D<1>l<1>m<1>n +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276495455]} +S30:T<1>1<12>G:os:M:sleep{:[1][4]} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<13>io.write(...){:[1]<27>waitForAny handles error OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276499459]} +S30:T<1>o<12>G:os:M:sleep{:[1][2]} +S30:T<1>q<12>G:os:M:sleep{:[1][2]} +S30:T<1>s<12>G:os:M:sleep{:[1][2]} +R15:T<1>q<7>{:[1]T} +R15:T<1>o<7>{:[1]T} +R15:T<1>s<7>{:[1]T} +S5:D<1>s +S5:D<1>r +S5:D<1>p +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276501456]} +S37:T<1>1<13>io.write(...){:[1]<6>Nested} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S42:T<1>1<13>io.write(...){:[1]<10>waitForAll} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S33:T<1>1<13>io.write(...){:[1]<2>OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276501465]} +S30:T<1>t<12>G:os:M:sleep{:[1][2]} +S30:T<1>v<12>G:os:M:sleep{:[1][2]} +S30:T<1>x<12>G:os:M:sleep{:[1][2]} +R15:T<1>t<7>{:[1]T} +S21:D<1>t<1>u<1>v<1>w<1>x +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R15:T<1>x<7>{:[1]T} +R15:T<1>v<7>{:[1]T} +R35:T<1>1<26>{:[1]T:[2][1699276503456]} +S37:T<1>1<13>io.write(...){:[1]<6>Nested} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S42:T<1>1<13>io.write(...){:[1]<10>waitForAny} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> } +R15:T<1>1<7>{:[1]T} +S33:T<1>1<13>io.write(...){:[1]<2>OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276503465]} +S30:T<1>z<12>G:os:M:sleep{:[1][2]} +S31:T<2>01<12>G:os:M:sleep{:[1][3]} +S33:T<2>11<12>G:os:M:sleep{:[1][0.5]} +S31:T<2>21<12>G:os:M:sleep{:[1][2]} +S31:T<2>31<12>G:os:M:sleep{:[1][3]} +R16:T<2>11<7>{:[1]T} +S16:D<2>11<2>21<2>31 +S14:D<2>01<1>y<1>z +S33:T<1>1<12>G:os:M:epoch{:[1]<3>utc} +R35:T<1>1<26>{:[1]T:[2][1699276503955]} +S48:T<1>1<13>io.write(...){:[1]<16>Nested errors OK} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral.txt new file mode 100644 index 0000000..8c0b59b --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral.txt @@ -0,0 +1,154 @@ +R672:0[5]{:[1]<13>peripheral.py:[0]<2>py}<13>peripheral.py<614>from cc import import_file, peripheral + +_lib = import_file('_lib.py', __file__) + +_lib.step('Remove all peripherals') + +side = 'top' + +assert peripheral.getNames() == [] +assert peripheral.getType(side) is None +assert peripheral.isPresent(side) is False +assert peripheral.wrap(side) is None + +_lib.step(f'Put disk drive on {side} side of computer') + +assert peripheral.getNames() == [side] +assert peripheral.getType(side) == 'drive' +assert peripheral.isPresent(side) is True +d = peripheral.wrap(side) +assert d is not None +assert d.isDiskPresent() is False + +print('Remove disk drive') + +print('Test finished successfully') +S262:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<13>peripheral.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S62:T<1>1<13>io.write(...){:[1]<30>Remove all peripherals [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S34:T<1>1<23>G:peripheral:M:getNames{} +R22:T<1>1<13>{:[1]T:[2]{}} +S43:T<1>1<22>G:peripheral:M:getType{:[1]<3>top} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<24>G:peripheral:M:isPresent{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]F} +S43:T<1>1<22>G:peripheral:M:getType{:[1]<3>top} +R15:T<1>1<7>{:[1]T} +S78:T<1>1<13>io.write(...){:[1]<46>Put disk drive on top side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S34:T<1>1<23>G:peripheral:M:getNames{} +R32:T<1>1<23>{:[1]T:[2]{:[1]<3>top}} +S43:T<1>1<22>G:peripheral:M:getType{:[1]<3>top} +R28:T<1>1<19>{:[1]T:[2]<5>drive} +S45:T<1>1<24>G:peripheral:M:isPresent{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]T} +S43:T<1>1<22>G:peripheral:M:getType{:[1]<3>top} +R28:T<1>1<19>{:[1]T:[2]<5>drive} +S61:T<1>1<19>G:peripheral:M:call{:[1]<3>top:[2]<13>isDiskPresent} +R21:T<1>1<12>{:[1]T:[2]F} +S49:T<1>1<13>io.write(...){:[1]<17>Remove disk drive} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_commandblock.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_commandblock.txt new file mode 100644 index 0000000..f3f9ee9 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_commandblock.txt @@ -0,0 +1,155 @@ +R777:0[5]{:[1]<26>peripheral_commandblock.py:[0]<2>py}<26>peripheral_commandblock.py<693>from cc import LuaException, import_file, peripheral + +_lib = import_file('_lib.py', __file__) + +side = 'left' + +_lib.step(f'Attach command block at {side} side of computer') + +m = peripheral.wrap(side) + +assert m.getCommand() == '' +assert m.setCommand('say Hello from python side') is None +assert m.getCommand() == 'say Hello from python side' +assert m.runCommand() is None + +assert m.setCommand('time query daytime') is None +assert m.getCommand() == 'time query daytime' +assert m.runCommand() is None + +assert m.setCommand('') is None +assert m.getCommand() == '' +with _lib.assert_raises(LuaException): + m.runCommand() + +print('You must have seen chat message') +print('Test finished successfully') +S275:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<26>peripheral_commandblock.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S85:T<1>1<13>io.write(...){:[1]<53>Attach command block at left side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R30:T<1>1<21>{:[1]T:[2]<7>command} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>getCommand} +R23:T<1>1<14>{:[1]T:[2]<0>} +S93:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>setCommand:[3]<26>say Hello from python side} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>getCommand} +R50:T<1>1<41>{:[1]T:[2]<26>say Hello from python side} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>runCommand} +R21:T<1>1<12>{:[1]T:[2]T} +S85:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>setCommand:[3]<18>time query daytime} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>getCommand} +R42:T<1>1<33>{:[1]T:[2]<18>time query daytime} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>runCommand} +R21:T<1>1<12>{:[1]T:[2]T} +S66:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>setCommand:[3]<0>} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>getCommand} +R23:T<1>1<14>{:[1]T:[2]<0>} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<10>runCommand} +R43:T<1>1<34>{:[1]T:[2]F:[3]<14>Command failed} +S63:T<1>1<13>io.write(...){:[1]<31>You must have seen chat message} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_computer.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_computer.txt new file mode 100644 index 0000000..5c3a426 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_computer.txt @@ -0,0 +1,178 @@ +R970:0[5]{:[1]<22>peripheral_computer.py:[0]<2>py}<22>peripheral_computer.py<894>from cc import import_file, peripheral + +_lib = import_file('_lib.py', __file__) + + +def computer_peri(place_thing, thing, finish): + side = 'left' + + _lib.step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + _lib.step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + _lib.step(f'{thing.capitalize()} must shutdown') + + _lib.step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + _lib.step(f'{thing.capitalize()} must reboot') + + print(f'Test {finish} finished successfully') + + +computer_peri('another computer', 'computer', '1/2') +computer_peri('turtle', 'turtle', '2/2') +S271:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<22>peripheral_computer.py} +R1162:T<1>1<1151>{:[1]T:[2]<1134>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text)} +S105:T<1>1<13>io.write(...){:[1]<73>Place another computer on left side of computer +Don't turn it on! [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R31:T<1>1<22>{:[1]T:[2]<8>computer} +S52:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>isOn} +R21:T<1>1<12>{:[1]T:[2]F} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>getID} +R24:T<1>1<15>{:[1]T:[2][-1]} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>getLabel} +R15:T<1>1<7>{:[1]T} +S54:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>turnOn} +R15:T<1>1<7>{:[1]T} +S70:T<1>1<13>io.write(...){:[1]<38>Computer must be turned on now [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>shutdown} +R15:T<1>1<7>{:[1]T} +S62:T<1>1<13>io.write(...){:[1]<30>Computer must shutdown [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S93:T<1>1<13>io.write(...){:[1]<61>Now turn on computer manually and enter some commands [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S54:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>reboot} +R15:T<1>1<7>{:[1]T} +S60:T<1>1<13>io.write(...){:[1]<28>Computer must reboot [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S62:T<1>1<13>io.write(...){:[1]<30>Test 1/2 finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S95:T<1>1<13>io.write(...){:[1]<63>Place turtle on left side of computer +Don't turn it on! [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R29:T<1>1<20>{:[1]T:[2]<6>turtle} +S52:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>isOn} +R21:T<1>1<12>{:[1]T:[2]F} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>getID} +R23:T<1>1<14>{:[1]T:[2][0]} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>getLabel} +R15:T<1>1<7>{:[1]T} +S54:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>turnOn} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<13>io.write(...){:[1]<36>Turtle must be turned on now [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>shutdown} +R15:T<1>1<7>{:[1]T} +S60:T<1>1<13>io.write(...){:[1]<28>Turtle must shutdown [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S91:T<1>1<13>io.write(...){:[1]<59>Now turn on turtle manually and enter some commands [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S54:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>reboot} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Turtle must reboot [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S62:T<1>1<13>io.write(...){:[1]<30>Test 2/2 finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_disk.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_disk.txt new file mode 100644 index 0000000..5bea866 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_disk.txt @@ -0,0 +1,268 @@ +R1830:0[5]{:[1]<18>peripheral_disk.py:[0]<2>py}<18>peripheral_disk.py<1761>from cc import LuaException, import_file, peripheral + +_lib = import_file('_lib.py', __file__) + +side = 'left' + +_lib.step(f'Put empty disk drive on {side} side of computer') + +d = peripheral.wrap(side) +assert d is not None + +assert d.isDiskPresent() is False +assert d.hasData() is False +assert d.getMountPath() is None +assert d.setDiskLabel('text') is None +assert d.getDiskLabel() is None +assert d.getDiskID() is None +assert d.hasAudio() is False +assert d.getAudioTitle() is None +assert d.playAudio() is None +assert d.stopAudio() is None +assert d.ejectDisk() is None + +_lib.step('Put new CC diskette into disk drive') + +assert d.isDiskPresent() is True +assert d.hasData() is True +assert isinstance(d.getMountPath(), str) +assert isinstance(d.getDiskID(), int) + +assert d.getDiskLabel() is None +assert d.setDiskLabel('label') is None +assert d.getDiskLabel() == 'label' +assert d.setDiskLabel(None) is None +assert d.getDiskLabel() is None + +assert d.hasAudio() is False +assert d.getAudioTitle() is None +assert d.playAudio() is None +assert d.stopAudio() is None + +assert d.ejectDisk() is None + +_lib.step('Put any audio disk into disk drive') + +assert d.isDiskPresent() is True +assert d.hasData() is False +assert d.getMountPath() is None +assert d.getDiskID() is None +assert d.hasAudio() is True + +label = d.getAudioTitle() +assert isinstance(label, str) +assert label != 'label' +print(f'Label is {label}') +assert d.getDiskLabel() == label +with _lib.assert_raises(LuaException): + d.setDiskLabel('label') +with _lib.assert_raises(LuaException): + d.setDiskLabel(None) +# no effect +assert d.getDiskLabel() == label + +assert d.playAudio() is None + +_lib.step('Audio must be playing now') + +assert d.stopAudio() is None +assert d.ejectDisk() is None + +print('Test finished successfully') +S267:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<18>peripheral_disk.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S85:T<1>1<13>io.write(...){:[1]<53>Put empty disk drive on left side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R28:T<1>1<19>{:[1]T:[2]<5>drive} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>isDiskPresent} +R21:T<1>1<12>{:[1]T:[2]F} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>hasData} +R21:T<1>1<12>{:[1]T:[2]F} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getMountPath} +R15:T<1>1<7>{:[1]T} +S72:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setDiskLabel:[3]<4>text} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getDiskLabel} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>getDiskID} +R15:T<1>1<7>{:[1]T} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>hasAudio} +R21:T<1>1<12>{:[1]T:[2]F} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>getAudioTitle} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>playAudio} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>stopAudio} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>ejectDisk} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<13>io.write(...){:[1]<43>Put new CC diskette into disk drive [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>isDiskPresent} +R21:T<1>1<12>{:[1]T:[2]T} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>hasData} +R21:T<1>1<12>{:[1]T:[2]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getMountPath} +R27:T<1>1<18>{:[1]T:[2]<4>disk} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>getDiskID} +R23:T<1>1<14>{:[1]T:[2][0]} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getDiskLabel} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setDiskLabel:[3]<5>label} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getDiskLabel} +R28:T<1>1<19>{:[1]T:[2]<5>label} +S66:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setDiskLabel:[3]N} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getDiskLabel} +R15:T<1>1<7>{:[1]T} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>hasAudio} +R21:T<1>1<12>{:[1]T:[2]F} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>getAudioTitle} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>playAudio} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>stopAudio} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>ejectDisk} +R15:T<1>1<7>{:[1]T} +S74:T<1>1<13>io.write(...){:[1]<42>Put any audio disk into disk drive [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>isDiskPresent} +R21:T<1>1<12>{:[1]T:[2]T} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>hasData} +R21:T<1>1<12>{:[1]T:[2]F} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getMountPath} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>getDiskID} +R15:T<1>1<7>{:[1]T} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>hasAudio} +R21:T<1>1<12>{:[1]T:[2]T} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>getAudioTitle} +R32:T<1>1<23>{:[1]T:[2]<9>C418 - 13} +S50:T<1>1<13>io.write(...){:[1]<18>Label is C418 - 13} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getDiskLabel} +R32:T<1>1<23>{:[1]T:[2]<9>C418 - 13} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setDiskLabel:[3]<5>label} +R52:T<1>1<43>{:[1]F:[2]<28>Disk label cannot be changed} +S66:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setDiskLabel:[3]N} +R52:T<1>1<43>{:[1]F:[2]<28>Disk label cannot be changed} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getDiskLabel} +R32:T<1>1<23>{:[1]T:[2]<9>C418 - 13} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>playAudio} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<13>io.write(...){:[1]<33>Audio must be playing now [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>stopAudio} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>ejectDisk} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_modem.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_modem.txt new file mode 100644 index 0000000..92e06e8 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_modem.txt @@ -0,0 +1,230 @@ +R1483:0[5]{:[1]<19>peripheral_modem.py:[0]<2>py}<19>peripheral_modem.py<1412>from cc import import_file, parallel, os, peripheral + +_lib = import_file('_lib.py', __file__) +side = 'back' + + +def do_test(): + m = peripheral.wrap(side) + + remote_channel = 5 + local_channel = 7 + messages = [] + + def _send(): + for msg in [ + 1, + b'hi', + {b'data': 5}, + b'stop', + ]: + os.sleep(1) + m.transmit(remote_channel, local_channel, msg) + + def _recv(): + assert m.isOpen(local_channel) is False + for msg in m.receive(local_channel): + assert m.isOpen(local_channel) is True + assert msg.reply_channel == remote_channel + assert msg.distance > 0 + messages.append(msg.content) + if len(messages) == 3: + break + + assert m.closeAll() is None + parallel.waitForAll(_recv, _send) + + assert messages == [1, b'hi', {b'data': 5}] + assert m.isOpen(local_channel) is False + assert m.closeAll() is None + assert isinstance(m.isWireless(), bool) + + +_lib.step( + f'Attach wired modem to {side} side\n' + f'Place another computer with wired modem on {side} side\n' + 'Connect modems\n' + 'On another computer start py modem_server.py' +) +do_test() +_lib.step( + 'Disconnect and remove wired modems\n' + 'Attach wireless modems\n' + 'Restart modem_server.py on another computer' +) +do_test() +print('Test finished successfully') +S268:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<19>peripheral_modem.py} +R1162:T<1>1<1151>{:[1]T:[2]<1134>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text)} +S185:T<1>1<13>io.write(...){:[1]<152>Attach wired modem to back side +Place another computer with wired modem on back side +Connect modems +On another computer start py modem_server.py [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>back} +R50:T<1>1<41>{:[1]T:[2]<5>modem:[3]<14>peripheral_hub} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]F} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<8>closeAll} +R15:T<1>1<7>{:[1]T} +S61:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +S30:T<1>3<12>G:os:M:sleep{:[1][1]} +R21:T<1>2<12>{:[1]T:[2]F} +S61:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>2<12>{:[1]T:[2]F} +S59:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<4>open:[3][7]} +R15:T<1>2<7>{:[1]T} +S18:S<13>modem_message +R15:T<1>3<7>{:[1]T} +S77:T<1>3<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5][1]} +R15:T<1>3<7>{:[1]T} +S30:T<1>3<12>G:os:M:sleep{:[1][1]} +R59:E<13>modem_message{:[1]<4>back:[2][7]:[3][5]:[4][1]:[5][5]} +S61:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>2<12>{:[1]T:[2]T} +R15:T<1>3<7>{:[1]T} +S79:T<1>3<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5]<2>hi} +R15:T<1>3<7>{:[1]T} +S30:T<1>3<12>G:os:M:sleep{:[1][1]} +R61:E<13>modem_message{:[1]<4>back:[2][7]:[3][5]:[4]<2>hi:[5][5]} +S61:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>2<12>{:[1]T:[2]T} +R15:T<1>3<7>{:[1]T} +S87:T<1>3<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5]{:<4>data[5]}} +R15:T<1>3<7>{:[1]T} +S30:T<1>3<12>G:os:M:sleep{:[1][1]} +R69:E<13>modem_message{:[1]<4>back:[2][7]:[3][5]:[4]{:<4>data[5]}:[5][5]} +S61:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>2<12>{:[1]T:[2]T} +S18:U<13>modem_message +S60:T<1>2<19>G:peripheral:M:call{:[1]<4>back:[2]<5>close:[3][7]} +R15:T<1>2<7>{:[1]T} +R15:T<1>3<7>{:[1]T} +S81:T<1>3<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5]<4>stop} +R15:T<1>3<7>{:[1]T} +S5:D<1>3 +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>1<12>{:[1]T:[2]F} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<8>closeAll} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]F} +S142:T<1>1<13>io.write(...){:[1]<109>Disconnect and remove wired modems +Attach wireless modems +Restart modem_server.py on another computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>back} +R28:T<1>1<19>{:[1]T:[2]<5>modem} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]T} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<8>closeAll} +R15:T<1>1<7>{:[1]T} +S61:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +S30:T<1>5<12>G:os:M:sleep{:[1][1]} +R21:T<1>4<12>{:[1]T:[2]F} +S61:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>4<12>{:[1]T:[2]F} +S59:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<4>open:[3][7]} +R15:T<1>4<7>{:[1]T} +S18:S<13>modem_message +R15:T<1>5<7>{:[1]T} +S77:T<1>5<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5][1]} +R15:T<1>5<7>{:[1]T} +S30:T<1>5<12>G:os:M:sleep{:[1][1]} +R59:E<13>modem_message{:[1]<4>back:[2][7]:[3][5]:[4][1]:[5][3]} +S61:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>4<12>{:[1]T:[2]T} +R15:T<1>5<7>{:[1]T} +S79:T<1>5<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5]<2>hi} +R15:T<1>5<7>{:[1]T} +S30:T<1>5<12>G:os:M:sleep{:[1][1]} +R61:E<13>modem_message{:[1]<4>back:[2][7]:[3][5]:[4]<2>hi:[5][3]} +S61:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>4<12>{:[1]T:[2]T} +R15:T<1>5<7>{:[1]T} +S87:T<1>5<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5]{:<4>data[5]}} +R15:T<1>5<7>{:[1]T} +S30:T<1>5<12>G:os:M:sleep{:[1][1]} +R69:E<13>modem_message{:[1]<4>back:[2][7]:[3][5]:[4]{:<4>data[5]}:[5][3]} +S61:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>4<12>{:[1]T:[2]T} +S18:U<13>modem_message +S60:T<1>4<19>G:peripheral:M:call{:[1]<4>back:[2]<5>close:[3][7]} +R15:T<1>4<7>{:[1]T} +R15:T<1>5<7>{:[1]T} +S81:T<1>5<19>G:peripheral:M:call{:[1]<4>back:[2]<8>transmit:[3][5]:[4][7]:[5]<4>stop} +R15:T<1>5<7>{:[1]T} +S5:D<1>5 +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<6>isOpen:[3][7]} +R21:T<1>1<12>{:[1]T:[2]F} +S56:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<8>closeAll} +R15:T<1>1<7>{:[1]T} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_monitor.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_monitor.txt new file mode 100644 index 0000000..64ea0b1 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_monitor.txt @@ -0,0 +1,430 @@ +R3406:0[5]{:[1]<21>peripheral_monitor.py:[0]<2>py}<21>peripheral_monitor.py<3331>from cc import import_file, colors, os, peripheral + +_lib = import_file('_lib.py', __file__) + + +side = 'left' + +_lib.step( + 'Use advanced computer and monitor for colors\n' + f'Place single block monitor on {side} side of computer', +) + +m = peripheral.wrap(side) +assert m is not None +assert m.getSize() == (7, 5) +assert m.isColor() is True +assert m.setTextColor(colors.white) is None +assert m.setBackgroundColor(colors.black) is None +assert m.clear() is None +assert m.setCursorPos(1, 1) is None +assert m.getCursorPos() == (1, 1) +assert m.write('Alpha') is None +assert m.getCursorPos() == (6, 1) +assert m.setCursorBlink(False) is None +assert m.getCursorBlink() is False +assert m.setCursorBlink(True) is None +assert m.getCursorBlink() is True + +_lib.step('You must have seen word Alpha with blinking cursor') + +assert m.clear() is None +assert m.setCursorBlink(False) is None +for offs, (tc, bc) in enumerate(( + (colors.lime, colors.green), + (colors.yellow, colors.brown), + (colors.red, colors.orange), +), start=1): + assert m.setTextColor(tc) is None + assert m.getTextColor() == tc + assert m.setBackgroundColor(bc) is None + assert m.getBackgroundColor() == bc + assert m.setCursorPos(offs, offs) is None + assert m.getCursorPos() == (offs, offs) + assert m.write('text') is None +assert m.setBackgroundColor(colors.black) is None +os.sleep(1) +for i in range(2): + assert m.scroll(-1) is None + os.sleep(0.5) +for i in range(2): + assert m.scroll(1) is None + os.sleep(0.5) + +_lib.step('You must have seen three texts with different colors scrolling') + +assert m.setTextColor(colors.white) is None +assert m.setBackgroundColor(colors.black) is None +assert m.clear() is None +for i in range(1, 5): + assert m.setCursorPos(1, i) is None + assert m.write((str(i) + ' ') * 4) is None +os.sleep(2) +for i in range(2, 5, 2): + assert m.setCursorPos(1, i) is None + assert m.clearLine() is None + +_lib.step('You must have seen some lines disappearing') + +assert m.setBackgroundColor(colors.black) is None +assert m.clear() is None +assert m.setCursorPos(1, 1) is None +assert m.blit( + 'rainbow', + b'e14d3ba', + b'fffffff', +) is None +assert m.setCursorPos(1, 2) is None +assert m.blit( + 'rainbow', + b'0000000', + b'e14d3ba', +) is None + +_lib.step('You must have seen per-letter colored text') + +assert m.setBackgroundColor(colors.black) is None +assert m.setTextColor(colors.white) is None +assert m.getTextScale() == 1 +assert m.setTextScale(5) is None +assert m.getTextScale() == 5 +assert m.setCursorPos(1, 1) is None +assert m.clear() is None +assert m.getSize() == (1, 1) +assert m.write('AAA') is None + +_lib.step('You must have seen single large letter A') + +assert m.setTextScale(1) is None +assert m.setBackgroundColor(colors.white) is None +assert m.clear() is None +for i, color in enumerate(colors.iter_colors()): + m.setPaletteColor(color, i / 15, 0, 0) +assert m.setCursorPos(1, 1) is None +assert m.blit( + ' redtex', + b'0123456', + b'0000000', +) is None +assert m.setCursorPos(1, 2) is None +assert m.blit( + 'tappear', + b'789abcd', + b'0000000', +) is None +assert m.setCursorPos(1, 3) is None +assert m.blit( + 's!', + b'ef', + b'00', +) is None + +_lib.step('You must have seen different shades of red made using palettes') + +print('Remove monitor') +print('Test finished successfully') +S270:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<21>peripheral_monitor.py} +R1162:T<1>1<1151>{:[1]T:[2]<1134>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text)} +S137:T<1>1<13>io.write(...){:[1]<104>Use advanced computer and monitor for colors +Place single block monitor on left side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R30:T<1>1<21>{:[1]T:[2]<7>monitor} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>getSize} +R30:T<1>1<21>{:[1]T:[2][7]:[3][5]} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>isColor} +R21:T<1>1<12>{:[1]T:[2]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextColor:[3][1]} +R15:T<1>1<7>{:[1]T} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][32768]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>clear} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][1]} +S65:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<5>Alpha} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][6]:[3][1]} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<14>setCursorBlink:[3]F} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<14>getCursorBlink} +R21:T<1>1<12>{:[1]T:[2]F} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<14>setCursorBlink:[3]T} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<14>getCursorBlink} +R21:T<1>1<12>{:[1]T:[2]T} +S90:T<1>1<13>io.write(...){:[1]<58>You must have seen word Alpha with blinking cursor [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>clear} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<14>setCursorBlink:[3]F} +R15:T<1>1<7>{:[1]T} +S69:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextColor:[3][32]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getTextColor} +R24:T<1>1<15>{:[1]T:[2][32]} +S77:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][8192]} +R15:T<1>1<7>{:[1]T} +S67:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>getBackgroundColor} +R26:T<1>1<17>{:[1]T:[2][8192]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][1]} +S64:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<4>text} +R15:T<1>1<7>{:[1]T} +S69:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextColor:[3][16]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getTextColor} +R24:T<1>1<15>{:[1]T:[2][16]} +S77:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][4096]} +R15:T<1>1<7>{:[1]T} +S67:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>getBackgroundColor} +R26:T<1>1<17>{:[1]T:[2][4096]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][2]:[4][2]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][2]:[3][2]} +S64:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<4>text} +R15:T<1>1<7>{:[1]T} +S72:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextColor:[3][16384]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getTextColor} +R27:T<1>1<18>{:[1]T:[2][16384]} +S74:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][2]} +R15:T<1>1<7>{:[1]T} +S67:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>getBackgroundColor} +R23:T<1>1<14>{:[1]T:[2][2]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][3]:[4][3]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][3]:[3][3]} +S64:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<4>text} +R15:T<1>1<7>{:[1]T} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][32768]} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][1]} +R15:T<1>1<7>{:[1]T} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>scroll:[3][-1]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>scroll:[3][-1]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>scroll:[3][1]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<6>scroll:[3][1]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S102:T<1>1<13>io.write(...){:[1]<70>You must have seen three texts with different colors scrolling [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextColor:[3][1]} +R15:T<1>1<7>{:[1]T} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][32768]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>clear} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<12>1 1 1 1 } +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][2]} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<12>2 2 2 2 } +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][3]} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<12>3 3 3 3 } +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][4]} +R15:T<1>1<7>{:[1]T} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<12>4 4 4 4 } +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][2]} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>clearLine} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][4]} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>clearLine} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<13>io.write(...){:[1]<50>You must have seen some lines disappearing [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][32768]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>clear} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>blit:[3]<7>rainbow:[4]<7>e14d3ba:[5]<7>fffffff} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][2]} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>blit:[3]<7>rainbow:[4]<7>0000000:[5]<7>e14d3ba} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<13>io.write(...){:[1]<50>You must have seen per-letter colored text [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][32768]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextColor:[3][1]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getTextScale} +R23:T<1>1<14>{:[1]T:[2][1]} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextScale:[3][5]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getTextScale} +R23:T<1>1<14>{:[1]T:[2][5]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>clear} +R15:T<1>1<7>{:[1]T} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>getSize} +R30:T<1>1<21>{:[1]T:[2][1]:[3][1]} +S63:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<3>AAA} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<13>io.write(...){:[1]<48>You must have seen single large letter A [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setTextScale:[3][1]} +R15:T<1>1<7>{:[1]T} +S74:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<18>setBackgroundColor:[3][1]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>clear} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][1]:[4][0.0]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S110:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][2]:[4][0.06666666666666667]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S110:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][4]:[4][0.13333333333333333]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][8]:[4][0.2]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S111:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][16]:[4][0.26666666666666666]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S110:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][32]:[4][0.3333333333333333]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S95:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][64]:[4][0.4]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S111:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][128]:[4][0.4666666666666667]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S111:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][256]:[4][0.5333333333333333]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S96:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][512]:[4][0.6]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S112:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][1024]:[4][0.6666666666666666]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S112:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][2048]:[4][0.7333333333333333]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S97:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][4096]:[4][0.8]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S112:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][8192]:[4][0.8666666666666667]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S113:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][16384]:[4][0.9333333333333333]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S98:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<15>setPaletteColor:[3][32768]:[4][1.0]:[5][0]:[6][0]} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>blit:[3]<7> redtex:[4]<7>0123456:[5]<7>0000000} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][2]} +R15:T<1>1<7>{:[1]T} +S94:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>blit:[3]<7>tappear:[4]<7>789abcd:[5]<7>0000000} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][3]} +R15:T<1>1<7>{:[1]T} +S79:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<4>blit:[3]<2>s!:[4]<2>ef:[5]<2>00} +R15:T<1>1<7>{:[1]T} +S102:T<1>1<13>io.write(...){:[1]<70>You must have seen different shades of red made using palettes [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S46:T<1>1<13>io.write(...){:[1]<14>Remove monitor} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_printer.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_printer.txt new file mode 100644 index 0000000..7553525 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_printer.txt @@ -0,0 +1,369 @@ +R2597:0[5]{:[1]<21>peripheral_printer.py:[0]<2>py}<21>peripheral_printer.py<2522>from cc import LuaException, import_file, peripheral + +_lib = import_file('_lib.py', __file__) +assert_raises = _lib.assert_raises + +side = 'left' + +_lib.step(f'Attach empty printer at {side} side of computer') + +m = peripheral.wrap(side) + +assert m.getPaperLevel() == 0 +assert m.getInkLevel() == 0 + +# no paper +assert m.newPage() is False +# page not started +with assert_raises(LuaException): + m.endPage() +with assert_raises(LuaException): + m.write('test') +with assert_raises(LuaException): + m.setCursorPos(2, 2) +with assert_raises(LuaException): + m.getCursorPos() +with assert_raises(LuaException): + m.getPageSize() +with assert_raises(LuaException): + m.setPageTitle('title') + +_lib.step('Put paper into printer') +paper_level = m.getPaperLevel() +assert paper_level > 0 +# no ink +assert m.newPage() is False + +_lib.step('Put ink into printer') +ink_level = m.getInkLevel() +assert ink_level > 0 + +assert m.newPage() is True +assert m.getPaperLevel() < paper_level +assert m.getInkLevel() < ink_level + +assert m.setCursorPos(2, 2) is None +assert m.getCursorPos() == (2, 2) +assert m.setCursorPos(1, 1) is None +assert m.getCursorPos() == (1, 1) +assert m.setPageTitle('Green bottles') is None +assert m.getPageSize() == (25, 21) + + +def row(n=1): + _, r = m.getCursorPos() + m.setCursorPos(1, r + n) + + +def split_text(text, max_width=25): + for i in range(0, len(text), max_width): + yield text[i:i + max_width] + + +def split_by_words(text, max_width=25): + stack = [] + stack_len = 0 + for word in text.split(' '): + assert len(word) <= max_width + with_word = len(word) if stack_len == 0 else stack_len + 1 + len(word) + if with_word > max_width: + yield ' '.join(stack) + stack.clear() + stack_len = 0 + else: + stack.append(word) + stack_len = with_word + if stack: + yield ' '.join(stack) + + +def multiline_write(text): + _, r = m.getCursorPos() + for pt in split_by_words(text): + assert m.setCursorPos(1, r) is None + assert m.write(pt) is None + r += 1 + assert m.setCursorPos(1, r) is None + + +assert m.write('Green bottles'.center(25)) is None +row(2) + +x = 2 +while x > 0: + multiline_write(f'{x} green bottles hanging on the wall') + multiline_write(f'{x} green bottles hanging on the wall') + multiline_write('if one green bottle accidently falls') + x -= 1 + multiline_write(f'there will be {x} hanging on the wall') + row() + +assert m.endPage() is True + +print('Test finished successfully') +S270:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<21>peripheral_printer.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S85:T<1>1<13>io.write(...){:[1]<53>Attach empty printer at left side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R30:T<1>1<21>{:[1]T:[2]<7>printer} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>getPaperLevel} +R23:T<1>1<14>{:[1]T:[2][0]} +S60:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<11>getInkLevel} +R23:T<1>1<14>{:[1]T:[2][0]} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>newPage} +R21:T<1>1<12>{:[1]T:[2]F} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>endPage} +R40:T<1>1<31>{:[1]F:[2]<16>Page not started} +S64:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<4>test} +R40:T<1>1<31>{:[1]F:[2]<16>Page not started} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][2]:[4][2]} +R40:T<1>1<31>{:[1]F:[2]<16>Page not started} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R40:T<1>1<31>{:[1]F:[2]<16>Page not started} +S60:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<11>getPageSize} +R40:T<1>1<31>{:[1]F:[2]<16>Page not started} +S73:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setPageTitle:[3]<5>title} +R40:T<1>1<31>{:[1]F:[2]<16>Page not started} +S62:T<1>1<13>io.write(...){:[1]<30>Put paper into printer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>getPaperLevel} +R24:T<1>1<15>{:[1]T:[2][64]} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>newPage} +R21:T<1>1<12>{:[1]T:[2]F} +S60:T<1>1<13>io.write(...){:[1]<28>Put ink into printer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S60:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<11>getInkLevel} +R24:T<1>1<15>{:[1]T:[2][64]} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>newPage} +R21:T<1>1<12>{:[1]T:[2]T} +S62:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<13>getPaperLevel} +R24:T<1>1<15>{:[1]T:[2][63]} +S60:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<11>getInkLevel} +R24:T<1>1<15>{:[1]T:[2][63]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][2]:[4][2]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][2]:[3][2]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][1]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][1]} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setPageTitle:[3]<13>Green bottles} +R15:T<1>1<7>{:[1]T} +S60:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<11>getPageSize} +R32:T<1>1<23>{:[1]T:[2][25]:[3][21]} +S86:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<25> Green bottles } +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][26]:[3][1]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][3]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][3]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][3]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<23>2 green bottles hanging} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][4]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<8>the wall} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][5]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][5]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][5]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<23>2 green bottles hanging} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][6]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<8>the wall} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][7]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][7]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][7]} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<19>if one green bottle} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][8]} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<5>falls} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][9]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R30:T<1>1<21>{:[1]T:[2][1]:[3][9]} +S75:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][9]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<23>there will be 1 hanging} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][10]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<8>the wall} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][11]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][1]:[3][11]} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][12]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][1]:[3][12]} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][12]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<23>1 green bottles hanging} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][13]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<8>the wall} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][14]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][1]:[3][14]} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][14]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<23>1 green bottles hanging} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][15]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<8>the wall} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][16]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][1]:[3][16]} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][16]} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<19>if one green bottle} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][17]} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<5>falls} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][18]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][1]:[3][18]} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][18]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<23>there will be 0 hanging} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][19]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<5>write:[3]<8>the wall} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][20]} +R15:T<1>1<7>{:[1]T} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>getCursorPos} +R31:T<1>1<22>{:[1]T:[2][1]:[3][20]} +S76:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<12>setCursorPos:[3][1]:[4][21]} +R15:T<1>1<7>{:[1]T} +S55:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<7>endPage} +R21:T<1>1<12>{:[1]T:[2]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_remote.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_remote.txt new file mode 100644 index 0000000..a77f600 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_remote.txt @@ -0,0 +1,150 @@ +R1152:0[5]{:[1]<20>peripheral_remote.py:[0]<2>py}<20>peripheral_remote.py<1079>from cc import import_file, peripheral + +_lib = import_file('_lib.py', __file__) + +side = 'back' + +_lib.step(f'Attach and disable (right-click) wired modem at {side} side') + +m = peripheral.wrap(side) +assert m.isWireless() is False +assert m.getNameLocal() is None + +_lib.step(f'Enable (right-click) wired modem at {side} side') + +assert isinstance(m.getNameLocal(), str) + +_lib.step('Connect networked speaker peripheral & enable its modem') + +names = m.getNamesRemote() +assert isinstance(names, list) +assert len(names) > 0 +speaker = [] +for n in names: + assert isinstance(n, str) + if n.startswith('speaker_'): + speaker.append(n) +assert len(speaker) == 1 +speaker = speaker[0] + +assert m.isPresentRemote('doesnotexist') is False +assert m.getTypeRemote('doesnotexist') is None + +assert m.isPresentRemote(speaker) is True +assert m.getTypeRemote(speaker) == 'speaker' + +assert m.wrapRemote('doesnotexist') is None +s = m.wrapRemote(speaker) + +assert s.playSound('minecraft:entity.player.levelup') is True + +print('You must have heard levelup sound') +print('Test finished successfully') +S269:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<20>peripheral_remote.py} +R1162:T<1>1<1151>{:[1]T:[2]<1134>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text)} +S97:T<1>1<13>io.write(...){:[1]<65>Attach and disable (right-click) wired modem at back side [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>back} +R50:T<1>1<41>{:[1]T:[2]<5>modem:[3]<14>peripheral_hub} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]F} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]F} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<12>getNameLocal} +R15:T<1>1<7>{:[1]T} +S85:T<1>1<13>io.write(...){:[1]<53>Enable (right-click) wired modem at back side [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S61:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<12>getNameLocal} +R34:T<1>1<25>{:[1]T:[2]<10>computer_3} +S95:T<1>1<13>io.write(...){:[1]<63>Connect networked speaker peripheral & enable its modem [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S63:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<14>getNamesRemote} +R38:T<1>1<29>{:[1]T:[2]{:[1]<9>speaker_0}} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<15>isPresentRemote:[3]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]F} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<13>getTypeRemote:[3]<12>doesnotexist} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<15>isPresentRemote:[3]<9>speaker_0} +R21:T<1>1<12>{:[1]T:[2]T} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<13>getTypeRemote:[3]<9>speaker_0} +R30:T<1>1<21>{:[1]T:[2]<7>speaker} +S53:T<1>1<22>G:peripheral:M:getType{:[1]<12>doesnotexist} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<22>G:peripheral:M:getType{:[1]<9>speaker_0} +R30:T<1>1<21>{:[1]T:[2]<7>speaker} +S115:T<1>1<19>G:peripheral:M:call{:[1]<9>speaker_0:[2]<9>playSound:[3]<31>minecraft:entity.player.levelup:[4][1]:[5][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S65:T<1>1<13>io.write(...){:[1]<33>You must have heard levelup sound} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/peripheral_speaker.txt b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_speaker.txt new file mode 100644 index 0000000..884f3c5 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/peripheral_speaker.txt @@ -0,0 +1,332 @@ +R780:0[5]{:[1]<21>peripheral_speaker.py:[0]<2>py}<21>peripheral_speaker.py<706>import random + +from cc import import_file, os, peripheral + +_lib = import_file('_lib.py', __file__) + +random.seed(598392) +side = 'left' + +_lib.step(f'Attach speaker at {side} side of computer') + +m = peripheral.wrap(side) + +for _ in range(48): + assert m.playNote( + random.choice([ + 'bass', 'basedrum', 'bell', 'chime', 'flute', 'guitar', 'hat', + 'snare', 'xylophone', 'iron_xylophone', 'pling', 'banjo', + 'bit', 'didgeridoo', 'cow_bell', + ]), + 3, + random.randint(0, 24) + ) is True + os.sleep(0.2) + +assert m.playSound('minecraft:entity.player.levelup') is True + +print('You must have heard notes and sounds') +print('Test finished successfully') +S270:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<21>peripheral_speaker.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S79:T<1>1<13>io.write(...){:[1]<47>Attach speaker at left side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R30:T<1>1<21>{:[1]T:[2]<7>speaker} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][2]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S88:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<10>didgeridoo:[4][3]:[5][0]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<8>basedrum:[4][3]:[5][18]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>banjo:[4][3]:[5][23]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S85:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<8>basedrum:[4][3]:[5][5]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>hat:[4][3]:[5][16]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][17]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bell:[4][3]:[5][14]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bass:[4][3]:[5][8]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>banjo:[4][3]:[5][24]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>snare:[4][3]:[5][11]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>hat:[4][3]:[5][6]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][6]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<8>basedrum:[4][3]:[5][23]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bass:[4][3]:[5][13]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>snare:[4][3]:[5][14]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>pling:[4][3]:[5][3]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>hat:[4][3]:[5][21]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bell:[4][3]:[5][17]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>bit:[4][3]:[5][10]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>hat:[4][3]:[5][16]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S80:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>bit:[4][3]:[5][4]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][6]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>banjo:[4][3]:[5][12]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bell:[4][3]:[5][23]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>pling:[4][3]:[5][14]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bass:[4][3]:[5][17]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>hat:[4][3]:[5][22]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>flute:[4][3]:[5][16]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>chime:[4][3]:[5][4]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>banjo:[4][3]:[5][3]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<3>bit:[4][3]:[5][14]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S89:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<10>didgeridoo:[4][3]:[5][14]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>flute:[4][3]:[5][24]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bell:[4][3]:[5][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][19]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][11]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<4>bell:[4][3]:[5][24]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>snare:[4][3]:[5][14]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<8>basedrum:[4][3]:[5][13]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S93:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<14>iron_xylophone:[4][3]:[5][20]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<8>cow_bell:[4][3]:[5][20]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S85:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<8>basedrum:[4][3]:[5][5]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>flute:[4][3]:[5][5]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S88:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<10>didgeridoo:[4][3]:[5][5]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S93:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<14>iron_xylophone:[4][3]:[5][17]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<5>pling:[4][3]:[5][16]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<8>playNote:[3]<6>guitar:[4][3]:[5][0]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.2]} +R15:T<1>1<7>{:[1]T} +S110:T<1>1<19>G:peripheral:M:call{:[1]<4>left:[2]<9>playSound:[3]<31>minecraft:entity.player.levelup:[4][1]:[5][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S68:T<1>1<13>io.write(...){:[1]<36>You must have heard notes and sounds} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/pocket.txt b/tests/proto/m_1.20.1/cc_1.108.3/pocket.txt new file mode 100644 index 0000000..a212663 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/pocket.txt @@ -0,0 +1,113 @@ +R671:0[5]{:[1]<9>pocket.py:[0]<2>py}<9>pocket.py<623>from cc import LuaException, import_file, pocket, peripheral + +_lib = import_file('_lib.py', __file__) + + +assert peripheral.isPresent('back') is False + +_lib.step('Clean inventory from any pocket upgrades') + +with _lib.assert_raises(LuaException): + pocket.equipBack() +with _lib.assert_raises(LuaException): + pocket.unequipBack() +assert peripheral.isPresent('back') is False + +_lib.step('Put any pocket upgrade to inventory') + +assert pocket.equipBack() is None +assert peripheral.isPresent('back') is True + +assert pocket.unequipBack() is None +assert peripheral.isPresent('back') is False + +print('Test finished successfully') +S257:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<9>pocket.py} +R1162:T<1>1<1151>{:[1]T:[2]<1134>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text)} +S46:T<1>1<24>G:peripheral:M:isPresent{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]F} +S80:T<1>1<13>io.write(...){:[1]<48>Clean inventory from any pocket upgrades [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S31:T<1>1<20>G:pocket:M:equipBack{} +R56:T<1>1<47>{:[1]T:[2]F:[3]<27>Cannot find a valid upgrade} +S33:T<1>1<22>G:pocket:M:unequipBack{} +R47:T<1>1<38>{:[1]T:[2]F:[3]<18>Nothing to unequip} +S46:T<1>1<24>G:peripheral:M:isPresent{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]F} +S75:T<1>1<13>io.write(...){:[1]<43>Put any pocket upgrade to inventory [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S31:T<1>1<20>G:pocket:M:equipBack{} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<24>G:peripheral:M:isPresent{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]T} +S33:T<1>1<22>G:pocket:M:unequipBack{} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<24>G:peripheral:M:isPresent{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]F} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_local_monitor.txt b/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_local_monitor.txt new file mode 100644 index 0000000..e47e601 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_local_monitor.txt @@ -0,0 +1,43 @@ +R452:0[5]{:[1]<28>redirect_to_local_monitor.py:[0]<2>py}<28>redirect_to_local_monitor.py<364>from cc import colors, term, peripheral + + +side = 'left' +input(f'Attach 3x3 color monitor to {side} side of computer [enter]') + +with term.redirect(peripheral.wrap(side)): + term.setBackgroundColor(colors.green) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + print('Redirected to monitor') + +print('Test finished successfully') +S89:T<1>1<13>io.write(...){:[1]<57>Attach 3x3 color monitor to left side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>left} +R30:T<1>1<21>{:[1]T:[2]<7>monitor} +S165:T<1>1<107>return(function(n,...)local o,e=term.redirect(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>2:[2]E30>return peripheral.wrap("left")} +R21:T<1>1<12>{:[1]T:[2]T} +S48:T<1>1<27>G:term:M:setBackgroundColor{:[1][8192]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<13>io.write(...){:[1]<21>Redirected to monitor} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<46>local n=...;term.redirect(temp[n]);temp[n]=nil{:[1]<1>2} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_remote_monitor.txt b/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_remote_monitor.txt new file mode 100644 index 0000000..8ab63a0 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_remote_monitor.txt @@ -0,0 +1,65 @@ +R666:0[5]{:[1]<29>redirect_to_remote_monitor.py:[0]<2>py}<29>redirect_to_remote_monitor.py<576>from cc import colors, term, peripheral + + +side = 'back' +input(f'Attach wired modem to {side} side of computer [enter]') + +mod = peripheral.wrap(side) + +input('Connect remote monitor using wires, activate its modem [enter]') + +for name in mod.getNamesRemote(): + if mod.getTypeRemote(name) == 'monitor': + break +else: + assert False + +with term.redirect(peripheral.wrap(name)): + term.setBackgroundColor(colors.blue) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + print('Redirected to monitor') + +print('Test finished successfully') +S83:T<1>1<13>io.write(...){:[1]<51>Attach wired modem to back side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S44:T<1>1<22>G:peripheral:M:getType{:[1]<4>back} +R50:T<1>1<41>{:[1]T:[2]<5>modem:[3]<14>peripheral_hub} +S59:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<10>isWireless} +R21:T<1>1<12>{:[1]T:[2]F} +S94:T<1>1<13>io.write(...){:[1]<62>Connect remote monitor using wires, activate its modem [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S63:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<14>getNamesRemote} +R38:T<1>1<29>{:[1]T:[2]{:[1]<9>monitor_0}} +S78:T<1>1<19>G:peripheral:M:call{:[1]<4>back:[2]<13>getTypeRemote:[3]<9>monitor_0} +R30:T<1>1<21>{:[1]T:[2]<7>monitor} +S49:T<1>1<22>G:peripheral:M:getType{:[1]<9>monitor_0} +R30:T<1>1<21>{:[1]T:[2]<7>monitor} +S170:T<1>1<107>return(function(n,...)local o,e=term.redirect(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>2:[2]E35>return peripheral.wrap("monitor_0")} +R21:T<1>1<12>{:[1]T:[2]T} +S48:T<1>1<27>G:term:M:setBackgroundColor{:[1][2048]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<13>io.write(...){:[1]<21>Redirected to monitor} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<46>local n=...;term.redirect(temp[n]);temp[n]=nil{:[1]<1>2} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_window.txt b/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_window.txt new file mode 100644 index 0000000..bb4a8c7 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/redirect_to_window.txt @@ -0,0 +1,88 @@ +R708:0[5]{:[1]<21>redirect_to_window.py:[0]<2>py}<21>redirect_to_window.py<634>from cc import colors, term, window + + +w, h = term.getSize() +left = window.create( + term.current(), + 1, 1, w // 2, h, True) +right = window.create( + term.current(), + w // 2 + 1, 1, w // 2, h, True) +with term.redirect(left): + term.setBackgroundColor(colors.green) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, h // 2) + print('Left part') +with term.redirect(right): + term.setBackgroundColor(colors.red) + term.setTextColor(colors.yellow) + term.clear() + term.setCursorPos(1, h // 2) + print('Right part') +print('Default terminal restored') + +print('Test finished successfully') +S27:T<1>1<16>G:term:M:getSize{} +R32:T<1>1<23>{:[1]T:[2][51]:[3][19]} +S123:T<1>1<103>return(function(n,...)local o,e=term.current();if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>2} +R21:T<1>1<12>{:[1]T:[2]T} +S170:T<1>1<107>return(function(n,...)local o,e=window.create(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>3:[2]X1>2:[3][1]:[4][1]:[5][25]:[6][19]:[7]T} +R21:T<1>1<12>{:[1]T:[2]T} +S123:T<1>1<103>return(function(n,...)local o,e=term.current();if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>4} +R21:T<1>1<12>{:[1]T:[2]T} +S171:T<1>1<107>return(function(n,...)local o,e=window.create(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>5:[2]X1>4:[3][26]:[4][1]:[5][25]:[6][19]:[7]T} +R21:T<1>1<12>{:[1]T:[2]T} +S135:T<1>1<107>return(function(n,...)local o,e=term.redirect(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>6:[2]X1>3} +R21:T<1>1<12>{:[1]T:[2]T} +S48:T<1>1<27>G:term:M:setBackgroundColor{:[1][8192]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][9]} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<13>io.write(...){:[1]<9>Left part} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<46>local n=...;term.redirect(temp[n]);temp[n]=nil{:[1]<1>6} +R15:T<1>1<7>{:[1]T} +S135:T<1>1<107>return(function(n,...)local o,e=term.redirect(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>7:[2]X1>5} +R21:T<1>1<12>{:[1]T:[2]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][16384]} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<21>G:term:M:setTextColor{:[1][16]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][9]} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<13>io.write(...){:[1]<10>Right part} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<46>local n=...;term.redirect(temp[n]);temp[n]=nil{:[1]<1>7} +R15:T<1>1<7>{:[1]T} +S57:T<1>1<13>io.write(...){:[1]<25>Default terminal restored} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<23>local n=...;temp[n]=nil{:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<23>local n=...;temp[n]=nil{:[1]<1>2} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<23>local n=...;temp[n]=nil{:[1]<1>5} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<23>local n=...;temp[n]=nil{:[1]<1>4} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/rednet.txt b/tests/proto/m_1.20.1/cc_1.108.3/rednet.txt new file mode 100644 index 0000000..78db60f --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/rednet.txt @@ -0,0 +1,175 @@ +R1474:0[5]{:[1]<9>rednet.py:[0]<2>py}<9>rednet.py<1425>from cc import LuaException, import_file, os, rednet, parallel + +_lib = import_file('_lib.py', __file__) +step, assert_raises = _lib.step, _lib.assert_raises + +side = 'back' + +step(f'Attach modem to {side} side of computer') + +assert rednet.close() is None + +assert rednet.isOpen(side) is False +assert rednet.isOpen() is False + +with assert_raises(LuaException): + rednet.close('doesnotexist') + +assert rednet.close(side) is None + +with assert_raises(LuaException): + rednet.open('doesnotexist') + +assert rednet.open(side) is None +assert rednet.isOpen(side) is True + +with assert_raises(LuaException): + # disallowed hostname + rednet.host('helloproto', 'localhost') +assert rednet.host('helloproto', 'alpha') is None + +cid = os.getComputerID() + +assert rednet.lookup('helloproto', 'localhost') == cid +assert rednet.lookup('helloproto') == [cid] +assert rednet.lookup('nonexistent', 'localhost') is None +assert rednet.lookup('nonexistent') == [] + +assert rednet.unhost('helloproto') is None + +assert rednet.send(cid + 100, b'message', 'anyproto') is True +assert rednet.broadcast(b'message', 'anyproto') is None + +assert rednet.receive(timeout=1) is None + + +def _send(): + assert rednet.send(cid, b'message') is True + + +def _recv(): + assert rednet.receive(timeout=1) == (cid, b'message', None) + + +parallel.waitForAll(_send, _recv) + +assert rednet.close() is None +assert rednet.isOpen(side) is False + +print('Test finished successfully') +S257:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<9>rednet.py} +R1162:T<1>1<1151>{:[1]T:[2]<1134>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text)} +S77:T<1>1<13>io.write(...){:[1]<45>Attach modem to back side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S32:T<1>1<16>G:rednet:M:close{:[1]N} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<17>G:rednet:M:isOpen{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]F} +S33:T<1>1<17>G:rednet:M:isOpen{:[1]N} +R21:T<1>1<12>{:[1]T:[2]F} +S47:T<1>1<16>G:rednet:M:close{:[1]<12>doesnotexist} +R51:T<1>1<42>{:[1]F:[2]<27>No such modem: doesnotexist} +S38:T<1>1<16>G:rednet:M:close{:[1]<4>back} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<15>G:rednet:M:open{:[1]<12>doesnotexist} +R51:T<1>1<42>{:[1]F:[2]<27>No such modem: doesnotexist} +S37:T<1>1<15>G:rednet:M:open{:[1]<4>back} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<17>G:rednet:M:isOpen{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]T} +S60:T<1>1<15>G:rednet:M:host{:[1]<10>helloproto:[2]<9>localhost} +R41:T<1>1<32>{:[1]F:[2]<17>Reserved hostname} +S56:T<1>1<15>G:rednet:M:host{:[1]<10>helloproto:[2]<5>alpha} +R15:T<1>1<7>{:[1]T} +S31:T<1>1<20>G:os:M:getComputerID{} +R23:T<1>1<14>{:[1]T:[2][1]} +S62:T<1>1<17>G:rednet:M:lookup{:[1]<10>helloproto:[2]<9>localhost} +R23:T<1>1<14>{:[1]T:[2][1]} +S51:T<1>1<17>G:rednet:M:lookup{:[1]<10>helloproto:[2]N} +R23:T<1>1<14>{:[1]T:[2][1]} +S63:T<1>1<17>G:rednet:M:lookup{:[1]<11>nonexistent:[2]<9>localhost} +R15:T<1>1<7>{:[1]T} +S52:T<1>1<17>G:rednet:M:lookup{:[1]<11>nonexistent:[2]N} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<17>G:rednet:M:unhost{:[1]<10>helloproto} +R15:T<1>1<7>{:[1]T} +S64:T<1>1<15>G:rednet:M:send{:[1][101]:[2]<7>message:[3]<8>anyproto} +R21:T<1>1<12>{:[1]T:[2]T} +S60:T<1>1<20>G:rednet:M:broadcast{:[1]<7>message:[2]<8>anyproto} +R15:T<1>1<7>{:[1]T} +S41:T<1>1<18>G:rednet:M:receive{:[1]N:[2][1]} +R15:T<1>1<7>{:[1]T} +S52:T<1>2<15>G:rednet:M:send{:[1][1]:[2]<7>message:[3]N} +S41:T<1>3<18>G:rednet:M:receive{:[1]N:[2][1]} +R21:T<1>2<12>{:[1]T:[2]T} +R37:T<1>3<28>{:[1]T:[2][1]:[3]<7>message} +S5:D<1>3 +S32:T<1>1<16>G:rednet:M:close{:[1]N} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<17>G:rednet:M:isOpen{:[1]<4>back} +R21:T<1>1<12>{:[1]T:[2]F} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/redstone.txt b/tests/proto/m_1.20.1/cc_1.108.3/redstone.txt new file mode 100644 index 0000000..5814ec7 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/redstone.txt @@ -0,0 +1,210 @@ +R1504:0[5]{:[1]<11>redstone.py:[0]<2>py}<11>redstone.py<1449>from cc import import_file, os, redstone + +_lib = import_file('_lib.py', __file__) + +assert set(redstone.getSides()) == {'top', 'bottom', 'front', 'back', 'left', 'right'} + +_lib.step('Remove all the redstone from sides of computer') + +side = 'top' + +assert redstone.setOutput(side, True) is None +assert redstone.getOutput(side) is True +assert redstone.getAnalogOutput(side) == 15 +assert redstone.setOutput(side, False) is None +assert redstone.getOutput(side) is False +assert redstone.getAnalogOutput(side) == 0 + +assert redstone.setAnalogOutput(side, 7) is None +assert redstone.getAnalogOutput(side) == 7 +assert redstone.getOutput(side) is True +assert redstone.setAnalogOutput(side, 15) is None +assert redstone.getAnalogOutput(side) == 15 +assert redstone.setAnalogOutput(side, 0) is None +assert redstone.getAnalogOutput(side) == 0 +assert redstone.getOutput(side) is False + +assert redstone.getInput(side) is False +assert redstone.getAnalogInput(side) == 0 + +_lib.step(f'Put redstone block on {side} side of computer') + +assert redstone.getInput(side) is True +assert redstone.getAnalogInput(side) > 0 + +_lib.step(f'Remove redstone block\nPut piston on {side} side of computer') + +assert redstone.getInput(side) is False +assert redstone.getAnalogInput(side) == 0 +assert redstone.setOutput(side, True) is None +os.sleep(2) +assert redstone.setOutput(side, False) is None + +print('Piston must have been activated\nRemove piston') + +print('Test finished successfully') +S260:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<11>redstone.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S32:T<1>1<21>G:redstone:M:getSides{} +R91:T<1>1<82>{:[1]T:[2]{:[1]<6>bottom:[2]<3>top:[3]<4>back:[4]<5>front:[5]<5>right:[6]<4>left}} +S86:T<1>1<13>io.write(...){:[1]<54>Remove all the redstone from sides of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S48:T<1>1<22>G:redstone:M:setOutput{:[1]<3>top:[2]T} +R15:T<1>1<7>{:[1]T} +S43:T<1>1<22>G:redstone:M:getOutput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]T} +S49:T<1>1<28>G:redstone:M:getAnalogOutput{:[1]<3>top} +R24:T<1>1<15>{:[1]T:[2][15]} +S48:T<1>1<22>G:redstone:M:setOutput{:[1]<3>top:[2]F} +R15:T<1>1<7>{:[1]T} +S43:T<1>1<22>G:redstone:M:getOutput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]F} +S49:T<1>1<28>G:redstone:M:getAnalogOutput{:[1]<3>top} +R23:T<1>1<14>{:[1]T:[2][0]} +S56:T<1>1<28>G:redstone:M:setAnalogOutput{:[1]<3>top:[2][7]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<28>G:redstone:M:getAnalogOutput{:[1]<3>top} +R23:T<1>1<14>{:[1]T:[2][7]} +S43:T<1>1<22>G:redstone:M:getOutput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]T} +S57:T<1>1<28>G:redstone:M:setAnalogOutput{:[1]<3>top:[2][15]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<28>G:redstone:M:getAnalogOutput{:[1]<3>top} +R24:T<1>1<15>{:[1]T:[2][15]} +S56:T<1>1<28>G:redstone:M:setAnalogOutput{:[1]<3>top:[2][0]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<28>G:redstone:M:getAnalogOutput{:[1]<3>top} +R23:T<1>1<14>{:[1]T:[2][0]} +S43:T<1>1<22>G:redstone:M:getOutput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]F} +S42:T<1>1<21>G:redstone:M:getInput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]F} +S48:T<1>1<27>G:redstone:M:getAnalogInput{:[1]<3>top} +R23:T<1>1<14>{:[1]T:[2][0]} +S82:T<1>1<13>io.write(...){:[1]<50>Put redstone block on top side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S42:T<1>1<21>G:redstone:M:getInput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]T} +S48:T<1>1<27>G:redstone:M:getAnalogInput{:[1]<3>top} +R24:T<1>1<15>{:[1]T:[2][15]} +S96:T<1>1<13>io.write(...){:[1]<64>Remove redstone block +Put piston on top side of computer [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S42:T<1>1<21>G:redstone:M:getInput{:[1]<3>top} +R21:T<1>1<12>{:[1]T:[2]F} +S48:T<1>1<27>G:redstone:M:getAnalogInput{:[1]<3>top} +R23:T<1>1<14>{:[1]T:[2][0]} +S48:T<1>1<22>G:redstone:M:setOutput{:[1]<3>top:[2]T} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<22>G:redstone:M:setOutput{:[1]<3>top:[2]F} +R15:T<1>1<7>{:[1]T} +S77:T<1>1<13>io.write(...){:[1]<45>Piston must have been activated +Remove piston} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/settings.txt b/tests/proto/m_1.20.1/cc_1.108.3/settings.txt new file mode 100644 index 0000000..6149b2d --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/settings.txt @@ -0,0 +1,311 @@ +R2945:0[5]{:[1]<11>settings.py:[0]<2>py}<11>settings.py<2890>from cc import LuaException, import_file, fs, settings + +_lib = import_file('_lib.py', __file__) +step, assert_raises = _lib.step, _lib.assert_raises + +step('Settings will be cleared') + +assert settings.undefine('test.a') is None +assert settings.undefine('test.a') is None +assert settings.undefine('test.a') is None +assert settings.undefine('test.b') is None +assert settings.undefine('test.c') is None +assert settings.undefine('test.d') is None + +assert settings.clear() is None +# names are not empty, there are system settings +assert isinstance(settings.getNames(), list) + +assert settings.define('test.a') is None +assert settings.define('test.b', description='b') is None +assert settings.define('test.c', type='string', default='x') is None +assert settings.define('test.d', default=42) is None + +assert settings.getDetails('test.a') == { + 'changed': False, +} +assert settings.getDetails('test.b') == { + 'changed': False, + 'description': 'b', +} +assert settings.getDetails('test.c') == { + 'changed': False, + 'type': 'string', + 'default': 'x', + 'value': 'x', +} +assert settings.getDetails('test.d') == { + 'changed': False, + 'default': 42, + 'value': 42, +} + +# redefining +assert settings.define('test.a', type='number', default=11) is None + +assert settings.getDetails('test.a') == { + 'changed': False, + 'type': 'number', + 'default': 11, + 'value': 11, +} + +assert settings.get('test.a') == 11 +assert settings.set('test.a', 12) is None +assert settings.get('test.a') == 12 +with assert_raises(LuaException): + settings.set('test.a', 'text') +assert settings.get('test.a') == 12 +assert settings.unset('test.a') is None +assert settings.get('test.a') == 11 + +assert settings.set('test.c', 'hello') is None + +assert {'test.a', 'test.b', 'test.c', 'test.d'}.issubset(set(settings.getNames())) + +assert settings.undefine('test.a') is None +assert settings.undefine('test.b') is None +assert settings.undefine('test.c') is None +assert settings.undefine('test.d') is None + +assert 'test.c' in settings.getNames() +assert settings.get('test.c') == 'hello' +assert settings.getDetails('test.c') == { + 'changed': True, + 'value': 'hello', +} + +assert settings.unset('test.c') is None + +assert settings.get('test.c') is None +assert settings.getDetails('test.c') == { + 'changed': False, +} + +assert {'test.a', 'test.b', 'test.c', 'test.d'} & set(settings.getNames()) == set() + +assert settings.set('test.e', [9, b'text', False]) is None +assert settings.get('test.e') == {1: 9, 2: b'text', 3: False} +assert settings.clear() is None +assert settings.get('test.e') is None + +fs.delete('.settings') + +assert settings.load() is False +assert settings.save() is True +assert settings.load() is True + +fs.delete('.settings') + +assert settings.set('key', 84) is None + +assert settings.save('sfile') is True +assert settings.load('sfile') is True + +fs.delete('sfile') + +print('Test finished successfully') +S260:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<11>settings.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S64:T<1>1<13>io.write(...){:[1]<32>Settings will be cleared [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.a} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.a} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.a} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.b} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.c} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.d} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>G:settings:M:clear{} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:settings:M:getNames{} +R460:T<1>1<450>{:[1]T:[2]{:[1]<19>bios.strict_globals:[2]<19>bios.use_multishell:[3]<17>edit.autocomplete:[4]<22>edit.default_extension:[5]<16>list.show_hidden:[6]<16>lua.autocomplete:[7]<17>lua.function_args:[8]<19>lua.function_source:[9]<29>lua.warn_against_use_of_local:[10]<11>motd.enable:[11]<9>motd.path:[12]<23>paint.default_extension:[13]<24>shell.allow_disk_startup:[14]<19>shell.allow_startup:[15]<18>shell.autocomplete:[16]<25>shell.autocomplete_hidden}} +S49:T<1>1<19>G:settings:M:define{:[1]<6>test.a:[2]{}} +R15:T<1>1<7>{:[1]T} +S69:T<1>1<19>G:settings:M:define{:[1]<6>test.b:[2]{:<11>description<1>b}} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<19>G:settings:M:define{:[1]<6>test.c:[2]{:<7>default<1>x:<4>type<6>string}} +R15:T<1>1<7>{:[1]T} +S64:T<1>1<19>G:settings:M:define{:[1]<6>test.d:[2]{:<7>default[42]}} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.a} +R34:T<1>1<25>{:[1]T:[2]{:<7>changedF}} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.b} +R54:T<1>1<45>{:[1]T:[2]{:<7>changedF:<11>description<1>b}} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.c} +R79:T<1>1<70>{:[1]T:[2]{:<5>value<1>x:<4>type<6>string:<7>default<1>x:<7>changedF}} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.d} +R62:T<1>1<53>{:[1]T:[2]{:<5>value[42]:<7>changedF:<7>default[42]}} +S81:T<1>1<19>G:settings:M:define{:[1]<6>test.a:[2]{:<7>default[11]:<4>type<6>number}} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.a} +R79:T<1>1<70>{:[1]T:[2]{:<5>value[11]:<4>type<6>number:<7>default[11]:<7>changedF}} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.a:[2]N} +R24:T<1>1<15>{:[1]T:[2][11]} +S48:T<1>1<16>G:settings:M:set{:[1]<6>test.a:[2][12]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.a:[2]N} +R24:T<1>1<15>{:[1]T:[2][12]} +S51:T<1>1<16>G:settings:M:set{:[1]<6>test.a:[2]<4>text} +R69:T<1>1<60>{:[1]F:[2]<45>bad argument #2 (number expected, got string)} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.a:[2]N} +R24:T<1>1<15>{:[1]T:[2][12]} +S42:T<1>1<18>G:settings:M:unset{:[1]<6>test.a} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.a:[2]N} +R24:T<1>1<15>{:[1]T:[2][11]} +S52:T<1>1<16>G:settings:M:set{:[1]<6>test.c:[2]<5>hello} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:settings:M:getNames{} +R516:T<1>1<506>{:[1]T:[2]{:[1]<19>bios.strict_globals:[2]<19>bios.use_multishell:[3]<17>edit.autocomplete:[4]<22>edit.default_extension:[5]<16>list.show_hidden:[6]<16>lua.autocomplete:[7]<17>lua.function_args:[8]<19>lua.function_source:[9]<29>lua.warn_against_use_of_local:[10]<11>motd.enable:[11]<9>motd.path:[12]<23>paint.default_extension:[13]<24>shell.allow_disk_startup:[14]<19>shell.allow_startup:[15]<18>shell.autocomplete:[16]<25>shell.autocomplete_hidden:[17]<6>test.a:[18]<6>test.b:[19]<6>test.c:[20]<6>test.d}} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.a} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.b} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.c} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<21>G:settings:M:undefine{:[1]<6>test.d} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:settings:M:getNames{} +R474:T<1>1<464>{:[1]T:[2]{:[1]<19>bios.strict_globals:[2]<19>bios.use_multishell:[3]<17>edit.autocomplete:[4]<22>edit.default_extension:[5]<16>list.show_hidden:[6]<16>lua.autocomplete:[7]<17>lua.function_args:[8]<19>lua.function_source:[9]<29>lua.warn_against_use_of_local:[10]<11>motd.enable:[11]<9>motd.path:[12]<23>paint.default_extension:[13]<24>shell.allow_disk_startup:[14]<19>shell.allow_startup:[15]<18>shell.autocomplete:[16]<25>shell.autocomplete_hidden:[17]<6>test.c}} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.c:[2]N} +R28:T<1>1<19>{:[1]T:[2]<5>hello} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.c} +R51:T<1>1<42>{:[1]T:[2]{:<5>value<5>hello:<7>changedT}} +S42:T<1>1<18>G:settings:M:unset{:[1]<6>test.c} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.c:[2]N} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<23>G:settings:M:getDetails{:[1]<6>test.c} +R34:T<1>1<25>{:[1]T:[2]{:<7>changedF}} +S32:T<1>1<21>G:settings:M:getNames{} +R460:T<1>1<450>{:[1]T:[2]{:[1]<19>bios.strict_globals:[2]<19>bios.use_multishell:[3]<17>edit.autocomplete:[4]<22>edit.default_extension:[5]<16>list.show_hidden:[6]<16>lua.autocomplete:[7]<17>lua.function_args:[8]<19>lua.function_source:[9]<29>lua.warn_against_use_of_local:[10]<11>motd.enable:[11]<9>motd.path:[12]<23>paint.default_extension:[13]<24>shell.allow_disk_startup:[14]<19>shell.allow_startup:[15]<18>shell.autocomplete:[16]<25>shell.autocomplete_hidden}} +S69:T<1>1<16>G:settings:M:set{:[1]<6>test.e:[2]{:[1][9]:[2]<4>text:[3]F}} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.e:[2]N} +R45:T<1>1<36>{:[1]T:[2]{:[1][9]:[2]<4>text:[3]F}} +S29:T<1>1<18>G:settings:M:clear{} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<16>G:settings:M:get{:[1]<6>test.e:[2]N} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<13>G:fs:M:delete{:[1]<9>.settings} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<17>G:settings:M:load{:[1]N} +R21:T<1>1<12>{:[1]T:[2]F} +S33:T<1>1<17>G:settings:M:save{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S33:T<1>1<17>G:settings:M:load{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S40:T<1>1<13>G:fs:M:delete{:[1]<9>.settings} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<16>G:settings:M:set{:[1]<3>key:[2][84]} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:settings:M:save{:[1]<5>sfile} +R21:T<1>1<12>{:[1]T:[2]T} +S40:T<1>1<17>G:settings:M:load{:[1]<5>sfile} +R21:T<1>1<12>{:[1]T:[2]T} +S36:T<1>1<13>G:fs:M:delete{:[1]<5>sfile} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/shell.txt b/tests/proto/m_1.20.1/cc_1.108.3/shell.txt new file mode 100644 index 0000000..741e1ca --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/shell.txt @@ -0,0 +1,218 @@ +R1526:0[5]{:[1]<8>shell.py:[0]<2>py}<8>shell.py<1479>from cc import import_file, shell + +_lib = import_file('_lib.py', __file__) + + +assert shell.complete('ls ro') == ['m/', 'm'] +assert shell.completeProgram('lu') == ['a'] + +ps = shell.programs() +assert 'shutdown' in ps + +als = shell.aliases() +assert 'ls' in als +assert als['ls'] == 'list' +assert 'xls' not in als +assert shell.setAlias('xls', 'list') is None +als = shell.aliases() +assert 'xls' in als +assert als['xls'] == 'list' +assert shell.clearAlias('xls') is None +als = shell.aliases() +assert 'xls' not in als + +assert shell.getRunningProgram() == 'py' + +assert shell.resolveProgram('doesnotexist') is None +assert shell.resolveProgram('hello') == 'rom/programs/fun/hello.lua' + +assert shell.dir() == '' +assert shell.resolve('doesnotexist') == 'doesnotexist' +assert shell.resolve('startup.lua') == 'startup.lua' +assert shell.setDir('rom') is None +assert shell.dir() == 'rom' +assert shell.resolve('startup.lua') == 'rom/startup.lua' +assert shell.setDir('') is None + +assert isinstance(shell.path(), str) +assert shell.setPath(shell.path()) is None + +assert shell.execute('hello') is True +assert shell.run('hello') is True +assert shell.execute('doesnotexist') is False +assert shell.run('doesnotexist') is False + +tab = shell.openTab('hello') +assert isinstance(tab, int) + +_lib.step(f'Program has been launched in tab {tab}') + +assert shell.switchTab(tab) is None + +_lib.step('Computer will shutdown after test due to shell.exit') + +assert shell.exit() is None + +print('Test finished successfully') +S256:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<8>shell.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S41:T<1>1<18>G:shell:M:complete{:[1]<5>ls ro} +R39:T<1>1<30>{:[1]T:[2]{:[1]<2>m/:[2]<1>m}} +S45:T<1>1<25>G:shell:M:completeProgram{:[1]<2>lu} +R30:T<1>1<21>{:[1]T:[2]{:[1]<1>a}} +S34:T<1>1<18>G:shell:M:programs{:[1]N} +R809:T<1>1<799>{:[1]T:[2]{:[1]<7>_lib.py:[2]<5>about:[3]<9>adventure:[4]<5>alias:[5]<4>apis:[6]<2>bg:[7]<2>cd:[8]<4>chat:[9]<5>clear:[10]<9>colors.py:[11]<4>copy:[12]<6>delete:[13]<7>disk.py:[14]<2>dj:[15]<5>drive:[16]<4>edit:[17]<5>eject:[18]<4>exit:[19]<2>fg:[20]<5>fs.py:[21]<3>gps:[22]<5>hello:[23]<13>helloworld.py:[24]<4>help:[25]<7>help.py:[26]<2>id:[27]<6>import:[28]<7>keys.py:[29]<5>label:[30]<4>list:[31]<3>lua:[32]<5>mkdir:[33]<7>monitor:[34]<4>motd:[35]<4>move:[36]<10>multishell:[37]<5>os.py:[38]<5>paint:[39]<8>pastebin:[40]<11>peripherals:[41]<8>programs:[42]<2>py:[43]<6>reboot:[44]<11>redirection:[45]<8>redstone:[46]<6>rename:[47]<6>repeat:[48]<3>set:[49]<11>settings.py:[50]<5>shell:[51]<8>shell.py:[52]<8>shutdown:[53]<7>speaker:[54]<7>term.py:[55]<4>time:[56]<4>type:[57]<4>wget:[58]<4>worm}} +S28:T<1>1<17>G:shell:M:aliases{} +R177:T<1>1<167>{:[1]T:[2]{:<2>rm<6>delete:<3>dir<4>list:<3>clr<5>clear:<2>sh<5>shell:<2>ls<4>list:<2>cp<4>copy:<10>foreground<2>fg:<10>background<2>bg:<2>mv<4>move:<2>rs<8>redstone}} +S50:T<1>1<18>G:shell:M:setAlias{:[1]<3>xls:[2]<4>list} +R15:T<1>1<7>{:[1]T} +S28:T<1>1<17>G:shell:M:aliases{} +R191:T<1>1<181>{:[1]T:[2]{:<2>rm<6>delete:<3>dir<4>list:<3>xls<4>list:<3>clr<5>clear:<2>sh<5>shell:<2>ls<4>list:<2>cp<4>copy:<10>foreground<2>fg:<10>background<2>bg:<2>mv<4>move:<2>rs<8>redstone}} +S41:T<1>1<20>G:shell:M:clearAlias{:[1]<3>xls} +R15:T<1>1<7>{:[1]T} +S28:T<1>1<17>G:shell:M:aliases{} +R177:T<1>1<167>{:[1]T:[2]{:<2>rm<6>delete:<3>dir<4>list:<3>clr<5>clear:<2>sh<5>shell:<2>ls<4>list:<2>cp<4>copy:<10>foreground<2>fg:<10>background<2>bg:<2>mv<4>move:<2>rs<8>redstone}} +S38:T<1>1<27>G:shell:M:getRunningProgram{} +R25:T<1>1<16>{:[1]T:[2]<2>py} +S55:T<1>1<24>G:shell:M:resolveProgram{:[1]<12>doesnotexist} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<24>G:shell:M:resolveProgram{:[1]<5>hello} +R50:T<1>1<41>{:[1]T:[2]<26>rom/programs/fun/hello.lua} +S24:T<1>1<13>G:shell:M:dir{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S48:T<1>1<17>G:shell:M:resolve{:[1]<12>doesnotexist} +R36:T<1>1<27>{:[1]T:[2]<12>doesnotexist} +S47:T<1>1<17>G:shell:M:resolve{:[1]<11>startup.lua} +R35:T<1>1<26>{:[1]T:[2]<11>startup.lua} +S37:T<1>1<16>G:shell:M:setDir{:[1]<3>rom} +R15:T<1>1<7>{:[1]T} +S24:T<1>1<13>G:shell:M:dir{} +R26:T<1>1<17>{:[1]T:[2]<3>rom} +S47:T<1>1<17>G:shell:M:resolve{:[1]<11>startup.lua} +R39:T<1>1<30>{:[1]T:[2]<15>rom/startup.lua} +S34:T<1>1<16>G:shell:M:setDir{:[1]<0>} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:shell:M:path{} +R149:T<1>1<139>{:[1]T:[2]<123>.:/rom/programs:/rom/programs/http:/rom/programs/advanced:/rom/programs/rednet:/rom/programs/fun:/rom/programs/fun/advanced} +S25:T<1>1<14>G:shell:M:path{} +R149:T<1>1<139>{:[1]T:[2]<123>.:/rom/programs:/rom/programs/http:/rom/programs/advanced:/rom/programs/rednet:/rom/programs/fun:/rom/programs/fun/advanced} +S160:T<1>1<17>G:shell:M:setPath{:[1]<123>.:/rom/programs:/rom/programs/http:/rom/programs/advanced:/rom/programs/rednet:/rom/programs/fun:/rom/programs/fun/advanced} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<17>G:shell:M:execute{:[1]<5>hello} +R21:T<1>1<12>{:[1]T:[2]T} +S36:T<1>1<13>G:shell:M:run{:[1]<5>hello} +R21:T<1>1<12>{:[1]T:[2]T} +S48:T<1>1<17>G:shell:M:execute{:[1]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]F} +S44:T<1>1<13>G:shell:M:run{:[1]<12>doesnotexist} +R21:T<1>1<12>{:[1]T:[2]F} +S40:T<1>1<17>G:shell:M:openTab{:[1]<5>hello} +R23:T<1>1<14>{:[1]T:[2][2]} +S74:T<1>1<13>io.write(...){:[1]<42>Program has been launched in tab 2 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S37:T<1>1<19>G:shell:M:switchTab{:[1][2]} +R15:T<1>1<7>{:[1]T} +S91:T<1>1<13>io.write(...){:[1]<59>Computer will shutdown after test due to shell.exit [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R24:T<1>1<15>{:[1]T:[2]<1> } +S25:T<1>1<14>G:shell:M:exit{} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/shutdown.txt b/tests/proto/m_1.20.1/cc_1.108.3/shutdown.txt new file mode 100644 index 0000000..7c802d8 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/shutdown.txt @@ -0,0 +1,10 @@ +R208:0[5]{:[1]<11>shutdown.py:[0]<2>py}<11>shutdown.py<154>from cc import os + + +if args[-1:] == [b'reboot']: + assert os.reboot() is None +else: + assert os.shutdown() is None +print('Test finished successfully') +S26:T<1>1<15>G:os:M:shutdown{} +R1:D diff --git a/tests/proto/m_1.20.1/cc_1.108.3/shutdown_reboot.txt b/tests/proto/m_1.20.1/cc_1.108.3/shutdown_reboot.txt new file mode 100644 index 0000000..0382e6b --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/shutdown_reboot.txt @@ -0,0 +1,10 @@ +R221:0[5]{:[1]<11>shutdown.py:[2]<6>reboot:[0]<2>py}<11>shutdown.py<154>from cc import os + + +if args[-1:] == [b'reboot']: + assert os.reboot() is None +else: + assert os.shutdown() is None +print('Test finished successfully') +S24:T<1>1<13>G:os:M:reboot{} +R1:D diff --git a/tests/proto/m_1.20.1/cc_1.108.3/term.txt b/tests/proto/m_1.20.1/cc_1.108.3/term.txt new file mode 100644 index 0000000..9f93f0e --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/term.txt @@ -0,0 +1,720 @@ +R2826:0[5]{:[1]<7>term.py:[0]<2>py}<7>term.py<2781>from cc import colors, term, os + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +step( + 'Detach all monitors\n' + 'Use advanced computer for colors\n' + 'Screen will be cleared' +) + +assert term.getSize() == (51, 19) +assert term.isColor() is True +assert term.clear() is None +assert term.setCursorPos(1, 1) is None +assert term.getCursorPos() == (1, 1) +assert term.write('Alpha') is None +assert term.getCursorPos() == (6, 1) +assert term.setCursorBlink(False) is None +assert term.getCursorBlink() is False +assert term.setCursorBlink(True) is None +assert term.getCursorBlink() is True +os.sleep(2) + +term_step('You must have seen word Alpha with blinking cursor') + +assert term.clear() is None +for offs, (tc, bc) in enumerate(( + (colors.lime, colors.green), + (colors.yellow, colors.brown), + (colors.red, colors.orange), +), start=1): + assert term.setTextColor(tc) is None + assert term.getTextColor() == tc + assert term.setBackgroundColor(bc) is None + assert term.getBackgroundColor() == bc + assert term.setCursorPos(offs * 2, offs) is None + assert term.getCursorPos() == (offs * 2, offs) + assert term.write('text with colors') is None +assert term.setBackgroundColor(colors.black) is None +os.sleep(1) +for i in range(3): + assert term.scroll(-2) is None + os.sleep(0.5) +for i in range(6): + assert term.scroll(1) is None + os.sleep(0.25) + +term_step('You must have seen three texts with different colors scrolling') + +assert term.clear() is None +for i in range(1, 10): + assert term.setCursorPos(1, i) is None + assert term.write((str(i) + ' ') * 10) is None +os.sleep(2) +for i in range(2, 10, 2): + assert term.setCursorPos(1, i) is None + assert term.clearLine() is None +os.sleep(2) + +term_step('You must have seen some lines disappearing') + +assert term.clear() is None +assert term.setCursorPos(1, 1) is None +assert term.blit( + 'rainbowrainbow', + b'e14d3ba0000000', + b'fffffffe14d3ba', +) is None +os.sleep(3) + +term_step('You must have seen per-letter colored text') + +assert term.setBackgroundColor(colors.white) is None +assert term.clear() is None +assert term.setCursorPos(1, 1) is None +for i, color in enumerate(colors.iter_colors()): + term.setPaletteColor(color, i / 15, 0, 0) +assert term.blit( + ' redtextappears!', + b'0123456789abcdef', + b'0000000000000000', +) is None +os.sleep(3) + +term_step('You must have seen different shades of red made using palettes') + +print('Test finished successfully') +S115:T<1>1<13>io.write(...){:[1]<83>Detach all monitors +Use advanced computer for colors +Screen will be cleared [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S27:T<1>1<16>G:term:M:getSize{} +R32:T<1>1<23>{:[1]T:[2][51]:[3][19]} +S27:T<1>1<16>G:term:M:isColor{} +R21:T<1>1<12>{:[1]T:[2]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getCursorPos{} +R30:T<1>1<21>{:[1]T:[2][1]:[3][1]} +S37:T<1>1<14>G:term:M:write{:[1]<5>Alpha} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getCursorPos{} +R30:T<1>1<21>{:[1]T:[2][6]:[3][1]} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]F} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>G:term:M:getCursorBlink{} +R21:T<1>1<12>{:[1]T:[2]F} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<23>G:term:M:getCursorBlink{} +R21:T<1>1<12>{:[1]T:[2]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][1]} +R82:T<1>1<73>{:[1]T:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][1]:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][2]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][2]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][4]} +R82:T<1>1<73>{:[1]T:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][4]:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][8]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][8]:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][16]} +R82:T<1>1<73>{:[1]T:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][16]:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][32]} +R83:T<1>1<74>{:[1]T:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][32]:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][64]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][64]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][128]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][128]:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][256]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][256]:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][512]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][512]:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][1024]} +R82:T<1>1<73>{:[1]T:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][1024]:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][2048]} +R82:T<1>1<73>{:[1]T:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][2048]:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][4096]} +R82:T<1>1<73>{:[1]T:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][4096]:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][8192]} +R82:T<1>1<73>{:[1]T:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][8192]:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][16384]} +R82:T<1>1<73>{:[1]T:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +S112:T<1>1<24>G:term:M:setPaletteColor{:[1][16384]:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][32768]} +R85:T<1>1<76>{:[1]T:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +S115:T<1>1<24>G:term:M:setPaletteColor{:[1][32768]:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S90:T<1>1<13>io.write(...){:[1]<58>You must have seen word Alpha with blinking cursor [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<21>G:term:M:setTextColor{:[1][32]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getTextColor{} +R24:T<1>1<15>{:[1]T:[2][32]} +S48:T<1>1<27>G:term:M:setBackgroundColor{:[1][8192]} +R15:T<1>1<7>{:[1]T} +S38:T<1>1<27>G:term:M:getBackgroundColor{} +R26:T<1>1<17>{:[1]T:[2][8192]} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][2]:[2][1]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getCursorPos{} +R30:T<1>1<21>{:[1]T:[2][2]:[3][1]} +S49:T<1>1<14>G:term:M:write{:[1]<16>text with colors} +R15:T<1>1<7>{:[1]T} +S40:T<1>1<21>G:term:M:setTextColor{:[1][16]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getTextColor{} +R24:T<1>1<15>{:[1]T:[2][16]} +S48:T<1>1<27>G:term:M:setBackgroundColor{:[1][4096]} +R15:T<1>1<7>{:[1]T} +S38:T<1>1<27>G:term:M:getBackgroundColor{} +R26:T<1>1<17>{:[1]T:[2][4096]} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][4]:[2][2]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getCursorPos{} +R30:T<1>1<21>{:[1]T:[2][4]:[3][2]} +S49:T<1>1<14>G:term:M:write{:[1]<16>text with colors} +R15:T<1>1<7>{:[1]T} +S43:T<1>1<21>G:term:M:setTextColor{:[1][16384]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getTextColor{} +R27:T<1>1<18>{:[1]T:[2][16384]} +S45:T<1>1<27>G:term:M:setBackgroundColor{:[1][2]} +R15:T<1>1<7>{:[1]T} +S38:T<1>1<27>G:term:M:getBackgroundColor{} +R23:T<1>1<14>{:[1]T:[2][2]} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][6]:[2][3]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<21>G:term:M:getCursorPos{} +R30:T<1>1<21>{:[1]T:[2][6]:[3][3]} +S49:T<1>1<14>G:term:M:write{:[1]<16>text with colors} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][1]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<15>G:term:M:scroll{:[1][-2]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<15>G:term:M:scroll{:[1][-2]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S34:T<1>1<15>G:term:M:scroll{:[1][-2]} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<12>G:os:M:sleep{:[1][0.5]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<15>G:term:M:scroll{:[1][1]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:sleep{:[1][0.25]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<15>G:term:M:scroll{:[1][1]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:sleep{:[1][0.25]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<15>G:term:M:scroll{:[1][1]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:sleep{:[1][0.25]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<15>G:term:M:scroll{:[1][1]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:sleep{:[1][0.25]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<15>G:term:M:scroll{:[1][1]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:sleep{:[1][0.25]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<15>G:term:M:scroll{:[1][1]} +R15:T<1>1<7>{:[1]T} +S33:T<1>1<12>G:os:M:sleep{:[1][0.25]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][1]} +R82:T<1>1<73>{:[1]T:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][1]:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][2]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][2]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][4]} +R82:T<1>1<73>{:[1]T:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][4]:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][8]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][8]:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][16]} +R82:T<1>1<73>{:[1]T:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][16]:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][32]} +R83:T<1>1<74>{:[1]T:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][32]:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][64]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][64]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][128]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][128]:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][256]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][256]:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][512]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][512]:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][1024]} +R82:T<1>1<73>{:[1]T:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][1024]:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][2048]} +R82:T<1>1<73>{:[1]T:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][2048]:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][4096]} +R82:T<1>1<73>{:[1]T:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][4096]:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][8192]} +R82:T<1>1<73>{:[1]T:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][8192]:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][16384]} +R82:T<1>1<73>{:[1]T:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +S112:T<1>1<24>G:term:M:setPaletteColor{:[1][16384]:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][32768]} +R85:T<1>1<76>{:[1]T:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +S115:T<1>1<24>G:term:M:setPaletteColor{:[1][32768]:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S102:T<1>1<13>io.write(...){:[1]<70>You must have seen three texts with different colors scrolling [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>1 1 1 1 1 1 1 1 1 1 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][2]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>2 2 2 2 2 2 2 2 2 2 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][3]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>3 3 3 3 3 3 3 3 3 3 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][4]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>4 4 4 4 4 4 4 4 4 4 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][5]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>5 5 5 5 5 5 5 5 5 5 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][6]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>6 6 6 6 6 6 6 6 6 6 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][7]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>7 7 7 7 7 7 7 7 7 7 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][8]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>8 8 8 8 8 8 8 8 8 8 } +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][9]} +R15:T<1>1<7>{:[1]T} +S63:T<1>1<14>G:term:M:write{:[1]<30>9 9 9 9 9 9 9 9 9 9 } +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][2]} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>G:term:M:clearLine{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][4]} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>G:term:M:clearLine{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][6]} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>G:term:M:clearLine{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][8]} +R15:T<1>1<7>{:[1]T} +S29:T<1>1<18>G:term:M:clearLine{} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][2]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][1]} +R82:T<1>1<73>{:[1]T:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][1]:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][2]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][2]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][4]} +R82:T<1>1<73>{:[1]T:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][4]:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][8]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][8]:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][16]} +R82:T<1>1<73>{:[1]T:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][16]:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][32]} +R83:T<1>1<74>{:[1]T:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][32]:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][64]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][64]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][128]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][128]:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][256]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][256]:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][512]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][512]:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][1024]} +R82:T<1>1<73>{:[1]T:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][1024]:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][2048]} +R82:T<1>1<73>{:[1]T:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][2048]:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][4096]} +R82:T<1>1<73>{:[1]T:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][4096]:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][8192]} +R82:T<1>1<73>{:[1]T:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][8192]:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][16384]} +R82:T<1>1<73>{:[1]T:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +S112:T<1>1<24>G:term:M:setPaletteColor{:[1][16384]:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][32768]} +R85:T<1>1<76>{:[1]T:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +S115:T<1>1<24>G:term:M:setPaletteColor{:[1][32768]:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<13>io.write(...){:[1]<50>You must have seen some lines disappearing [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S90:T<1>1<13>G:term:M:blit{:[1]<14>rainbowrainbow:[2]<14>e14d3ba0000000:[3]<14>fffffffe14d3ba} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][3]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][1]} +R82:T<1>1<73>{:[1]T:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][1]:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][2]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][2]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][4]} +R82:T<1>1<73>{:[1]T:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][4]:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][8]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][8]:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][16]} +R82:T<1>1<73>{:[1]T:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][16]:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][32]} +R83:T<1>1<74>{:[1]T:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][32]:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][64]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][64]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][128]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][128]:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][256]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][256]:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][512]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][512]:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][1024]} +R82:T<1>1<73>{:[1]T:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][1024]:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][2048]} +R82:T<1>1<73>{:[1]T:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][2048]:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][4096]} +R82:T<1>1<73>{:[1]T:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][4096]:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][8192]} +R82:T<1>1<73>{:[1]T:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][8192]:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][16384]} +R82:T<1>1<73>{:[1]T:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +S112:T<1>1<24>G:term:M:setPaletteColor{:[1][16384]:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][32768]} +R85:T<1>1<76>{:[1]T:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +S115:T<1>1<24>G:term:M:setPaletteColor{:[1][32768]:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<13>io.write(...){:[1]<50>You must have seen per-letter colored text [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S45:T<1>1<27>G:term:M:setBackgroundColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<24>G:term:M:setPaletteColor{:[1][1]:[2][0.0]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<24>G:term:M:setPaletteColor{:[1][2]:[2][0.06666666666666667]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<24>G:term:M:setPaletteColor{:[1][4]:[2][0.13333333333333333]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S65:T<1>1<24>G:term:M:setPaletteColor{:[1][8]:[2][0.2]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<24>G:term:M:setPaletteColor{:[1][16]:[2][0.26666666666666666]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S81:T<1>1<24>G:term:M:setPaletteColor{:[1][32]:[2][0.3333333333333333]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S66:T<1>1<24>G:term:M:setPaletteColor{:[1][64]:[2][0.4]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<24>G:term:M:setPaletteColor{:[1][128]:[2][0.4666666666666667]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S82:T<1>1<24>G:term:M:setPaletteColor{:[1][256]:[2][0.5333333333333333]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S67:T<1>1<24>G:term:M:setPaletteColor{:[1][512]:[2][0.6]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<24>G:term:M:setPaletteColor{:[1][1024]:[2][0.6666666666666666]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<24>G:term:M:setPaletteColor{:[1][2048]:[2][0.7333333333333333]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S68:T<1>1<24>G:term:M:setPaletteColor{:[1][4096]:[2][0.8]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S83:T<1>1<24>G:term:M:setPaletteColor{:[1][8192]:[2][0.8666666666666667]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S84:T<1>1<24>G:term:M:setPaletteColor{:[1][16384]:[2][0.9333333333333333]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S69:T<1>1<24>G:term:M:setPaletteColor{:[1][32768]:[2][1.0]:[3][0]:[4][0]} +R15:T<1>1<7>{:[1]T} +S96:T<1>1<13>G:term:M:blit{:[1]<16> redtextappears!:[2]<16>0123456789abcdef:[3]<16>0000000000000000} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][3]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][1]} +R82:T<1>1<73>{:[1]T:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][1]:[2][0.94117647409439]:[3][0.94117647409439]:[4][0.94117647409439]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][2]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][2]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.20000000298023]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][4]} +R82:T<1>1<73>{:[1]T:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][4]:[2][0.89803922176361]:[3][0.49803921580315]:[4][0.84705883264542]} +R15:T<1>1<7>{:[1]T} +S45:T<1>1<27>G:term:M:nativePaletteColor{:[1][8]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +S108:T<1>1<24>G:term:M:setPaletteColor{:[1][8]:[2][0.60000002384186]:[3][0.69803923368454]:[4][0.94901961088181]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][16]} +R82:T<1>1<73>{:[1]T:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][16]:[2][0.87058824300766]:[3][0.87058824300766]:[4][0.42352941632271]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][32]} +R83:T<1>1<74>{:[1]T:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][32]:[2][0.49803921580315]:[3][0.80000001192093]:[4][0.098039217293262]} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<27>G:term:M:nativePaletteColor{:[1][64]} +R82:T<1>1<73>{:[1]T:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +S109:T<1>1<24>G:term:M:setPaletteColor{:[1][64]:[2][0.94901961088181]:[3][0.69803923368454]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][128]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][128]:[2][0.29803922772408]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][256]} +R82:T<1>1<73>{:[1]T:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][256]:[2][0.60000002384186]:[3][0.60000002384186]:[4][0.60000002384186]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<27>G:term:M:nativePaletteColor{:[1][512]} +R82:T<1>1<73>{:[1]T:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +S110:T<1>1<24>G:term:M:setPaletteColor{:[1][512]:[2][0.29803922772408]:[3][0.60000002384186]:[4][0.69803923368454]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][1024]} +R82:T<1>1<73>{:[1]T:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][1024]:[2][0.69803923368454]:[3][0.40000000596046]:[4][0.89803922176361]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][2048]} +R82:T<1>1<73>{:[1]T:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][2048]:[2][0.20000000298023]:[3][0.40000000596046]:[4][0.80000001192093]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][4096]} +R82:T<1>1<73>{:[1]T:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][4096]:[2][0.49803921580315]:[3][0.40000000596046]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S48:T<1>1<27>G:term:M:nativePaletteColor{:[1][8192]} +R82:T<1>1<73>{:[1]T:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +S111:T<1>1<24>G:term:M:setPaletteColor{:[1][8192]:[2][0.34117648005486]:[3][0.65098041296005]:[4][0.30588236451149]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][16384]} +R82:T<1>1<73>{:[1]T:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +S112:T<1>1<24>G:term:M:setPaletteColor{:[1][16384]:[2][0.80000001192093]:[3][0.29803922772408]:[4][0.29803922772408]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:nativePaletteColor{:[1][32768]} +R85:T<1>1<76>{:[1]T:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +S115:T<1>1<24>G:term:M:setPaletteColor{:[1][32768]:[2][0.066666670143604]:[3][0.066666670143604]:[4][0.066666670143604]} +R15:T<1>1<7>{:[1]T} +S49:T<1>1<27>G:term:M:setBackgroundColor{:[1][32768]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<21>G:term:M:setTextColor{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S46:T<1>1<21>G:term:M:setCursorPos{:[1][1]:[2][1]} +R15:T<1>1<7>{:[1]T} +S39:T<1>1<23>G:term:M:setCursorBlink{:[1]T} +R15:T<1>1<7>{:[1]T} +S102:T<1>1<13>io.write(...){:[1]<70>You must have seen different shades of red made using palettes [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/textutils.txt b/tests/proto/m_1.20.1/cc_1.108.3/textutils.txt new file mode 100644 index 0000000..14e59ba --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/textutils.txt @@ -0,0 +1,81 @@ +R1472:0[5]{:[1]<12>textutils.py:[0]<2>py}<12>textutils.py<1415>from cc import colors, textutils + + +assert textutils.slowWrite('write ') is None +assert textutils.slowWrite('write ', 5) is None +assert textutils.slowPrint('print') is None +assert textutils.slowPrint('print', 5) is None + +assert textutils.formatTime(0) == '12:00 AM' +assert textutils.formatTime(0, True) == '0:00' + +table = [ + colors.red, + ['Planet', 'Distance', 'Mass'], + colors.gray, + ['Mercury', '0.387', '0.055'], + colors.lightGray, + ['Venus', '0.723', '0.815'], + colors.green, + ['Earth', '1.000', '1.000'], + colors.red, + ['Mars', '1.524', '0.107'], + colors.orange, + ['Jupiter', '5.203', '318'], + colors.yellow, + ['Saturn', '9.537', '95'], + colors.cyan, + ['Uranus', '19.191', '14.5'], + colors.blue, + ['Neptune', '30.069', '17'], + colors.white, +] + +assert textutils.tabulate(*table) is None + +lines = textutils.pagedPrint(''' +Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Suspendisse feugiat diam et velit aliquam, nec porttitor eros facilisis. +Nulla facilisi. +Sed eget dui vel tellus aliquam fermentum. +Aliquam sed lorem congue, dignissim nulla in, porta diam. +Aliquam erat volutpat. +'''.strip()) +assert isinstance(lines, int) +assert lines > 0 + +assert textutils.pagedTabulate(*table[:-1], *table[2:-1], *table[2:]) is None + +assert textutils.complete('co', ['command', 'row', 'column']) == [ + 'mmand', 'lumn'] + +print('Test finished successfully') +S52:T<1>1<23>G:textutils:M:slowWrite{:[1]<6>write :[2]N} +R15:T<1>1<7>{:[1]T} +S54:T<1>1<23>G:textutils:M:slowWrite{:[1]<6>write :[2][5]} +R15:T<1>1<7>{:[1]T} +S51:T<1>1<23>G:textutils:M:slowPrint{:[1]<5>print:[2]N} +R15:T<1>1<7>{:[1]T} +S53:T<1>1<23>G:textutils:M:slowPrint{:[1]<5>print:[2][5]} +R15:T<1>1<7>{:[1]T} +S47:T<1>1<24>G:textutils:M:formatTime{:[1][0]:[2]N} +R31:T<1>1<22>{:[1]T:[2]<8>12:00 AM} +S47:T<1>1<24>G:textutils:M:formatTime{:[1][0]:[2]T} +R27:T<1>1<18>{:[1]T:[2]<4>0:00} +S515:T<1>1<22>G:textutils:M:tabulate{:[1][16384]:[2]{:[1]<6>Planet:[2]<8>Distance:[3]<4>Mass}:[3][128]:[4]{:[1]<7>Mercury:[2]<5>0.387:[3]<5>0.055}:[5][256]:[6]{:[1]<5>Venus:[2]<5>0.723:[3]<5>0.815}:[7][8192]:[8]{:[1]<5>Earth:[2]<5>1.000:[3]<5>1.000}:[9][16384]:[10]{:[1]<4>Mars:[2]<5>1.524:[3]<5>0.107}:[11][2]:[12]{:[1]<7>Jupiter:[2]<5>5.203:[3]<3>318}:[13][16]:[14]{:[1]<6>Saturn:[2]<5>9.537:[3]<2>95}:[15][512]:[16]{:[1]<6>Uranus:[2]<6>19.191:[3]<4>14.5}:[17][2048]:[18]{:[1]<7>Neptune:[2]<6>30.069:[3]<2>17}:[19][1]} +R15:T<1>1<7>{:[1]T} +S318:T<1>1<24>G:textutils:M:pagedPrint{:[1]<269>Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Suspendisse feugiat diam et velit aliquam, nec porttitor eros facilisis. +Nulla facilisi. +Sed eget dui vel tellus aliquam fermentum. +Aliquam sed lorem congue, dignissim nulla in, porta diam. +Aliquam erat volutpat.:[2]N} +R23:T<1>1<14>{:[1]T:[2][9]} +S1370:T<1>1<27>G:textutils:M:pagedTabulate{:[1][16384]:[2]{:[1]<6>Planet:[2]<8>Distance:[3]<4>Mass}:[3][128]:[4]{:[1]<7>Mercury:[2]<5>0.387:[3]<5>0.055}:[5][256]:[6]{:[1]<5>Venus:[2]<5>0.723:[3]<5>0.815}:[7][8192]:[8]{:[1]<5>Earth:[2]<5>1.000:[3]<5>1.000}:[9][16384]:[10]{:[1]<4>Mars:[2]<5>1.524:[3]<5>0.107}:[11][2]:[12]{:[1]<7>Jupiter:[2]<5>5.203:[3]<3>318}:[13][16]:[14]{:[1]<6>Saturn:[2]<5>9.537:[3]<2>95}:[15][512]:[16]{:[1]<6>Uranus:[2]<6>19.191:[3]<4>14.5}:[17][2048]:[18]{:[1]<7>Neptune:[2]<6>30.069:[3]<2>17}:[19][128]:[20]{:[1]<7>Mercury:[2]<5>0.387:[3]<5>0.055}:[21][256]:[22]{:[1]<5>Venus:[2]<5>0.723:[3]<5>0.815}:[23][8192]:[24]{:[1]<5>Earth:[2]<5>1.000:[3]<5>1.000}:[25][16384]:[26]{:[1]<4>Mars:[2]<5>1.524:[3]<5>0.107}:[27][2]:[28]{:[1]<7>Jupiter:[2]<5>5.203:[3]<3>318}:[29][16]:[30]{:[1]<6>Saturn:[2]<5>9.537:[3]<2>95}:[31][512]:[32]{:[1]<6>Uranus:[2]<6>19.191:[3]<4>14.5}:[33][2048]:[34]{:[1]<7>Neptune:[2]<6>30.069:[3]<2>17}:[35][128]:[36]{:[1]<7>Mercury:[2]<5>0.387:[3]<5>0.055}:[37][256]:[38]{:[1]<5>Venus:[2]<5>0.723:[3]<5>0.815}:[39][8192]:[40]{:[1]<5>Earth:[2]<5>1.000:[3]<5>1.000}:[41][16384]:[42]{:[1]<4>Mars:[2]<5>1.524:[3]<5>0.107}:[43][2]:[44]{:[1]<7>Jupiter:[2]<5>5.203:[3]<3>318}:[45][16]:[46]{:[1]<6>Saturn:[2]<5>9.537:[3]<2>95}:[47][512]:[48]{:[1]<6>Uranus:[2]<6>19.191:[3]<4>14.5}:[49][2048]:[50]{:[1]<7>Neptune:[2]<6>30.069:[3]<2>17}:[51][1]} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/turtle.txt b/tests/proto/m_1.20.1/cc_1.108.3/turtle.txt new file mode 100644 index 0000000..c238e23 --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/turtle.txt @@ -0,0 +1,690 @@ +R6264:0[5]{:[1]<9>turtle.py:[0]<2>py}<9>turtle.py<6215>from cc import LuaException, import_file, turtle, peripheral + +_lib = import_file('_lib.py', __file__) +assert_raises, step = _lib.assert_raises, _lib.step + +flimit = turtle.getFuelLimit() +assert isinstance(flimit, int) +assert flimit > 0 + +flevel = turtle.getFuelLevel() +assert isinstance(flevel, int) +assert 0 <= flevel <= flimit + +assert turtle.select(2) is None +assert turtle.getSelectedSlot() == 2 +with assert_raises(LuaException): + turtle.select(0) +assert turtle.select(1) is None +assert turtle.getSelectedSlot() == 1 + +step('Put 3 coals into slot 1') + +assert turtle.getItemCount() == 3 +assert turtle.getItemCount(1) == 3 + +assert turtle.getItemDetail() == { + 'count': 3, + 'name': 'minecraft:coal', +} +assert turtle.getItemDetail(1) == { + 'count': 3, + 'name': 'minecraft:coal', +} + +assert turtle.getItemSpace() == 61 +assert turtle.getItemSpace(1) == 61 + +assert turtle.refuel(1) is None + +assert turtle.getFuelLevel() > flevel +flevel = turtle.getFuelLevel() +assert turtle.getItemCount() == 2 + +assert turtle.refuel() is None + +assert turtle.getFuelLevel() > flevel +assert turtle.getItemCount() == 0 + +with assert_raises(LuaException): + turtle.refuel(1) +with assert_raises(LuaException): + turtle.refuel() + +step('Remove blocks in front/below/above turtle') + +assert turtle.detect() is False +assert turtle.detectUp() is False +assert turtle.detectDown() is False + +assert turtle.inspect() is None +assert turtle.inspectUp() is None +assert turtle.inspectDown() is None + +step('Put cobblestone blocks in front/below/above turtle') + +assert turtle.detect() is True +assert turtle.detectUp() is True +assert turtle.detectDown() is True + +for c in [ + turtle.inspect(), + turtle.inspectUp(), + turtle.inspectDown() +]: + assert isinstance(c, dict) + assert c['name'] == 'minecraft:cobblestone' + +assert turtle.select(1) is None +assert turtle.getItemCount() == 0 +assert turtle.equipLeft() is None + +assert turtle.select(2) is None +assert turtle.getItemCount() == 0 +assert turtle.equipRight() is None + +if ( + turtle.getItemCount(1) != 0 + or turtle.getItemCount(2) != 0 +): + step('Remove all items from slots 1 and 2') + +assert turtle.select(1) is None +if turtle.getItemDetail(1) != { + 'count': 1, + 'name': 'minecraft:diamond_pickaxe', +}: + step('Put fresh diamond pickaxe at slot 1') + +assert turtle.equipLeft() is None + +assert turtle.dig() is True +assert turtle.dig() is False +assert turtle.digUp() is True +assert turtle.digUp() is False +assert turtle.digDown() is True +assert turtle.digDown() is False + +assert turtle.getItemCount() == 3 + +assert turtle.forward() is None +assert turtle.back() is None +assert turtle.up() is None +assert turtle.down() is None +assert turtle.turnLeft() is None +assert turtle.turnRight() is None + +assert turtle.place() is None +with assert_raises(LuaException, 'Cannot place block here'): + turtle.place() +assert turtle.placeUp() is None +with assert_raises(LuaException, 'Cannot place block here'): + turtle.placeUp() +assert turtle.placeDown() is None +with assert_raises(LuaException, 'No items to place'): + turtle.placeDown() + +step('Put 3 cobblestone blocks to slot 1') + +assert turtle.getItemCount(1) == 3 +assert turtle.getItemCount(2) == 0 + +assert turtle.compare() is True +assert turtle.compareUp() is True +assert turtle.compareDown() is True + +assert turtle.select(2) is None + +assert turtle.compare() is False +assert turtle.compareUp() is False +assert turtle.compareDown() is False + +assert turtle.select(1) is None + +assert turtle.transferTo(2, 1) is None +assert turtle.getItemCount(1) == 2 +assert turtle.getItemCount(2) == 1 +assert turtle.compareTo(2) is True + +assert turtle.transferTo(2) is None +assert turtle.getItemCount(1) == 0 +assert turtle.getItemCount(2) == 3 +assert turtle.compareTo(2) is False + +assert turtle.select(2) is None +assert turtle.transferTo(1) is None +assert turtle.select(1) is None +assert turtle.dig() is True +assert turtle.digUp() is True +assert turtle.digDown() is True +assert turtle.getItemCount() == 6 + +assert turtle.drop(1) is None +assert turtle.dropUp(1) is None +assert turtle.dropDown(1) is None +assert turtle.getItemCount() == 3 +assert turtle.drop() is None +assert turtle.getItemCount() == 0 +with assert_raises(LuaException, 'No items to drop'): + turtle.drop() + +step( + 'Collect dropped cobblestone\n' + 'Drop stack of sticks right in front of the turtle\n' + 'Its better to build 1-block room then throw sticks there', +) + +assert turtle.suck(1) is True +assert turtle.getItemCount() == 1 +assert turtle.suck() is True +assert turtle.getItemCount() == 64 +assert turtle.suck() is False +assert turtle.drop() is None +assert turtle.getItemCount() == 0 + +step( + 'Collect dropped sticks\n' + 'Drop stack of sticks right below the turtle\n' + 'Its better to build 1-block room then throw sticks there', +) + +assert turtle.suckDown(1) is True +assert turtle.getItemCount() == 1 +assert turtle.suckDown() is True +assert turtle.getItemCount() == 64 +assert turtle.suckDown() is False +assert turtle.dropDown() is None +assert turtle.getItemCount() == 0 + +step( + 'Collect dropped sticks\n' + 'Drop stack of sticks right above the turtle\n' + 'Its better to build 1-block room then throw sticks there', +) + +assert turtle.suckUp(1) is True +assert turtle.getItemCount() == 1 +assert turtle.suckUp() is True +assert turtle.getItemCount() == 64 +assert turtle.suckUp() is False +assert turtle.dropUp() is None +assert turtle.getItemCount() == 0 + + +def craft1(): + return turtle.craft() + + +def craft2(): + c = peripheral.wrap('right') + return c.craft() + + +step('Put crafting table into slot 1') +assert turtle.select(1) is None +assert turtle.equipRight() is None + +for craft_fn in craft1, craft2: + step( + 'Clean inventory of turtle\n' + 'Put 8 cobblestones into slot 1', + ) + + assert turtle.select(1) is None + with assert_raises(LuaException, 'No matching recipes'): + craft_fn() + for idx in [2, 3, 5, 7, 9, 10, 11]: + assert turtle.transferTo(idx, 1) is None + assert craft_fn() is None + with assert_raises(LuaException, 'No matching recipes'): + craft_fn() + assert turtle.getItemDetail() == { + 'count': 1, + 'name': 'minecraft:furnace', + } + +print('Test finished successfully') +S257:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<9>turtle.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S34:T<1>1<23>G:turtle:M:getFuelLimit{} +R28:T<1>1<19>{:[1]T:[2][100000]} +S34:T<1>1<23>G:turtle:M:getFuelLevel{} +R25:T<1>1<16>{:[1]T:[2][948]} +S35:T<1>1<17>G:turtle:M:select{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]T} +S37:T<1>1<26>G:turtle:M:getSelectedSlot{} +R23:T<1>1<14>{:[1]T:[2][2]} +S35:T<1>1<17>G:turtle:M:select{:[1][0]} +R70:T<1>1<61>{:[1]F:[2]<46>function: 20f31942: Slot number 0 out of range} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S37:T<1>1<26>G:turtle:M:getSelectedSlot{} +R23:T<1>1<14>{:[1]T:[2][1]} +S63:T<1>1<13>io.write(...){:[1]<31>Put 3 coals into slot 1 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][3]} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][1]} +R23:T<1>1<14>{:[1]T:[2][3]} +S40:T<1>1<24>G:turtle:M:getItemDetail{:[1]N} +R60:T<1>1<51>{:[1]T:[2]{:<4>name<14>minecraft:coal:<5>count[3]}} +S42:T<1>1<24>G:turtle:M:getItemDetail{:[1][1]} +R60:T<1>1<51>{:[1]T:[2]{:<4>name<14>minecraft:coal:<5>count[3]}} +S39:T<1>1<23>G:turtle:M:getItemSpace{:[1]N} +R24:T<1>1<15>{:[1]T:[2][61]} +S41:T<1>1<23>G:turtle:M:getItemSpace{:[1][1]} +R24:T<1>1<15>{:[1]T:[2][61]} +S35:T<1>1<17>G:turtle:M:refuel{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S34:T<1>1<23>G:turtle:M:getFuelLevel{} +R26:T<1>1<17>{:[1]T:[2][1028]} +S34:T<1>1<23>G:turtle:M:getFuelLevel{} +R26:T<1>1<17>{:[1]T:[2][1028]} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][2]} +S33:T<1>1<17>G:turtle:M:refuel{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S34:T<1>1<23>G:turtle:M:getFuelLevel{} +R26:T<1>1<17>{:[1]T:[2][1188]} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S35:T<1>1<17>G:turtle:M:refuel{:[1][1]} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No items to combust} +S33:T<1>1<17>G:turtle:M:refuel{:[1]N} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No items to combust} +S81:T<1>1<13>io.write(...){:[1]<49>Remove blocks in front/below/above turtle [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S28:T<1>1<17>G:turtle:M:detect{} +R21:T<1>1<12>{:[1]T:[2]F} +S30:T<1>1<19>G:turtle:M:detectUp{} +R21:T<1>1<12>{:[1]T:[2]F} +S32:T<1>1<21>G:turtle:M:detectDown{} +R21:T<1>1<12>{:[1]T:[2]F} +S29:T<1>1<18>G:turtle:M:inspect{} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No block to inspect} +S31:T<1>1<20>G:turtle:M:inspectUp{} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No block to inspect} +S33:T<1>1<22>G:turtle:M:inspectDown{} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No block to inspect} +S90:T<1>1<13>io.write(...){:[1]<58>Put cobblestone blocks in front/below/above turtle [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S28:T<1>1<17>G:turtle:M:detect{} +R21:T<1>1<12>{:[1]T:[2]T} +S30:T<1>1<19>G:turtle:M:detectUp{} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<21>G:turtle:M:detectDown{} +R21:T<1>1<12>{:[1]T:[2]T} +S29:T<1>1<18>G:turtle:M:inspect{} +R167:T<1>1<157>{:[1]T:[2]T:[3]{:<5>state{}:<4>name<21>minecraft:cobblestone:<4>tags{:<26>minecraft:mineable/pickaxeT:<17>forge:cobblestoneT:<24>forge:cobblestone/normalT}}} +S31:T<1>1<20>G:turtle:M:inspectUp{} +R167:T<1>1<157>{:[1]T:[2]T:[3]{:<5>state{}:<4>name<21>minecraft:cobblestone:<4>tags{:<26>minecraft:mineable/pickaxeT:<17>forge:cobblestoneT:<24>forge:cobblestone/normalT}}} +S33:T<1>1<22>G:turtle:M:inspectDown{} +R167:T<1>1<157>{:[1]T:[2]T:[3]{:<5>state{}:<4>name<21>minecraft:cobblestone:<4>tags{:<26>minecraft:mineable/pickaxeT:<17>forge:cobblestoneT:<24>forge:cobblestone/normalT}}} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S31:T<1>1<20>G:turtle:M:equipLeft{} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<17>G:turtle:M:select{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S32:T<1>1<21>G:turtle:M:equipRight{} +R21:T<1>1<12>{:[1]T:[2]T} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][1]} +R23:T<1>1<14>{:[1]T:[2][1]} +S75:T<1>1<13>io.write(...){:[1]<43>Remove all items from slots 1 and 2 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S42:T<1>1<24>G:turtle:M:getItemDetail{:[1][1]} +R15:T<1>1<7>{:[1]T} +S75:T<1>1<13>io.write(...){:[1]<43>Put fresh diamond pickaxe at slot 1 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S31:T<1>1<20>G:turtle:M:equipLeft{} +R21:T<1>1<12>{:[1]T:[2]T} +S25:T<1>1<14>G:turtle:M:dig{} +R21:T<1>1<12>{:[1]T:[2]T} +S25:T<1>1<14>G:turtle:M:dig{} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>Nothing to dig here} +S27:T<1>1<16>G:turtle:M:digUp{} +R21:T<1>1<12>{:[1]T:[2]T} +S27:T<1>1<16>G:turtle:M:digUp{} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>Nothing to dig here} +S29:T<1>1<18>G:turtle:M:digDown{} +R21:T<1>1<12>{:[1]T:[2]T} +S29:T<1>1<18>G:turtle:M:digDown{} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>Nothing to dig here} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][3]} +S29:T<1>1<18>G:turtle:M:forward{} +R21:T<1>1<12>{:[1]T:[2]T} +S26:T<1>1<15>G:turtle:M:back{} +R21:T<1>1<12>{:[1]T:[2]T} +S24:T<1>1<13>G:turtle:M:up{} +R21:T<1>1<12>{:[1]T:[2]T} +S26:T<1>1<15>G:turtle:M:down{} +R21:T<1>1<12>{:[1]T:[2]T} +S30:T<1>1<19>G:turtle:M:turnLeft{} +R21:T<1>1<12>{:[1]T:[2]T} +S31:T<1>1<20>G:turtle:M:turnRight{} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<16>G:turtle:M:place{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<16>G:turtle:M:place{:[1]N} +R52:T<1>1<43>{:[1]T:[2]F:[3]<23>Cannot place block here} +S34:T<1>1<18>G:turtle:M:placeUp{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S34:T<1>1<18>G:turtle:M:placeUp{:[1]N} +R52:T<1>1<43>{:[1]T:[2]F:[3]<23>Cannot place block here} +S36:T<1>1<20>G:turtle:M:placeDown{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S36:T<1>1<20>G:turtle:M:placeDown{:[1]N} +R46:T<1>1<37>{:[1]T:[2]F:[3]<17>No items to place} +S74:T<1>1<13>io.write(...){:[1]<42>Put 3 cobblestone blocks to slot 1 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][1]} +R23:T<1>1<14>{:[1]T:[2][3]} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][2]} +R23:T<1>1<14>{:[1]T:[2][0]} +S29:T<1>1<18>G:turtle:M:compare{} +R21:T<1>1<12>{:[1]T:[2]T} +S31:T<1>1<20>G:turtle:M:compareUp{} +R21:T<1>1<12>{:[1]T:[2]T} +S33:T<1>1<22>G:turtle:M:compareDown{} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<17>G:turtle:M:select{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]T} +S29:T<1>1<18>G:turtle:M:compare{} +R21:T<1>1<12>{:[1]T:[2]F} +S31:T<1>1<20>G:turtle:M:compareUp{} +R21:T<1>1<12>{:[1]T:[2]F} +S33:T<1>1<22>G:turtle:M:compareDown{} +R21:T<1>1<12>{:[1]T:[2]F} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][2]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][1]} +R23:T<1>1<14>{:[1]T:[2][2]} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][2]} +R23:T<1>1<14>{:[1]T:[2][1]} +S38:T<1>1<20>G:turtle:M:compareTo{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]T} +S44:T<1>1<21>G:turtle:M:transferTo{:[1][2]:[2]N} +R21:T<1>1<12>{:[1]T:[2]T} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][1]} +R23:T<1>1<14>{:[1]T:[2][0]} +S41:T<1>1<23>G:turtle:M:getItemCount{:[1][2]} +R23:T<1>1<14>{:[1]T:[2][3]} +S38:T<1>1<20>G:turtle:M:compareTo{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]F} +S35:T<1>1<17>G:turtle:M:select{:[1][2]} +R21:T<1>1<12>{:[1]T:[2]T} +S44:T<1>1<21>G:turtle:M:transferTo{:[1][1]:[2]N} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S25:T<1>1<14>G:turtle:M:dig{} +R21:T<1>1<12>{:[1]T:[2]T} +S27:T<1>1<16>G:turtle:M:digUp{} +R21:T<1>1<12>{:[1]T:[2]T} +S29:T<1>1<18>G:turtle:M:digDown{} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][6]} +S33:T<1>1<15>G:turtle:M:drop{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<17>G:turtle:M:dropUp{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S37:T<1>1<19>G:turtle:M:dropDown{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][3]} +S31:T<1>1<15>G:turtle:M:drop{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S31:T<1>1<15>G:turtle:M:drop{:[1]N} +R45:T<1>1<36>{:[1]T:[2]F:[3]<16>No items to drop} +S175:T<1>1<13>io.write(...){:[1]<142>Collect dropped cobblestone +Drop stack of sticks right in front of the turtle +Its better to build 1-block room then throw sticks there [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S33:T<1>1<15>G:turtle:M:suck{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][1]} +S31:T<1>1<15>G:turtle:M:suck{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R24:T<1>1<15>{:[1]T:[2][64]} +S31:T<1>1<15>G:turtle:M:suck{:[1]N} +R45:T<1>1<36>{:[1]T:[2]F:[3]<16>No items to take} +S31:T<1>1<15>G:turtle:M:drop{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S164:T<1>1<13>io.write(...){:[1]<131>Collect dropped sticks +Drop stack of sticks right below the turtle +Its better to build 1-block room then throw sticks there [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S37:T<1>1<19>G:turtle:M:suckDown{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][1]} +S35:T<1>1<19>G:turtle:M:suckDown{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R24:T<1>1<15>{:[1]T:[2][64]} +S35:T<1>1<19>G:turtle:M:suckDown{:[1]N} +R45:T<1>1<36>{:[1]T:[2]F:[3]<16>No items to take} +S35:T<1>1<19>G:turtle:M:dropDown{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S164:T<1>1<13>io.write(...){:[1]<131>Collect dropped sticks +Drop stack of sticks right above the turtle +Its better to build 1-block room then throw sticks there [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S35:T<1>1<17>G:turtle:M:suckUp{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][1]} +S33:T<1>1<17>G:turtle:M:suckUp{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R24:T<1>1<15>{:[1]T:[2][64]} +S33:T<1>1<17>G:turtle:M:suckUp{:[1]N} +R45:T<1>1<36>{:[1]T:[2]F:[3]<16>No items to take} +S33:T<1>1<17>G:turtle:M:dropUp{:[1]N} +R21:T<1>1<12>{:[1]T:[2]T} +S39:T<1>1<23>G:turtle:M:getItemCount{:[1]N} +R23:T<1>1<14>{:[1]T:[2][0]} +S70:T<1>1<13>io.write(...){:[1]<38>Put crafting table into slot 1 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<21>G:turtle:M:equipRight{} +R21:T<1>1<12>{:[1]T:[2]T} +S96:T<1>1<13>io.write(...){:[1]<64>Clean inventory of turtle +Put 8 cobblestones into slot 1 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<16>G:turtle:M:craft{:[1][64]} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No matching recipes} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][2]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][3]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][5]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][7]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][9]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S47:T<1>1<21>G:turtle:M:transferTo{:[1][10]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S47:T<1>1<21>G:turtle:M:transferTo{:[1][11]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<16>G:turtle:M:craft{:[1][64]} +R21:T<1>1<12>{:[1]T:[2]T} +S35:T<1>1<16>G:turtle:M:craft{:[1][64]} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No matching recipes} +S40:T<1>1<24>G:turtle:M:getItemDetail{:[1]N} +R63:T<1>1<54>{:[1]T:[2]{:<4>name<17>minecraft:furnace:<5>count[1]}} +S96:T<1>1<13>io.write(...){:[1]<64>Clean inventory of turtle +Put 8 cobblestones into slot 1 [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S35:T<1>1<17>G:turtle:M:select{:[1][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S45:T<1>1<22>G:peripheral:M:getType{:[1]<5>right} +R32:T<1>1<23>{:[1]T:[2]<9>workbench} +S62:T<1>1<19>G:peripheral:M:call{:[1]<5>right:[2]<5>craft:[3][64]} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No matching recipes} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][2]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][3]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][5]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][7]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S46:T<1>1<21>G:turtle:M:transferTo{:[1][9]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S47:T<1>1<21>G:turtle:M:transferTo{:[1][10]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S47:T<1>1<21>G:turtle:M:transferTo{:[1][11]:[2][1]} +R21:T<1>1<12>{:[1]T:[2]T} +S45:T<1>1<22>G:peripheral:M:getType{:[1]<5>right} +R32:T<1>1<23>{:[1]T:[2]<9>workbench} +S62:T<1>1<19>G:peripheral:M:call{:[1]<5>right:[2]<5>craft:[3][64]} +R21:T<1>1<12>{:[1]T:[2]T} +S45:T<1>1<22>G:peripheral:M:getType{:[1]<5>right} +R32:T<1>1<23>{:[1]T:[2]<9>workbench} +S62:T<1>1<19>G:peripheral:M:call{:[1]<5>right:[2]<5>craft:[3][64]} +R48:T<1>1<39>{:[1]T:[2]F:[3]<19>No matching recipes} +S40:T<1>1<24>G:turtle:M:getItemDetail{:[1]N} +R63:T<1>1<54>{:[1]T:[2]{:<4>name<17>minecraft:furnace:<5>count[1]}} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/turtle_attack.txt b/tests/proto/m_1.20.1/cc_1.108.3/turtle_attack.txt new file mode 100644 index 0000000..4ea918a --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/turtle_attack.txt @@ -0,0 +1,166 @@ +R800:0[5]{:[1]<16>turtle_attack.py:[0]<2>py}<16>turtle_attack.py<736>from cc import import_file, turtle + +_lib = import_file('_lib.py', __file__) + + +_lib.step( + 'NOTE: this test is unreliable\n' + 'Build 1x1x1 stone cage in front of turtle\n' + 'Spawn here a chicken', +) + +assert turtle.attack() is True +assert type(turtle.attack()) is bool +assert turtle.attack() is False + +_lib.step( + 'Build 1x1x1 stone cage below turtle\n' + 'Spawn here a chicken', +) + +assert turtle.attackDown() is True +assert type(turtle.attackDown()) is bool +assert turtle.attackDown() is False + +_lib.step( + 'Build 1x1x1 stone cage above turtle\n' + 'Spawn here a chicken', +) + +assert turtle.attackUp() is True +assert type(turtle.attackUp()) is bool +assert turtle.attackUp() is False + +print('Test finished successfully') +S265:T<1>1<215>local p, rel = ... +if rel ~= nil then +p = fs.combine(fs.getDir(rel), p) +end +if not fs.exists(p) then return nil end +if fs.isDir(p) then return nil end +f = fs.open(p, "r") +local src = f.readAll() +f.close() +return src{:[1]<7>_lib.py:[2]<16>turtle_attack.py} +R1870:T<1>1<1859>{:[1]T:[2]<1842>from contextlib import contextmanager +from cc import os + + +@contextmanager +def assert_raises(etype, message=None): + try: + yield + except Exception as e: + assert isinstance(e, etype), repr(e) + if message is not None: + assert e.args == (message, ) + else: + raise AssertionError(f'Exception of type {etype} was not raised') + + +@contextmanager +def assert_takes_time(at_least, at_most): + t = os.epoch('utc') / 1000 + yield + dt = os.epoch('utc') / 1000 - t + # print(at_least, '<=', dt, '<=', at_most) + assert at_least <= dt <= at_most + + +class AnyInstanceOf: + def __init__(self, cls): + self.c = cls + + def __eq__(self, other): + return isinstance(other, self.c) + + +def step(text): + input(f'{text} [enter]') + + +def term_step(text): + from cc import colors, term + + for color in colors.iter_colors(): + r, g, b = term.nativePaletteColor(color) + term.setPaletteColor(color, r, g, b) + term.setBackgroundColor(colors.black) + term.setTextColor(colors.white) + term.clear() + term.setCursorPos(1, 1) + term.setCursorBlink(True) + step(text) + + +def _computer_peri(place_thing, thing): + from cc import peripheral + + side = 'left' + + step( + f'Place {place_thing} on {side} side of computer\n' + "Don't turn it on!", + ) + + c = peripheral.wrap(side) + assert c is not None + + assert c.isOn() is False + assert isinstance(c.getID(), int) + assert c.getLabel() is None + assert c.turnOn() is None + + step(f'{thing.capitalize()} must be turned on now') + + assert c.shutdown() is None + + step(f'{thing.capitalize()} must shutdown') + + step(f'Now turn on {thing} manually and enter some commands') + + assert c.reboot() is None + + step(f'{thing.capitalize()} must reboot') + + print('Test finished successfully')} +S133:T<1>1<13>io.write(...){:[1]<100>NOTE: this test is unreliable +Build 1x1x1 stone cage in front of turtle +Spawn here a chicken [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S28:T<1>1<17>G:turtle:M:attack{} +R21:T<1>1<12>{:[1]T:[2]T} +S28:T<1>1<17>G:turtle:M:attack{} +R51:T<1>1<42>{:[1]T:[2]F:[3]<22>Nothing to attack here} +S28:T<1>1<17>G:turtle:M:attack{} +R51:T<1>1<42>{:[1]T:[2]F:[3]<22>Nothing to attack here} +S96:T<1>1<13>io.write(...){:[1]<64>Build 1x1x1 stone cage below turtle +Spawn here a chicken [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S32:T<1>1<21>G:turtle:M:attackDown{} +R21:T<1>1<12>{:[1]T:[2]T} +S32:T<1>1<21>G:turtle:M:attackDown{} +R51:T<1>1<42>{:[1]T:[2]F:[3]<22>Nothing to attack here} +S32:T<1>1<21>G:turtle:M:attackDown{} +R51:T<1>1<42>{:[1]T:[2]F:[3]<22>Nothing to attack here} +S96:T<1>1<13>io.write(...){:[1]<64>Build 1x1x1 stone cage above turtle +Spawn here a chicken [enter]} +R15:T<1>1<7>{:[1]T} +S22:T<1>1<11>G:io:M:read{} +R23:T<1>1<14>{:[1]T:[2]<0>} +S30:T<1>1<19>G:turtle:M:attackUp{} +R21:T<1>1<12>{:[1]T:[2]T} +S30:T<1>1<19>G:turtle:M:attackUp{} +R51:T<1>1<42>{:[1]T:[2]F:[3]<22>Nothing to attack here} +S30:T<1>1<19>G:turtle:M:attackUp{} +R51:T<1>1<42>{:[1]T:[2]F:[3]<22>Nothing to attack here} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/proto/m_1.20.1/cc_1.108.3/window.txt b/tests/proto/m_1.20.1/cc_1.108.3/window.txt new file mode 100644 index 0000000..7fcd16e --- /dev/null +++ b/tests/proto/m_1.20.1/cc_1.108.3/window.txt @@ -0,0 +1,83 @@ +R627:0[5]{:[1]<9>window.py:[0]<2>py}<9>window.py<579>from cc import colors, term, os, window + + +win = window.create(term.current(), 15, 5, 5, 5, False) +assert win.getPosition() == (15, 5) +assert win.getSize() == (5, 5) + +win.setBackgroundColor(colors.red) +win.clear() +win.setVisible(True) + +os.sleep(1) + +win.setVisible(False) +win.setCursorPos(1, 1) +win.setTextColor(colors.yellow) +win.write('*********') +win.setVisible(True) + +os.sleep(1) + +term.clear() + +os.sleep(1) + +win.redraw() +assert win.getLine(1) == ('*****', b'44444', b'eeeee') + +# draws immediately +win.reposition(21, 5) +win.reposition(27, 5) + +print('Test finished successfully') +S123:T<1>1<103>return(function(n,...)local o,e=term.current();if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>2} +R21:T<1>1<12>{:[1]T:[2]T} +S169:T<1>1<107>return(function(n,...)local o,e=window.create(...);if o then temp[n]=o;return true;end;return o,e;end)(...){:[1]<1>3:[2]X1>2:[3][15]:[4][5]:[5][5]:[6][5]:[7]F} +R21:T<1>1<12>{:[1]T:[2]T} +S82:T<1>1<63>return(function(n,...)return temp[n].getPosition(...);end)(...){:[1]<1>3} +R31:T<1>1<22>{:[1]T:[2][15]:[3][5]} +S78:T<1>1<59>return(function(n,...)return temp[n].getSize(...);end)(...){:[1]<1>3} +R30:T<1>1<21>{:[1]T:[2][5]:[3][5]} +S100:T<1>1<70>return(function(n,...)return temp[n].setBackgroundColor(...);end)(...){:[1]<1>3:[2][16384]} +R15:T<1>1<7>{:[1]T} +S76:T<1>1<57>return(function(n,...)return temp[n].clear(...);end)(...){:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<62>return(function(n,...)return temp[n].setVisible(...);end)(...){:[1]<1>3:[2]T} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][1]} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<62>return(function(n,...)return temp[n].setVisible(...);end)(...){:[1]<1>3:[2]F} +R15:T<1>1<7>{:[1]T} +S97:T<1>1<64>return(function(n,...)return temp[n].setCursorPos(...);end)(...){:[1]<1>3:[2][1]:[3][1]} +R15:T<1>1<7>{:[1]T} +S91:T<1>1<64>return(function(n,...)return temp[n].setTextColor(...);end)(...){:[1]<1>3:[2][16]} +R15:T<1>1<7>{:[1]T} +S92:T<1>1<57>return(function(n,...)return temp[n].write(...);end)(...){:[1]<1>3:[2]<9>*********} +R15:T<1>1<7>{:[1]T} +S86:T<1>1<62>return(function(n,...)return temp[n].setVisible(...);end)(...){:[1]<1>3:[2]T} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][1]} +R15:T<1>1<7>{:[1]T} +S25:T<1>1<14>G:term:M:clear{} +R15:T<1>1<7>{:[1]T} +S30:T<1>1<12>G:os:M:sleep{:[1][1]} +R15:T<1>1<7>{:[1]T} +S77:T<1>1<58>return(function(n,...)return temp[n].redraw(...);end)(...){:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S85:T<1>1<59>return(function(n,...)return temp[n].getLine(...);end)(...){:[1]<1>3:[2][1]} +R52:T<1>1<43>{:[1]T:[2]<5>*****:[3]<5>44444:[4]<5>eeeee} +S111:T<1>1<62>return(function(n,...)return temp[n].reposition(...);end)(...){:[1]<1>3:[2][21]:[3][5]:[4]N:[5]N:[6]N} +R15:T<1>1<7>{:[1]T} +S111:T<1>1<62>return(function(n,...)return temp[n].reposition(...);end)(...){:[1]<1>3:[2][27]:[3][5]:[4]N:[5]N:[6]N} +R15:T<1>1<7>{:[1]T} +S58:T<1>1<13>io.write(...){:[1]<26>Test finished successfully} +R15:T<1>1<7>{:[1]T} +S32:T<1>1<13>io.write(...){:[1]<1> +} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<23>local n=...;temp[n]=nil{:[1]<1>3} +R15:T<1>1<7>{:[1]T} +S42:T<1>1<23>local n=...;temp[n]=nil{:[1]<1>2} +R15:T<1>1<7>{:[1]T} +S2:CN diff --git a/tests/serialization.lua b/tests/serialization.lua new file mode 100644 index 0000000..02dd877 --- /dev/null +++ b/tests/serialization.lua @@ -0,0 +1,150 @@ +local serialize +do + local function s_rec(v, tracking) + local t = type(v) + if v == nil then + return 'N' + elseif v == false then + return 'F' + elseif v == true then + return 'T' + elseif t == 'number' then + return '\[' .. tostring(v) .. '\]' + elseif t == 'string' then + return string.format('<%u>', #v) .. v + elseif t == 'table' then + if tracking[v] ~= nil then + error('Cannot serialize table with recursive entries', 0) + end + tracking[v] = true + local r = '{' + for k, x in pairs(v) do + r = r .. ':' .. s_rec(k, tracking) .. s_rec(x, tracking) + end + return r .. '}' + else + error('Cannot serialize type ' .. t, 0) + end + local tp = type(t) + end + serialize = function(v) return s_rec(v, {}) end +end + +local deserialize +do + local function d_rec(s, idx) + local tok = s:sub(idx, idx) + idx = idx + 1 + if tok == 'N' then + return nil, idx + elseif tok == 'F' then + return false, idx + elseif tok == 'T' then + return true, idx + elseif tok == '\[' then + local newidx = s:find('\]', idx, true) + return tonumber(s:sub(idx, newidx - 1)), newidx + 1 + elseif tok == '<' then + local newidx = s:find('>', idx, true) + local slen = tonumber(s:sub(idx, newidx - 1)) + if slen == 0 then + return '', newidx + 1 + end + return s:sub(newidx + 1, newidx + slen), newidx + slen + 1 + elseif tok == '{' then + local r = {} + while true do + tok = s:sub(idx, idx) + idx = idx + 1 + if tok == '}' then break end + local key, value + key, idx = d_rec(s, idx) + value, idx = d_rec(s, idx) + r[key] = value + end + return r, idx + else + error('Unknown token ' .. tok, 0) + end + end + deserialize = function(s) + local r = d_rec(s, 1) + return r + end +end + + +print(serialize(nil)) +assert(deserialize(serialize(nil)) == nil) + + +local roundtrip_vals = { + true, + false, + 0, + -1, + 1, + 1e6, + 1.5, + 2.4e-9, + tonumber('inf'), + tonumber('-inf'), + '', + 'string', + '\n\r\0', + '\0', + '2', +} + + +for _, v in ipairs(roundtrip_vals) do + print(serialize(v)) + assert(v == deserialize(serialize(v))) +end + + +print(serialize(tonumber('nan'))) +assert(tostring(deserialize(serialize(tonumber('nan')))) == 'nan') + + +function areTablesEqual(a, b) + assert(type(a) == 'table') + assert(type(b) == 'table') + for k, v in pairs(a) do + if type(v) == 'table' then + if not areTablesEqual(v, b[k]) then return false end + else + if b[k] ~= v then return false end + end + end + for k, v in pairs(b) do + if type(v) == 'table' then + if not areTablesEqual(v, a[k]) then return false end + else + if a[k] ~= v then return false end + end + end + return true +end + + +local roundtrip_tables = { + {}, + {[2]=4}, + {a=1, b=true, c={}, d={x=8}}, + {1, 2, 3}, + {1}, + {'abc'}, + {[1]='a', [2]='b', [3]='c'}, + {'a', 'b', 'c'}, +} + + +for _, v in ipairs(roundtrip_tables) do + print(serialize(v)) + assert(areTablesEqual(v, deserialize(serialize(v)))) +end + +print('ALL OK') + +print(serialize({true, false, 'Position is negative'})) diff --git a/tests/test_proto.py b/tests/test_proto.py new file mode 100644 index 0000000..2a27743 --- /dev/null +++ b/tests/test_proto.py @@ -0,0 +1,65 @@ +from collections import deque +from pathlib import Path + +import pytest + +import computercraft.server +import computercraft.sess + + +@pytest.fixture(autouse=True) +def _patch(monkeypatch): + monkeypatch.setattr( + computercraft.sess, 'python_version', + lambda: '') + monkeypatch.setattr( + computercraft.sess.CCSession, '_drop_command', + computercraft.sess.CCSession._sorted_drop_command) + + +_proto_folder = (Path(__file__).parent / 'proto') + + +@pytest.mark.parametrize( + 'logfile', + _proto_folder.glob('**/*.txt'), + ids=lambda p: str(p.relative_to(_proto_folder))) +def test_proto(logfile): + sbuf = deque() + with computercraft.sess.patch_std_files(): + pgen = computercraft.server.protocol( + sbuf.append, + oc='/oc_' in str(logfile.relative_to(_proto_folder))) + pgen.send(None) + + with logfile.open('rb') as lf: + + def read_frame(): + flen = [] + while (b := lf.read(1)) != b':': + flen.append(b) + flen = int(b''.join(flen)) + frame = lf.read(flen) + assert len(frame) == flen + assert lf.read(1) == b'\n' + return frame + + while True: + t = lf.read(1) + if t == b'': + break + elif t == b'R': + frame = read_frame() + try: + pgen.send(frame) + except StopIteration: + if frame == b'D': + break + pytest.fail( + 'Protocol prematurely finished on frame ' + repr(frame)) + elif t == b'S': + assert read_frame() == sbuf.popleft() + else: + raise ValueError('Bad prefix ' + repr(t)) + + assert len(sbuf) == 0 diff --git a/tests/test_serialization.py b/tests/test_serialization.py new file mode 100644 index 0000000..a88030b --- /dev/null +++ b/tests/test_serialization.py @@ -0,0 +1,44 @@ +from math import inf, nan, isnan + +import pytest + +from computercraft.ser import serialize, deserialize + + +@pytest.mark.parametrize('v', [ + None, + True, + False, + 0, + -1, + 1, + 1e6, + 1.5, + 2.4e-9, + inf, + -inf, + b'', + b'string', + b'\n\r\0', + b'\0', + b'2', + {}, + {2: 4}, + {b'a': 1, b'b': None, b'c': {}, b'd': {b'x': 8}}, +]) +def test_roundtrip(v): + assert v == deserialize(serialize(v, 'ascii')) + + +def test_nan(): + assert isnan(deserialize(serialize(nan, 'ascii'))) + + +@pytest.mark.parametrize('a,b', [ + ([1], {1: 1}), + ([1, 2, 3], {1: 1, 2: 2, 3: 3}), + ([b'abc'], {1: b'abc'}), + ([b'a', b'b', b'c'], {1: b'a', 2: b'b', 3: b'c'}), +]) +def test_oneway(a, b): + assert b == deserialize(serialize(a, 'ascii'))