From 1b5c81679e2413dfa17241ab564926ce310bb1a4 Mon Sep 17 00:00:00 2001 From: neumond Date: Tue, 21 Jul 2020 01:32:37 +0300 Subject: [PATCH] Rewrite lua_eval result processing --- computercraft/back.lua | 14 ++- computercraft/lua.py | 2 + computercraft/rproc.py | 149 ++++++++++++++++++++++++++ computercraft/ser.py | 10 +- computercraft/server.py | 16 +-- computercraft/sess.py | 21 ++-- computercraft/subapis/_pkg.py | 13 ++- computercraft/subapis/colors.py | 12 +-- computercraft/subapis/commands.py | 17 +-- computercraft/subapis/disk.py | 23 ++--- computercraft/subapis/fs.py | 155 +++++++++++++--------------- computercraft/subapis/gps.py | 5 +- computercraft/subapis/help.py | 11 +- computercraft/subapis/keys.py | 7 +- computercraft/subapis/mixins.py | 38 +++---- computercraft/subapis/multishell.py | 15 ++- computercraft/subapis/os.py | 35 +++---- computercraft/subapis/paintutils.py | 16 ++- computercraft/subapis/peripheral.py | 105 +++++++++---------- computercraft/subapis/pocket.py | 5 +- computercraft/subapis/rednet.py | 36 +++---- computercraft/subapis/redstone.py | 23 ++--- computercraft/subapis/settings.py | 46 +++++---- computercraft/subapis/shell.py | 39 ++++--- computercraft/subapis/term.py | 4 +- computercraft/subapis/textutils.py | 13 ++- computercraft/subapis/turtle.py | 68 +++++------- computercraft/subapis/window.py | 15 +-- examples/_lib.py | 10 +- examples/test_fs.py | 41 ++------ tests/serialization.lua | 2 + 31 files changed, 541 insertions(+), 425 deletions(-) diff --git a/computercraft/back.lua b/computercraft/back.lua index 2e2cc41..a7ebc77 100644 --- a/computercraft/back.lua +++ b/computercraft/back.lua @@ -6,6 +6,7 @@ local url = 'http://127.0.0.1:4343/' local tasks = {} local filters = {} local ycounts = {} +local coparams = {} ws = http.websocket(url..'ws/') if ws == false then @@ -117,12 +118,13 @@ while true do ws_send{ action='task_result', task_id=msg.task_id, - result={fn()}, + result={fn(table.unpack(msg.params or {}))}, yields=0, } else tasks[msg.task_id] = coroutine.create(fn) ycounts[msg.task_id] = 0 + coparams[msg.task_id] = msg.params or {} end end elseif msg.action == 'drop' then @@ -130,6 +132,7 @@ while true do tasks[task_id] = nil filters[task_id] = nil ycounts[task_id] = nil + coparams[task_id] = nil end elseif msg.action == 'sub' then event_sub[msg.event] = true @@ -152,7 +155,13 @@ while true do 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)} + local r + if coparams[task_id] ~= nil then + r = {coroutine.resume(tasks[task_id], table.unpack(coparams[task_id]))} + coparams[task_id] = nil + else + r = {coroutine.resume(tasks[task_id], event, p1, p2, p3, p4, p5)} + end if coroutine.status(tasks[task_id]) == 'dead' then ws_send{ action='task_result', @@ -175,6 +184,7 @@ while true do tasks[task_id] = nil filters[task_id] = nil ycounts[task_id] = nil + coparams[task_id] = nil end end diff --git a/computercraft/lua.py b/computercraft/lua.py index 80e27b3..02bb409 100644 --- a/computercraft/lua.py +++ b/computercraft/lua.py @@ -58,6 +58,8 @@ def lua_value(v): return 'true' if isinstance(v, str): return lua_string(v) + if isinstance(v, bytes): + return lua_string(v.decode('latin1')) if isinstance(v, (int, float)): return str(v) if isinstance(v, list): diff --git a/computercraft/rproc.py b/computercraft/rproc.py index ae84a9d..c10e394 100644 --- a/computercraft/rproc.py +++ b/computercraft/rproc.py @@ -1,6 +1,155 @@ from .errors import LuaException +def lua_table_to_list(x, length: int = None): + 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()) >= 1 + if length is not None: + assert max(x.keys()) <= length + else: + length = max(x.keys()) + return [x.get(i + 1) for i in range(length)] + + +class ResultProc: + def __init__(self, result): + self._v = result + self._i = 1 + + def forward(self): + self._i += 1 + + def back(self): + self._i -= 1 + + def peek(self): + return self._v.get(self._i) + + 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_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('latin1') + + def take_unicode(self): + return self.take_bytes().decode('utf-8') + + def take_dict(self, keys=None): + x = self.take() + assert isinstance(x, dict) + if keys is None: + return x + return TableProc(x, keys) + + def take_list(self, length: int = None): + return lua_table_to_list(self.take_dict(), length) + + def check_bool_error(self): + success = self.take_bool() + if not success: + raise LuaException(self.take_string()) + + def check_nil_error(self): + if self.peek() is None: + self.forward() + raise LuaException(self.take_string()) + + def bool_error_exclude(self, exc_msg): + success = self.take_bool() + if success: + return True + msg = self.take_string() + if msg == exc_msg: + return False + raise LuaException(msg) + + def take_option_int(self): + if self.peek() is None: + return self.take_none() + return self.take_int() + + def take_option_bytes(self): + if self.peek() is None: + return self.take_none() + return self.take_bytes() + + def take_option_string(self): + if self.peek() is None: + return self.take_none() + return self.take_string() + + def take_option_unicode(self): + if self.peek() is None: + return self.take_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(map(lambda v: isinstance(v, bytes), x)) + return [v.decode('latin1') for v in x] + + def take_list_of_ints(self): + x = self.take_list() + assert all(map(lambda v: isinstance(v, int), 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 + + +class TableProc(ResultProc): + def __init__(self, result, keys): + self._v = result + self._keys = keys + self._i = 0 + + def peek(self): + return self._v.get(self._keys[self._i]) + + def coro(result): assert isinstance(result, list) assert len(result) >= 1 diff --git a/computercraft/ser.py b/computercraft/ser.py index 4be74e3..04bb65d 100644 --- a/computercraft/ser.py +++ b/computercraft/ser.py @@ -21,6 +21,8 @@ def serialize(v: Any) -> bytes: return b'T' elif isinstance(v, (int, float)): return '[{}]'.format(v).encode(_ENC) + elif isinstance(v, bytes): + return '<{}>'.format(len(v)) + v elif isinstance(v, str): v = v.encode(_ENC) return '<{}>'.format(len(v)).encode(_ENC) + v @@ -56,7 +58,7 @@ def _deserialize(b: bytes, _idx: int) -> Tuple[Any, int]: elif tok == 60: # < newidx = b.index(b'>', _idx) ln = int(b[_idx:newidx].decode(_ENC)) - return b[newidx + 1:newidx + 1 + ln].decode(_ENC), newidx + 1 + ln + return b[newidx + 1:newidx + 1 + ln], newidx + 1 + ln elif tok == 123: # { r = {} while True: @@ -67,12 +69,6 @@ def _deserialize(b: bytes, _idx: int) -> Tuple[Any, int]: key, _idx = _deserialize(b, _idx) value, _idx = _deserialize(b, _idx) r[key] = value - if r: - for i in range(1, len(r) + 1): - if i not in r: - break - else: - r = [r[i + 1] for i in range(len(r))] return r, _idx else: raise ValueError diff --git a/computercraft/server.py b/computercraft/server.py index 9a23884..a43b32e 100644 --- a/computercraft/server.py +++ b/computercraft/server.py @@ -25,7 +25,7 @@ class CCApplication(web.Application): async def _launch_program(self, ws): async for msg in self._bin_messages(ws): msg = ser.deserialize(msg) - if msg['action'] != 'run': + if msg[b'action'] != b'run': await ws.send_bytes(ser.serialize({ 'action': 'close', 'error': 'protocol error', @@ -36,9 +36,9 @@ class CCApplication(web.Application): sys.__stdout__.write('ws send ' + repr(data) + '\n') asyncio.create_task(ws.send_bytes(data)) - sess = CCSession(msg['computer'], sender) - if msg['args']: - sess.run_program(msg['args'][0]) + sess = CCSession(msg[b'computer'], sender) + if msg[b'args']: + sess.run_program(msg[b'args'][1].decode('latin1')) else: sess.run_repl() return sess @@ -51,10 +51,10 @@ class CCApplication(web.Application): if sess is not None: async for msg in self._bin_messages(ws): msg = ser.deserialize(msg) - 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']) + if msg[b'action'] == b'event': + sess.on_event(msg[b'event'].decode('latin1'), msg[b'params']) + elif msg[b'action'] == b'task_result': + sess.on_task_result(msg[b'task_id'].decode('latin1'), msg[b'result']) else: await ws.send_bytes(ser.serialize({ 'action': 'close', diff --git a/computercraft/sess.py b/computercraft/sess.py index 60fa2fe..c750a17 100644 --- a/computercraft/sess.py +++ b/computercraft/sess.py @@ -73,22 +73,22 @@ class StdFileProxy: raise RuntimeError( "Computercraft environment doesn't support " "stdin readline method with parameter") - return rproc.string(eval_lua( + return eval_lua( return_lua_call('io.read') - )) + '\n' + ).take_string() + '\n' def write(self, s): if _is_global_greenlet(): return self._native.write(s) else: if self._err: - return rproc.nil(eval_lua( + return eval_lua( lua_call('io.stderr:write', s) - )) + ).take_none() else: - return rproc.nil(eval_lua( + return eval_lua( lua_call('io.write', s) - )) + ).take_none() def fileno(self): if _is_global_greenlet(): @@ -147,9 +147,10 @@ def eval_lua(lua_code, immediate=False): }) result = get_current_session()._server_greenlet.switch(request) # debug('{} → {}'.format(lua_code, repr(result))) + rp = rproc.ResultProc(result) if not immediate: - result = rproc.coro(result) - return result + rp.check_bool_error() + return rp @contextmanager @@ -342,7 +343,7 @@ class CCSession: def run_program(self, program): def _run_program(): - p, code = eval_lua(''' + rp = 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 @@ -351,6 +352,8 @@ local code = f.readAll() f.close() return p, code '''.lstrip().format(lua_string(program))) + p = rp.take_string() + code = rp.take_string() cc = compile(code, p, 'exec') exec(cc, {'__file__': p}) diff --git a/computercraft/subapis/_pkg.py b/computercraft/subapis/_pkg.py index 5da475d..cf13fee 100644 --- a/computercraft/subapis/_pkg.py +++ b/computercraft/subapis/_pkg.py @@ -2,7 +2,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 @@ -26,7 +25,7 @@ def import_file(path: str, relative_to: str = None): lua_string(relative_to), path_expr, ) - source = option_string(eval_lua(''' + source = eval_lua(''' local p = {} if not fs.exists(p) then return nil end if fs.isDir(p) then return nil end @@ -36,7 +35,7 @@ f.close() return src '''.lstrip().format( path_expr, - ))) + )).take_option_string() if source is None: raise ImportError('File not found: {}'.format(path)) cc = compile(source, mod.__name__, 'exec') @@ -45,16 +44,16 @@ return src def is_commands() -> bool: - return boolean(eval_lua('return commands ~= nil')) + return eval_lua('return commands ~= nil').take_bool() def is_multishell() -> bool: - return boolean(eval_lua('return multishell ~= nil')) + return eval_lua('return multishell ~= nil').take_bool() def is_turtle() -> bool: - return boolean(eval_lua('return turtle ~= nil')) + return eval_lua('return turtle ~= nil').take_bool() def is_pocket() -> bool: - return boolean(eval_lua('return pocket ~= nil')) + return eval_lua('return pocket ~= nil').take_bool() diff --git a/computercraft/subapis/colors.py b/computercraft/subapis/colors.py index 208026e..ba6b230 100644 --- a/computercraft/subapis/colors.py +++ b/computercraft/subapis/colors.py @@ -1,6 +1,5 @@ from typing import Tuple -from ..rproc import boolean, integer, tuple3_number from ..sess import eval_lua_method_factory @@ -55,23 +54,24 @@ black = 0x8000 # combine, subtract and test are mostly for redstone.setBundledOutput def combine(*colors: int) -> int: - return integer(method('combine', *colors)) + return method('combine', *colors).take_int() def subtract(color_set: int, *colors: int) -> int: - return integer(method('subtract', color_set, *colors)) + return method('subtract', color_set, *colors).take_int() def test(colors: int, color: int) -> bool: - return boolean(method('test', colors, color)) + return method('test', colors, color).take_bool() def packRGB(r: float, g: float, b: float) -> int: - return integer(method('packRGB', r, g, b)) + return method('packRGB', r, g, b).take_int() def unpackRGB(rgb: int) -> Tuple[float, float, float]: - return tuple3_number(method('unpackRGB', rgb)) + rp = method('unpackRGB', rgb) + return tuple(rp.take_number() for _ in range(3)) # use these chars for term.blit diff --git a/computercraft/subapis/commands.py b/computercraft/subapis/commands.py index 6c834ac..3bc949e 100644 --- a/computercraft/subapis/commands.py +++ b/computercraft/subapis/commands.py @@ -1,10 +1,8 @@ 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.') @@ -18,20 +16,25 @@ __all__ = ( def exec(command: str) -> Tuple[bool, List[str], Optional[int]]: - return command_result(method('exec', command)) + rp = method('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 array_string(method('list')) + return method('list').take_list_of_strings() def getBlockPosition() -> Tuple[int, int, int]: - return tuple3_integer(method('getBlockPosition')) + rp = method('getBlockPosition') + return tuple(rp.take_int() for _ in range(3)) def getBlockInfo(x: int, y: int, z: int) -> dict: - return any_dict(method('getBlockInfo', x, y, z)) + return method('getBlockInfo', x, y, z).take_dict() 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)) + return method('getBlockInfos', x1, y1, z1, x2, y2, z2).take_list() diff --git a/computercraft/subapis/disk.py b/computercraft/subapis/disk.py index b9afd48..ff0f858 100644 --- a/computercraft/subapis/disk.py +++ b/computercraft/subapis/disk.py @@ -1,6 +1,5 @@ from typing import Optional, Union -from ..rproc import boolean, nil, option_integer, option_string, option_string_bool from ..sess import eval_lua_method_factory @@ -23,44 +22,44 @@ __all__ = ( def isPresent(side: str) -> bool: - return boolean(method('isPresent', side)) + return method('isPresent', side).take_bool() def hasData(side: str) -> bool: - return boolean(method('hasData', side)) + return method('hasData', side).take_bool() def getMountPath(side: str) -> Optional[str]: - return option_string(method('getMountPath', side)) + return method('getMountPath', side).take_option_string() def setLabel(side: str, label: str): - return nil(method('setLabel', side, label)) + return method('setLabel', side, label).take_none() def getLabel(side: str) -> Optional[str]: - return option_string(method('getLabel', side)) + return method('getLabel', side).take_option_string() def getID(side: str) -> Optional[int]: - return option_integer(method('getID', side)) + return method('getID', side).take_option_int() def hasAudio(side: str) -> bool: - return boolean(method('hasAudio', side)) + return method('hasAudio', side).take_bool() def getAudioTitle(side: str) -> Optional[Union[bool, str]]: - return option_string_bool(method('getAudioTitle', side)) + return method('getAudioTitle', side).take_option_string_bool() def playAudio(side: str): - return nil(method('playAudio', side)) + return method('playAudio', side).take_none() def stopAudio(side: str): - return nil(method('stopAudio', side)) + return method('stopAudio', side).take_none() def eject(side: str): - return nil(method('eject', side)) + return method('eject', side).take_none() diff --git a/computercraft/subapis/fs.py b/computercraft/subapis/fs.py index f1f7415..5d50648 100644 --- a/computercraft/subapis/fs.py +++ b/computercraft/subapis/fs.py @@ -1,55 +1,31 @@ -import builtins from contextlib import contextmanager from typing import Optional, List from .base import BaseSubAPI -from ..errors import LuaException -from ..lua import lua_call, lua_args, lua_string -from ..rproc import boolean, string, integer, nil, array_string, option_string, fact_scheme_dict -from ..sess import eval_lua, eval_lua_method_factory, lua_context_object - - -attribute = fact_scheme_dict({ - 'created': integer, - 'modification': integer, - 'isDir': boolean, - 'size': integer, -}, {}) +from ..lua import lua_call +from ..sess import eval_lua_method_factory, lua_context_object 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) + rp = self._method('seek', whence, offset) + rp.check_nil_error() + return rp.take_int() -class ReadHandle(BaseSubAPI): - def _decode(self, b): - return b.decode('utf-8') - - def _read(self, name, params, val): - code = ''' -local s = {}.{}({}) -if s == nil then return nil end -s = s:gsub('.', function(c) return string.format('%02X', string.byte(c)) end) -return s -'''.lstrip().format( - self.get_expr_code(), name, lua_args(*params), - ) - return self._decode(bytes.fromhex(val(eval_lua(code)))) +class ReadMixin: + def _take(self, rp): + raise NotImplementedError def read(self, count: int = 1) -> Optional[str]: - return self._read('read', (count, ), option_string) + return self._take(self._method('read', count)) - def readLine(self) -> Optional[str]: - return self._read('readLine', (), option_string) + def readLine(self, withTrailing: bool = False) -> Optional[str]: + return self._take(self._method('readLine', withTrailing)) - def readAll(self) -> str: - return self._read('readAll', (), string) + def readAll(self) -> Optional[str]: + return self._take(self._method('readAll')) def __iter__(self): return self @@ -61,39 +37,38 @@ return s return line -class BinaryReadHandle(ReadHandle, SeekMixin): - def _decode(self, b): - return b - - -class WriteHandle(BaseSubAPI): - def _encode(self, s): - return s.encode('utf-8') - - def _write(self, name, text, val): - code = ''' -local s = {} -s = s:gsub('..', function(cc) return string.char(tonumber(cc, 16)) end) -return {}.{}(s) -'''.lstrip().format( - lua_string(self._encode(text).hex()), - self.get_expr_code(), name, - ) - return val(eval_lua(code)) +class WriteMixin: + def _put(self, t): + raise NotImplementedError def write(self, text: str): - return nil(self._method('write', text)) - - def writeLine(self, text: str): - return nil(self._method('writeLine', text)) + return self._method('write', self._put(text)).take_none() def flush(self): - return nil(self._method('flush')) + return self._method('flush').take_none() -class BinaryWriteHandle(WriteHandle, SeekMixin): - def _encode(self, s): - return s +class ReadHandle(ReadMixin, BaseSubAPI): + def _take(self, rp): + return rp.take_option_unicode() + + +class BinaryReadHandle(ReadMixin, SeekMixin, BaseSubAPI): + def _take(self, rp): + return rp.take_option_bytes() + + +class WriteHandle(WriteMixin, BaseSubAPI): + def _put(self, t: str) -> bytes: + return t.encode('utf-8') + + def writeLine(self, text: str): + return self.write(text + '\n') + + +class BinaryWriteHandle(WriteMixin, SeekMixin, BaseSubAPI): + def _put(self, b: bytes) -> bytes: + return b method = eval_lua_method_factory('fs.') @@ -124,55 +99,55 @@ __all__ = ( def list(path: str) -> List[str]: - return array_string(method('list', path)) + return method('list', path).take_list_of_strings() def exists(path: str) -> bool: - return boolean(method('exists', path)) + return method('exists', path).take_bool() def isDir(path: str) -> bool: - return boolean(method('isDir', path)) + return method('isDir', path).take_bool() def isReadOnly(path: str) -> bool: - return boolean(method('isReadOnly', path)) + return method('isReadOnly', path).take_bool() def getDrive(path: str) -> Optional[str]: - return option_string(method('getDrive', path)) + return method('getDrive', path).take_option_string() def getSize(path: str) -> int: - return integer(method('getSize', path)) + return method('getSize', path).take_int() def getFreeSpace(path: str) -> int: - return integer(method('getFreeSpace', path)) + return method('getFreeSpace', path).take_int() def getCapacity(path: str) -> int: - return integer(method('getCapacity', path)) + return method('getCapacity', path).take_int() def makeDir(path: str): - return nil(method('makeDir', path)) + return method('makeDir', path).take_none() def move(fromPath: str, toPath: str): - return nil(method('move', fromPath, toPath)) + return method('move', fromPath, toPath).take_none() def copy(fromPath: str, toPath: str): - return nil(method('copy', fromPath, toPath)) + return method('copy', fromPath, toPath).take_none() def delete(path: str): - return nil(method('delete', path)) + return method('delete', path).take_none() def combine(basePath: str, localPath: str) -> str: - return string(method('combine', basePath, localPath)) + return method('combine', basePath, localPath).take_string() @contextmanager @@ -199,27 +174,39 @@ def open(path: str, mode: str): def find(wildcard: str) -> List[str]: - return array_string(method('find', wildcard)) + return method('find', wildcard).take_list_of_strings() def getDir(path: str) -> str: - return string(method('getDir', path)) + return method('getDir', path).take_string() def getName(path: str) -> str: - return string(method('getName', path)) + return method('getName', path).take_string() def isDriveRoot(path: str) -> bool: - return boolean(method('isDriveRoot', path)) + return method('isDriveRoot', path).take_bool() def complete( partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None, ) -> List[str]: - return array_string(method( - 'complete', partialName, path, includeFiles, includeDirs)) + return method( + 'complete', partialName, path, includeFiles, includeDirs, + ).take_list_of_strings() def attributes(path: str) -> dict: - return attribute(method('attributes', path)) + tp = method('attributes', path).take_dict(( + b'created', + b'modification', + b'isDir', + b'size', + )) + r = {} + r['created'] = tp.take_int() + r['modification'] = tp.take_int() + r['isDir'] = tp.take_bool() + r['size'] = tp.take_int() + return r diff --git a/computercraft/subapis/gps.py b/computercraft/subapis/gps.py index 9a770e9..c6c987e 100644 --- a/computercraft/subapis/gps.py +++ b/computercraft/subapis/gps.py @@ -19,4 +19,7 @@ CHANNEL_GPS = 65534 def locate(timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[LuaNum, LuaNum, LuaNum]]: - return option_tuple3_number(method('locate', timeout, debug)) + rp = method('locate', timeout, debug) + if rp.peek() is None: + return None + return tuple(rp.take_number() for _ in range(3)) diff --git a/computercraft/subapis/help.py b/computercraft/subapis/help.py index 3b551f1..3bd11c5 100644 --- a/computercraft/subapis/help.py +++ b/computercraft/subapis/help.py @@ -1,6 +1,5 @@ from typing import Optional, List -from ..rproc import string, nil, array_string, option_string from ..sess import eval_lua_method_factory @@ -17,20 +16,20 @@ __all__ = ( def path() -> str: - return string(method('path')) + return method('path').take_string() def setPath(path: str): - return nil(method('setPath', path)) + return method('setPath', path).take_none() def lookup(topic: str) -> Optional[str]: - return option_string(method('lookup', topic)) + return method('lookup', topic).take_option_string() def topics() -> List[str]: - return array_string(method('topics')) + return method('topics').take_list_of_strings() def completeTopic(topicPrefix: str) -> List[str]: - return array_string(method('completeTopic', topicPrefix)) + return method('completeTopic', topicPrefix).take_list_of_strings() diff --git a/computercraft/subapis/keys.py b/computercraft/subapis/keys.py index cf5e6ef..25fb6d2 100644 --- a/computercraft/subapis/keys.py +++ b/computercraft/subapis/keys.py @@ -1,7 +1,6 @@ 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 @@ -17,12 +16,12 @@ __all__ = ( def getCode(name: str) -> Optional[int]: # replaces properties # keys.space → keys.getCode('space') - return option_integer(eval_lua(''' + return eval_lua(''' if type(keys[{key}]) == 'number' then return keys[{key}] end -return nil'''.format(key=lua_string(name)))) +return nil'''.format(key=lua_string(name))).take_option_int() def getName(code: int) -> Optional[str]: - return option_string(method('getName', code)) + return method('getName', code).take_option_string() diff --git a/computercraft/subapis/mixins.py b/computercraft/subapis/mixins.py index c8843a5..7c32bba 100644 --- a/computercraft/subapis/mixins.py +++ b/computercraft/subapis/mixins.py @@ -1,60 +1,62 @@ 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)) + return self._method('write', text).take_none() def blit(self, text: str, textColors: str, backgroundColors: str): - return nil(self._method('blit', text, textColors, backgroundColors)) + return self._method('blit', text, textColors, backgroundColors).take_none() def clear(self): - return nil(self._method('clear')) + return self._method('clear').take_none() def clearLine(self): - return nil(self._method('clearLine')) + return self._method('clearLine').take_none() def getCursorPos(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getCursorPos')) + rp = self._method('getCursorPos') + return tuple(rp.take_int() for _ in range(2)) def setCursorPos(self, x: int, y: int): - return nil(self._method('setCursorPos', x, y)) + return self._method('setCursorPos', x, y).take_none() def getCursorBlink(self) -> bool: - return boolean(self._method('getCursorBlink')) + return self._method('getCursorBlink').take_bool() def setCursorBlink(self, value: bool): - return nil(self._method('setCursorBlink', value)) + return self._method('setCursorBlink', value).take_none() def isColor(self) -> bool: - return boolean(self._method('isColor')) + return self._method('isColor').take_bool() def getSize(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getSize')) + rp = self._method('getSize') + return tuple(rp.take_int() for _ in range(2)) def scroll(self, lines: int): - return nil(self._method('scroll', lines)) + return self._method('scroll', lines).take_none() def setTextColor(self, colorID: int): - return nil(self._method('setTextColor', colorID)) + return self._method('setTextColor', colorID).take_none() def getTextColor(self) -> int: - return integer(self._method('getTextColor')) + return self._method('getTextColor').take_int() def setBackgroundColor(self, colorID: int): - return nil(self._method('setBackgroundColor', colorID)) + return self._method('setBackgroundColor', colorID).take_none() def getBackgroundColor(self) -> int: - return integer(self._method('getBackgroundColor')) + return self._method('getBackgroundColor').take_int() def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]: - return tuple3_number(self._method('getPaletteColor', colorID)) + rp = self._method('getPaletteColor', colorID) + return tuple(rp.take_number() for _ in range(3)) def setPaletteColor(self, colorID: int, r: float, g: float, b: float): - return nil(self._method('setPaletteColor', colorID, r, g, b)) + return self._method('setPaletteColor', colorID, r, g, b).take_none() class TermTarget(LuaExpr): diff --git a/computercraft/subapis/multishell.py b/computercraft/subapis/multishell.py index 2b5ddf8..b584b88 100644 --- a/computercraft/subapis/multishell.py +++ b/computercraft/subapis/multishell.py @@ -1,6 +1,5 @@ from typing import Optional -from ..rproc import integer, nil, boolean, option_string from ..sess import eval_lua_method_factory @@ -19,28 +18,28 @@ __all__ = ( def getCurrent() -> int: - return integer(method('getCurrent')) + return method('getCurrent').take_int() def getCount() -> int: - return integer(method('getCount')) + return method('getCount').take_int() def launch(environment: dict, programPath: str, *args: str) -> int: - return integer(method('launch', environment, programPath, *args)) + return method('launch', environment, programPath, *args).take_int() def setTitle(tabID: int, title: str): - return nil(method('setTitle', tabID, title)) + return method('setTitle', tabID, title).take_none() def getTitle(tabID: int) -> Optional[str]: - return option_string(method('getTitle', tabID)) + return method('getTitle', tabID).take_option_string() def setFocus(tabID: int) -> bool: - return boolean(method('setFocus', tabID)) + return method('setFocus', tabID).take_bool() def getFocus() -> int: - return integer(method('getFocus')) + return method('getFocus').take_int() diff --git a/computercraft/subapis/os.py b/computercraft/subapis/os.py index 73c1784..ac4252a 100644 --- a/computercraft/subapis/os.py +++ b/computercraft/subapis/os.py @@ -1,7 +1,6 @@ from typing import Optional, List from ..lua import LuaNum -from ..rproc import nil, string, option_string, number, integer, boolean from ..sess import eval_lua_method_factory, get_current_greenlet @@ -31,23 +30,23 @@ __all__ = ( def version() -> str: - return string(method('version')) + return method('version').take_string() def getComputerID() -> int: - return integer(method('getComputerID')) + return method('getComputerID').take_int() def getComputerLabel() -> Optional[str]: - return option_string(method('getComputerLabel')) + return method('getComputerLabel').take_option_string() def setComputerLabel(label: Optional[str]): - return nil(method('setComputerLabel', label)) + return method('setComputerLabel', label).take_none() def run(environment: dict, programPath: str, *args: List[str]): - return boolean(method('run', environment, programPath, *args)) + return method('run', environment, programPath, *args).take_bool() def captureEvent(event: str): @@ -68,12 +67,12 @@ def captureEvent(event: str): def queueEvent(event: str, *params): - return nil(method('queueEvent', event, *params)) + return method('queueEvent', event, *params).take_none() def clock() -> LuaNum: # number of game ticks * 0.05, roughly seconds - return number(method('clock')) + return method('clock').take_number() # regarding ingame parameter below: @@ -82,42 +81,42 @@ def clock() -> LuaNum: def time() -> LuaNum: # in hours 0..24 - return number(method('time', 'ingame')) + return method('time', 'ingame').take_number() def day() -> int: - return integer(method('day', 'ingame')) + return method('day', 'ingame').take_int() def epoch() -> int: - return integer(method('epoch', 'ingame')) + return method('epoch', 'ingame').take_int() def sleep(seconds: LuaNum): - return nil(method('sleep', seconds)) + return method('sleep', seconds).take_none() def startTimer(timeout: LuaNum) -> int: - return integer(method('startTimer', timeout)) + return method('startTimer', timeout).take_int() def cancelTimer(timerID: int): - return nil(method('cancelTimer', timerID)) + return method('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 method('setAlarm', time).take_int() def cancelAlarm(alarmID: int): - return nil(method('cancelAlarm', alarmID)) + return method('cancelAlarm', alarmID).take_none() def shutdown(): - return nil(method('shutdown')) + return method('shutdown').take_none() def reboot(): - return nil(method('reboot')) + return method('reboot').take_none() diff --git a/computercraft/subapis/paintutils.py b/computercraft/subapis/paintutils.py index 9d0e825..70c27b6 100644 --- a/computercraft/subapis/paintutils.py +++ b/computercraft/subapis/paintutils.py @@ -1,10 +1,8 @@ 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.') @@ -20,28 +18,28 @@ __all__ = ( def parseImage(data: str) -> List[List[int]]: - return array_2d_integer(method('parseImage', data)) + return method('parseImage', data).take_2d_int() def loadImage(path: str) -> List[List[int]]: - return array_2d_integer(method('loadImage', path)) + return method('loadImage', path).take_2d_int() def drawPixel(x: int, y: int, color: int = None): - return nil(method('drawPixel', x, y, color)) + return method('drawPixel', x, y, color).take_none() def drawLine(startX: int, startY: int, endX: int, endY: int, color: int = None): - return nil(method('drawLine', startX, startY, endX, endY, color)) + return method('drawLine', startX, startY, endX, endY, color).take_none() def drawBox(startX: int, startY: int, endX: int, endY: int, color: int = None): - return nil(method('drawBox', startX, startY, endX, endY, color)) + return method('drawBox', startX, startY, endX, endY, color).take_none() def drawFilledBox(startX: int, startY: int, endX: int, endY: int, color: int = None): - return nil(method('drawFilledBox', startX, startY, endX, endY, color)) + return method('drawFilledBox', startX, startY, endX, endY, color).take_none() def drawImage(image: List[List[int]], xPos: int, yPos: int): - return nil(method('drawImage', image, xPos, yPos)) + return method('drawImage', image, xPos, yPos).take_none() diff --git a/computercraft/subapis/peripheral.py b/computercraft/subapis/peripheral.py index 96d98bf..f13e4ba 100644 --- a/computercraft/subapis/peripheral.py +++ b/computercraft/subapis/peripheral.py @@ -2,12 +2,7 @@ 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 @@ -28,65 +23,65 @@ class BasePeripheral: class CCDrive(BasePeripheral): def isDiskPresent(self) -> bool: - return boolean(self._method('isDiskPresent')) + return self._method('isDiskPresent').take_bool() def getDiskLabel(self) -> Optional[str]: - return option_string(self._method('getDiskLabel')) + return self._method('getDiskLabel').take_option_string() def setDiskLabel(self, label: str): - return nil(self._method('setDiskLabel', label)) + return self._method('setDiskLabel', label).take_none() def hasData(self) -> bool: - return boolean(self._method('hasData')) + return self._method('hasData').take_bool() def getMountPath(self) -> Optional[str]: - return option_string(self._method('getMountPath')) + return self._method('getMountPath').take_option_string() def hasAudio(self) -> bool: - return boolean(self._method('hasAudio')) + return self._method('hasAudio').take_bool() def getAudioTitle(self) -> Optional[Union[bool, str]]: - return option_string_bool(self._method('getAudioTitle')) + return self._method('getAudioTitle').take_option_string_bool() def playAudio(self): - return nil(self._method('playAudio')) + return self._method('playAudio').take_none() def stopAudio(self): - return nil(self._method('stopAudio')) + return self._method('stopAudio').take_none() def ejectDisk(self): - return nil(self._method('ejectDisk')) + return self._method('ejectDisk').take_none() def getDiskID(self) -> Optional[int]: - return option_integer(self._method('getDiskID')) + return self._method('getDiskID').take_option_int() class CCMonitor(BasePeripheral, TermMixin): def getTextScale(self) -> int: - return integer(self._method('getTextScale')) + return self._method('getTextScale').take_int() def setTextScale(self, scale: int): - return nil(self._method('setTextScale', scale)) + return self._method('setTextScale', scale).take_none() class ComputerMixin: def turnOn(self): - return nil(self._method('turnOn')) + return self._method('turnOn').take_none() def shutdown(self): - return nil(self._method('shutdown')) + return self._method('shutdown').take_none() def reboot(self): - return nil(self._method('reboot')) + return self._method('reboot').take_none() def getID(self) -> int: - return integer(self._method('getID')) + return self._method('getID').take_int() def getLabel(self) -> Optional[str]: - return option_string(self._method('getLabel')) + return self._method('getLabel').take_option_string() def isOn(self) -> bool: - return boolean(self._method('isOn')) + return self._method('isOn').take_bool() class CCComputer(BasePeripheral, ComputerMixin): @@ -106,22 +101,22 @@ class ModemMessage: class ModemMixin: def isOpen(self, channel: int) -> bool: - return boolean(self._method('isOpen', channel)) + return self._method('isOpen', channel).take_bool() def open(self, channel: int): - return nil(self._method('open', channel)) + return self._method('open', channel).take_none() def close(self, channel: int): - return nil(self._method('close', channel)) + return self._method('close', channel).take_none() def closeAll(self): - return nil(self._method('closeAll')) + return self._method('closeAll').take_none() def transmit(self, channel: int, replyChannel: int, message: Any): - return nil(self._method('transmit', channel, replyChannel, message)) + return self._method('transmit', channel, replyChannel, message).take_none() def isWireless(self) -> bool: - return boolean(self._method('isWireless')) + return self._method('isWireless').take_bool() @property def _side(self): @@ -151,16 +146,16 @@ class CCWirelessModem(BasePeripheral, ModemMixin): class CCWiredModem(BasePeripheral, ModemMixin): def getNameLocal(self) -> Optional[str]: - return option_string(self._method('getNameLocal')) + return self._method('getNameLocal').take_option_string() def getNamesRemote(self) -> List[str]: - return array_string(self._method('getNamesRemote')) + return self._method('getNamesRemote').take_list_of_strings() def getTypeRemote(self, peripheralName: str) -> Optional[str]: - return option_string(self._method('getTypeRemote', peripheralName)) + return self._method('getTypeRemote', peripheralName).take_option_string() def isPresentRemote(self, peripheralName: str) -> bool: - return boolean(self._method('isPresentRemote', peripheralName)) + return self._method('isPresentRemote', peripheralName).take_bool() def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]: # use instead getMethodsRemote and callRemote @@ -180,31 +175,33 @@ class CCWiredModem(BasePeripheral, ModemMixin): class CCPrinter(BasePeripheral): def newPage(self) -> bool: - return boolean(self._method('newPage')) + return self._method('newPage').take_bool() def endPage(self) -> bool: - return boolean(self._method('endPage')) + return self._method('endPage').take_bool() def write(self, text: str): - return nil(self._method('write', text)) + return self._method('write', text).take_none() def setCursorPos(self, x: int, y: int): - return nil(self._method('setCursorPos', x, y)) + return self._method('setCursorPos', x, y).take_none() def getCursorPos(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getCursorPos')) + rp = self._method('getCursorPos') + return tuple(rp.take_int() for _ in range(2)) def getPageSize(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getPageSize')) + rp = self._method('getPageSize') + return tuple(rp.take_int() for _ in range(2)) def setPageTitle(self, title: str): - return nil(self._method('setPageTitle', title)) + return self._method('setPageTitle', title).take_none() def getPaperLevel(self) -> int: - return integer(self._method('getPaperLevel')) + return self._method('getPaperLevel').take_int() def getInkLevel(self) -> int: - return integer(self._method('getInkLevel')) + return self._method('getInkLevel').take_int() class CCSpeaker(BasePeripheral): @@ -229,28 +226,28 @@ class CCSpeaker(BasePeripheral): # volume 0..3 # pitch 0..24 - return boolean(self._method('playNote', instrument, volume, pitch)) + return self._method('playNote', instrument, volume, pitch).take_bool() - def playSound(self, sound: str, volume: int = 1, pitch: int = 1): + def playSound(self, sound: str, volume: int = 1, pitch: int = 1) -> bool: # volume 0..3 # pitch 0..2 - return boolean(self._method('playSound', sound, volume, pitch)) + return self._method('playSound', sound, volume, pitch).take_bool() class CCCommandBlock(BasePeripheral): def getCommand(self) -> str: - return string(self._method('getCommand')) + return self._method('getCommand').take_string() def setCommand(self, command: str): - return nil(self._method('setCommand', command)) + return self._method('setCommand', command).take_none() def runCommand(self): - return flat_try_result(self._method('runCommand')) + return self._method('runCommand').check_bool_error() class CCWorkbench(BasePeripheral): def craft(self, quantity: int = 64) -> bool: - return craft_result(self._method('craft', quantity)) + return self._method('craft', quantity).bool_error_exclude('No matching recipes') TYPE_MAP = { @@ -278,15 +275,15 @@ __all__ = ( def isPresent(side: str) -> bool: - return boolean(method('isPresent', side)) + return method('isPresent', side).take_bool() def getType(side: str) -> Optional[str]: - return option_string(method('getType', side)) + return method('getType', side).take_option_string() def getNames() -> List[str]: - return array_string(method('getNames')) + return method('getNames').take_list_of_strings() # use instead getMethods and call @@ -298,7 +295,7 @@ def wrap(side: str) -> Optional[BasePeripheral]: m = 'peripheral.call' if ptype == 'modem': - if boolean(method('call', side, 'isWireless')): + if method('call', side, 'isWireless').take_bool(): return CCWirelessModem(m, side) else: return CCWiredModem(m, side) diff --git a/computercraft/subapis/pocket.py b/computercraft/subapis/pocket.py index ddcb599..6aa89a5 100644 --- a/computercraft/subapis/pocket.py +++ b/computercraft/subapis/pocket.py @@ -1,4 +1,3 @@ -from ..rproc import flat_try_result from ..sess import eval_lua_method_factory @@ -12,8 +11,8 @@ __all__ = ( def equipBack(): - return flat_try_result(method('equipBack')) + return method('equipBack').check_bool_error() def unequipBack(): - return flat_try_result(method('unequipBack')) + return method('unequipBack').check_bool_error() diff --git a/computercraft/subapis/rednet.py b/computercraft/subapis/rednet.py index 9a038d7..00f6cbb 100644 --- a/computercraft/subapis/rednet.py +++ b/computercraft/subapis/rednet.py @@ -1,16 +1,9 @@ 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.') @@ -34,46 +27,47 @@ CHANNEL_BROADCAST = 65535 def open(side: str): - return nil(method('open', side)) + return method('open', side).take_none() def close(side: str = None): - return nil(method('close', side)) + return method('close', side).take_none() def send(receiverID: int, message: Any, protocol: str = None) -> bool: - return boolean(method('send', receiverID, message, protocol)) + return method('send', receiverID, message, protocol).take_bool() def broadcast(message: Any, protocol: str = None): - return nil(method('broadcast', message, protocol)) + return method('broadcast', message, protocol).take_none() def receive( protocolFilter: str = None, timeout: LuaNum = None, ) -> Optional[Tuple[int, Any, Optional[str]]]: - return recv_result(method('receive', protocolFilter, timeout)) + rp = method('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 boolean(method('isOpen', side)) + return method('isOpen', side).take_bool() def host(protocol: str, hostname: str): - return nil(method('host', protocol, hostname)) + return method('host', protocol, hostname).take_none() def unhost(protocol: str): - return nil(method('unhost', protocol)) + return method('unhost', protocol).take_none() def lookup(protocol: str, hostname: str = None) -> Union[Optional[int], List[int]]: - result = method('lookup', protocol, hostname) + rp = method('lookup', protocol, hostname) if hostname is None: - if result is None: + if rp.peek() is None: return [] - if isinstance(result, list): - return array_integer(result) - return [integer(result)] + return rp.take_list_of_ints() else: - return option_integer(result) + return rp.take_option_int() diff --git a/computercraft/subapis/redstone.py b/computercraft/subapis/redstone.py index ee671d4..68bfe53 100644 --- a/computercraft/subapis/redstone.py +++ b/computercraft/subapis/redstone.py @@ -1,6 +1,5 @@ from typing import List -from ..rproc import boolean, nil, integer, array_string from ..sess import eval_lua_method_factory @@ -23,46 +22,46 @@ __all__ = ( def getSides() -> List[str]: - return array_string(method('getSides')) + return method('getSides').take_list_of_strings() def getInput(side: str) -> bool: - return boolean(method('getInput', side)) + return method('getInput', side).take_bool() def setOutput(side: str, value: bool): - return nil(method('setOutput', side, value)) + return method('setOutput', side, value).take_none() def getOutput(side: str) -> bool: - return boolean(method('getOutput', side)) + return method('getOutput', side).take_bool() def getAnalogInput(side: str) -> int: - return integer(method('getAnalogInput', side)) + return method('getAnalogInput', side).take_int() def setAnalogOutput(side: str, strength: int): - return nil(method('setAnalogOutput', side, strength)) + return method('setAnalogOutput', side, strength).take_none() def getAnalogOutput(side: str) -> int: - return integer(method('getAnalogOutput', side)) + return method('getAnalogOutput', side).take_int() # bundled cables are not available in vanilla def getBundledInput(side: str) -> int: - return integer(method('getBundledInput', side)) + return method('getBundledInput', side).take_int() def setBundledOutput(side: str, colors: int): - return nil(method('setBundledOutput', side, colors)) + return method('setBundledOutput', side, colors).take_none() def getBundledOutput(side: str) -> int: - return integer(method('getBundledOutput', side)) + return method('getBundledOutput', side).take_int() def testBundledInput(side: str, color: int) -> bool: - return boolean(method('testBundledInput', side, color)) + return method('testBundledInput', side, color).take_bool() diff --git a/computercraft/subapis/settings.py b/computercraft/subapis/settings.py index 6610514..60c19fa 100644 --- a/computercraft/subapis/settings.py +++ b/computercraft/subapis/settings.py @@ -1,17 +1,8 @@ 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.') @@ -37,40 +28,57 @@ def define(name: str, description: str = None, default: Any = None, type: str = options['default'] = default if type is not None: options['type'] = type - return nil(method('define', name, options)) + return method('define', name, options).take_none() def undefine(name: str): - return nil(method('undefine', name)) + return method('undefine', name).take_none() def getDetails(name: str) -> dict: - return setting(method('getDetails', name)) + tp = method('getDetails', name).take_dict(( + b'changed', + b'description', + b'default', + b'type', + b'value', + )) + r = {} + r['changed'] = tp.take_bool() + for k, v in [ + ('description', tp.take_option_string()), + ('default', tp.take()), + ('type', tp.take_option_string()), + ('value', tp.take()), + ]: + if v is not None: + r[k] = v + return r def set(name: str, value: Any): - return nil(method('set', name, value)) + return method('set', name, value).take_none() def get(name: str, default: Any = None) -> Any: - return method('get', name, default) + return method('get', name, default).take() def unset(name: str): - return nil(method('unset', name)) + return method('unset', name).take_none() def clear(): - return nil(method('clear')) + return method('clear').take_none() def getNames() -> List[str]: - return array_string(method('getNames')) + return method('getNames').take_list_of_strings() def load(path: str = None) -> bool: - return boolean(method('load', path)) + return method('load', path).take_bool() def save(path: str = None) -> bool: - return boolean(method('save', path)) + return method('save', path).take_bool() diff --git a/computercraft/subapis/shell.py b/computercraft/subapis/shell.py index 41156f9..8b0d84c 100644 --- a/computercraft/subapis/shell.py +++ b/computercraft/subapis/shell.py @@ -1,10 +1,8 @@ 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.') @@ -31,75 +29,76 @@ __all__ = ( def exit(): - return nil(method('exit')) + return method('exit').take_none() def dir() -> str: - return string(method('dir')) + return method('dir').take_string() def setDir(path: str): - return nil(method('setDir', path)) + return method('setDir', path).take_none() def path() -> str: - return string(method('path')) + return method('path').take_string() def setPath(path: str): - return nil(method('setPath', path)) + return method('setPath', path).take_none() def resolve(localPath: str) -> str: - return string(method('resolve', localPath)) + return method('resolve', localPath).take_string() def resolveProgram(name: str) -> Optional[str]: - return option_string(method('resolveProgram', name)) + return method('resolveProgram', name).take_option_string() def aliases() -> Dict[str, str]: - return map_string_string(method('aliases')) + d = method('aliases').take_dict() + return {k.decode('latin1'): v.decode('latin1') for k, v in d.items()} def setAlias(alias: str, program: str): - return nil(method('setAlias', alias, program)) + return method('setAlias', alias, program).take_none() def clearAlias(alias: str): - return nil(method('clearAlias', alias)) + return method('clearAlias', alias).take_none() def programs(showHidden: bool = None) -> List[str]: - return array_string(method('programs', showHidden)) + return method('programs', showHidden).take_list_of_strings() def getRunningProgram() -> str: - return string(method('getRunningProgram')) + return method('getRunningProgram').take_string() def run(command: str, *args: str) -> bool: - return boolean(method('run', command, *args)) + return method('run', command, *args).take_bool() def execute(command: str, *args: str) -> bool: - return boolean(method('execute', command, *args)) + return method('execute', command, *args).take_bool() def openTab(command: str, *args: str) -> int: - return integer(method('openTab', command, *args)) + return method('openTab', command, *args).take_int() def switchTab(tabID: int): - return nil(method('switchTab', tabID)) + return method('switchTab', tabID).take_none() def complete(prefix: str) -> List[str]: - return array_string(method('complete', prefix)) + return method('complete', prefix).take_list_of_strings() def completeProgram(prefix: str) -> List[str]: - return array_string(method('completeProgram', prefix)) + return method('completeProgram', prefix).take_list_of_strings() # TODO: ? # these functions won't be implemented diff --git a/computercraft/subapis/term.py b/computercraft/subapis/term.py index 33d7119..47167fc 100644 --- a/computercraft/subapis/term.py +++ b/computercraft/subapis/term.py @@ -4,7 +4,6 @@ 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 @@ -61,7 +60,8 @@ setPaletteColor = tapi.setPaletteColor def nativePaletteColor(colorID: int) -> Tuple[float, float, float]: - return tuple3_number(method('nativePaletteColor', colorID)) + rp = method('nativePaletteColor', colorID) + return tuple(rp.take_number() for _ in range(3)) @contextmanager diff --git a/computercraft/subapis/textutils.py b/computercraft/subapis/textutils.py index 8513764..bc70b2b 100644 --- a/computercraft/subapis/textutils.py +++ b/computercraft/subapis/textutils.py @@ -1,7 +1,6 @@ from typing import List, Union from ..lua import LuaNum -from ..rproc import nil, string, integer from ..sess import eval_lua_method_factory @@ -20,27 +19,27 @@ __all__ = ( def slowWrite(text: str, rate: LuaNum = None): - return nil(method('slowWrite', text, rate)) + return method('slowWrite', text, rate).take_none() def slowPrint(text: str, rate: LuaNum = None): - return nil(method('slowPrint', text, rate)) + return method('slowPrint', text, rate).take_none() def formatTime(time: LuaNum, twentyFourHour: bool = None) -> str: - return string(method('formatTime', time, twentyFourHour)) + return method('formatTime', time, twentyFourHour).take_string() def tabulate(*rows_and_colors: Union[list, int]): - return nil(method('tabulate', *rows_and_colors)) + return method('tabulate', *rows_and_colors).take_none() def pagedTabulate(*rows_and_colors: Union[list, int]): - return nil(method('pagedTabulate', *rows_and_colors)) + return method('pagedTabulate', *rows_and_colors).take_none() def pagedPrint(text: str, freeLines: int = None) -> int: - return integer(method('pagedPrint', text, freeLines)) + return method('pagedPrint', text, freeLines).take_int() def complete(partial: str, possible: List[str]) -> List[str]: diff --git a/computercraft/subapis/turtle.py b/computercraft/subapis/turtle.py index 0870979..e231497 100644 --- a/computercraft/subapis/turtle.py +++ b/computercraft/subapis/turtle.py @@ -1,41 +1,26 @@ 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) +def inspect_result(rp): + success = rp.take_bool() if not success: - if data == 'No block to inspect': + msg = rp.take_string() + if msg == 'No block to inspect': return None - raise LuaException(data) + raise LuaException(msg) else: - return any_dict(data) + return rp.take_dict() 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 + def proc(rp): + return rp.bool_error_exclude(exclude_msg) return proc @@ -49,8 +34,8 @@ 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 +def always_true(rp): + assert rp.take_bool() is True # return value is useless return None @@ -135,19 +120,22 @@ def select(slotNum: int): def getSelectedSlot() -> int: - return integer(method('getSelectedSlot')) + return method('getSelectedSlot').take_int() def getItemCount(slotNum: int = None) -> int: - return integer(method('getItemCount', slotNum)) + return method('getItemCount', slotNum).take_int() def getItemSpace(slotNum: int = None) -> int: - return integer(method('getItemSpace', slotNum)) + return method('getItemSpace', slotNum).take_int() -def getItemDetail(slotNum: int = None) -> dict: - return option_any_dict(method('getItemDetail', slotNum)) +def getItemDetail(slotNum: int = None) -> Optional[dict]: + rp = method('getItemDetail', slotNum) + if rp.peek() is None: + return None + return rp.take_dict() def equipLeft(): @@ -195,15 +183,15 @@ def placeDown() -> bool: def detect() -> bool: - return boolean(method('detect')) + return method('detect').take_bool() def detectUp() -> bool: - return boolean(method('detectUp')) + return method('detectUp').take_bool() def detectDown() -> bool: - return boolean(method('detectDown')) + return method('detectDown').take_bool() def inspect() -> Optional[dict]: @@ -219,19 +207,19 @@ def inspectDown() -> Optional[dict]: def compare() -> bool: - return boolean(method('compare')) + return method('compare').take_bool() def compareUp() -> bool: - return boolean(method('compareUp')) + return method('compareUp').take_bool() def compareDown() -> bool: - return boolean(method('compareDown')) + return method('compareDown').take_bool() def compareTo(slot: int) -> bool: - return boolean(method('compareTo', slot)) + return method('compareTo', slot).take_bool() def drop(count: int = None) -> bool: @@ -259,15 +247,15 @@ def suckDown(amount: int = None) -> bool: def refuel(quantity: int = None): - return flat_try_result(method('refuel', quantity)) + return method('refuel', quantity).check_bool_error() def getFuelLevel() -> int: - return integer(method('getFuelLevel')) + return method('getFuelLevel').take_int() def getFuelLimit() -> int: - return integer(method('getFuelLimit')) + return method('getFuelLimit').take_int() def transferTo(slot: int, quantity: int = None) -> bool: diff --git a/computercraft/subapis/window.py b/computercraft/subapis/window.py index 4486898..6c3dbdf 100644 --- a/computercraft/subapis/window.py +++ b/computercraft/subapis/window.py @@ -2,7 +2,6 @@ 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 @@ -10,22 +9,24 @@ from .mixins import TermMixin, TermTarget class CCWindow(BaseSubAPI, TermMixin): def setVisible(self, visibility: bool): - return nil(self._method('setVisible', visibility)) + return self._method('setVisible', visibility).take_none() def redraw(self): - return nil(self._method('redraw')) + return self._method('redraw').take_none() def restoreCursor(self): - return nil(self._method('restoreCursor')) + return self._method('restoreCursor').take_none() def getPosition(self) -> Tuple[int, int]: - return tuple2_integer(self._method('getPosition')) + rp = self._method('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): - return nil(self._method('reposition', x, y, width, height, parent)) + return self._method('reposition', x, y, width, height, parent).take_none() def getLine(self, y: int) -> Tuple[str, str, str]: - return tuple3_string(self._method('getLine', y)) + rp = self._method('getLine', y) + return tuple(rp.take_string() for _ in range(3)) def get_term_target(self) -> TermTarget: return TermTarget(self.get_expr_code()) diff --git a/examples/_lib.py b/examples/_lib.py index a722fb3..4b2d90c 100644 --- a/examples/_lib.py +++ b/examples/_lib.py @@ -39,7 +39,7 @@ def step(text): def get_object_table(objname): - r = eval_lua(f""" + rp = eval_lua(f""" local r = {{}} for k in pairs({objname}) do local t = type({objname}[k]) @@ -51,8 +51,12 @@ for k in pairs({objname}) do end end return r""", immediate=True) - assert len(r) == 1 - return r[0] + d = rp.take_dict() + return { + k1.decode('latin1'): { + k2.decode('latin1'): v for k2, v in t.items() + } for k1, t in d.items() + } def get_class_table(cls): diff --git a/examples/test_fs.py b/examples/test_fs.py index 56b806f..3887a17 100644 --- a/examples/test_fs.py +++ b/examples/test_fs.py @@ -151,50 +151,32 @@ assert fs.complete('ap', 'tdir', includeFiles=False) == [] 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 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 +184,19 @@ with assert_raises(LuaException): 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/serialization.lua b/tests/serialization.lua index 1f9d722..02dd877 100644 --- a/tests/serialization.lua +++ b/tests/serialization.lua @@ -146,3 +146,5 @@ for _, v in ipairs(roundtrip_tables) do end print('ALL OK') + +print(serialize({true, false, 'Position is negative'}))