Rewrite lua_eval result processing

This commit is contained in:
neumond 2020-07-21 01:32:37 +03:00
parent cb66ca9741
commit 1b5c81679e
31 changed files with 541 additions and 425 deletions

View file

@ -6,6 +6,7 @@ local url = 'http://127.0.0.1:4343/'
local tasks = {} local tasks = {}
local filters = {} local filters = {}
local ycounts = {} local ycounts = {}
local coparams = {}
ws = http.websocket(url..'ws/') ws = http.websocket(url..'ws/')
if ws == false then if ws == false then
@ -117,12 +118,13 @@ while true do
ws_send{ ws_send{
action='task_result', action='task_result',
task_id=msg.task_id, task_id=msg.task_id,
result={fn()}, result={fn(table.unpack(msg.params or {}))},
yields=0, yields=0,
} }
else else
tasks[msg.task_id] = coroutine.create(fn) tasks[msg.task_id] = coroutine.create(fn)
ycounts[msg.task_id] = 0 ycounts[msg.task_id] = 0
coparams[msg.task_id] = msg.params or {}
end end
end end
elseif msg.action == 'drop' then elseif msg.action == 'drop' then
@ -130,6 +132,7 @@ while true do
tasks[task_id] = nil tasks[task_id] = nil
filters[task_id] = nil filters[task_id] = nil
ycounts[task_id] = nil ycounts[task_id] = nil
coparams[task_id] = nil
end end
elseif msg.action == 'sub' then elseif msg.action == 'sub' then
event_sub[msg.event] = true event_sub[msg.event] = true
@ -152,7 +155,13 @@ while true do
local del_tasks = {} local del_tasks = {}
for task_id in pairs(tasks) do for task_id in pairs(tasks) do
if filters[task_id] == nil or filters[task_id] == event then 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 if coroutine.status(tasks[task_id]) == 'dead' then
ws_send{ ws_send{
action='task_result', action='task_result',
@ -175,6 +184,7 @@ while true do
tasks[task_id] = nil tasks[task_id] = nil
filters[task_id] = nil filters[task_id] = nil
ycounts[task_id] = nil ycounts[task_id] = nil
coparams[task_id] = nil
end end
end end

View file

@ -58,6 +58,8 @@ def lua_value(v):
return 'true' return 'true'
if isinstance(v, str): if isinstance(v, str):
return lua_string(v) return lua_string(v)
if isinstance(v, bytes):
return lua_string(v.decode('latin1'))
if isinstance(v, (int, float)): if isinstance(v, (int, float)):
return str(v) return str(v)
if isinstance(v, list): if isinstance(v, list):

View file

@ -1,6 +1,155 @@
from .errors import LuaException 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): def coro(result):
assert isinstance(result, list) assert isinstance(result, list)
assert len(result) >= 1 assert len(result) >= 1

View file

@ -21,6 +21,8 @@ def serialize(v: Any) -> bytes:
return b'T' return b'T'
elif isinstance(v, (int, float)): elif isinstance(v, (int, float)):
return '[{}]'.format(v).encode(_ENC) return '[{}]'.format(v).encode(_ENC)
elif isinstance(v, bytes):
return '<{}>'.format(len(v)) + v
elif isinstance(v, str): elif isinstance(v, str):
v = v.encode(_ENC) v = v.encode(_ENC)
return '<{}>'.format(len(v)).encode(_ENC) + v return '<{}>'.format(len(v)).encode(_ENC) + v
@ -56,7 +58,7 @@ def _deserialize(b: bytes, _idx: int) -> Tuple[Any, int]:
elif tok == 60: # < elif tok == 60: # <
newidx = b.index(b'>', _idx) newidx = b.index(b'>', _idx)
ln = int(b[_idx:newidx].decode(_ENC)) 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: # { elif tok == 123: # {
r = {} r = {}
while True: while True:
@ -67,12 +69,6 @@ def _deserialize(b: bytes, _idx: int) -> Tuple[Any, int]:
key, _idx = _deserialize(b, _idx) key, _idx = _deserialize(b, _idx)
value, _idx = _deserialize(b, _idx) value, _idx = _deserialize(b, _idx)
r[key] = value 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 return r, _idx
else: else:
raise ValueError raise ValueError

View file

@ -25,7 +25,7 @@ class CCApplication(web.Application):
async def _launch_program(self, ws): async def _launch_program(self, ws):
async for msg in self._bin_messages(ws): async for msg in self._bin_messages(ws):
msg = ser.deserialize(msg) msg = ser.deserialize(msg)
if msg['action'] != 'run': if msg[b'action'] != b'run':
await ws.send_bytes(ser.serialize({ await ws.send_bytes(ser.serialize({
'action': 'close', 'action': 'close',
'error': 'protocol error', 'error': 'protocol error',
@ -36,9 +36,9 @@ class CCApplication(web.Application):
sys.__stdout__.write('ws send ' + repr(data) + '\n') sys.__stdout__.write('ws send ' + repr(data) + '\n')
asyncio.create_task(ws.send_bytes(data)) asyncio.create_task(ws.send_bytes(data))
sess = CCSession(msg['computer'], sender) sess = CCSession(msg[b'computer'], sender)
if msg['args']: if msg[b'args']:
sess.run_program(msg['args'][0]) sess.run_program(msg[b'args'][1].decode('latin1'))
else: else:
sess.run_repl() sess.run_repl()
return sess return sess
@ -51,10 +51,10 @@ class CCApplication(web.Application):
if sess is not None: if sess is not None:
async for msg in self._bin_messages(ws): async for msg in self._bin_messages(ws):
msg = ser.deserialize(msg) msg = ser.deserialize(msg)
if msg['action'] == 'event': if msg[b'action'] == b'event':
sess.on_event(msg['event'], msg['params']) sess.on_event(msg[b'event'].decode('latin1'), msg[b'params'])
elif msg['action'] == 'task_result': elif msg[b'action'] == b'task_result':
sess.on_task_result(msg['task_id'], msg['result']) sess.on_task_result(msg[b'task_id'].decode('latin1'), msg[b'result'])
else: else:
await ws.send_bytes(ser.serialize({ await ws.send_bytes(ser.serialize({
'action': 'close', 'action': 'close',

View file

@ -73,22 +73,22 @@ class StdFileProxy:
raise RuntimeError( raise RuntimeError(
"Computercraft environment doesn't support " "Computercraft environment doesn't support "
"stdin readline method with parameter") "stdin readline method with parameter")
return rproc.string(eval_lua( return eval_lua(
return_lua_call('io.read') return_lua_call('io.read')
)) + '\n' ).take_string() + '\n'
def write(self, s): def write(self, s):
if _is_global_greenlet(): if _is_global_greenlet():
return self._native.write(s) return self._native.write(s)
else: else:
if self._err: if self._err:
return rproc.nil(eval_lua( return eval_lua(
lua_call('io.stderr:write', s) lua_call('io.stderr:write', s)
)) ).take_none()
else: else:
return rproc.nil(eval_lua( return eval_lua(
lua_call('io.write', s) lua_call('io.write', s)
)) ).take_none()
def fileno(self): def fileno(self):
if _is_global_greenlet(): if _is_global_greenlet():
@ -147,9 +147,10 @@ def eval_lua(lua_code, immediate=False):
}) })
result = get_current_session()._server_greenlet.switch(request) result = get_current_session()._server_greenlet.switch(request)
# debug('{} → {}'.format(lua_code, repr(result))) # debug('{} → {}'.format(lua_code, repr(result)))
rp = rproc.ResultProc(result)
if not immediate: if not immediate:
result = rproc.coro(result) rp.check_bool_error()
return result return rp
@contextmanager @contextmanager
@ -342,7 +343,7 @@ class CCSession:
def run_program(self, program): def run_program(self, program):
def _run_program(): def _run_program():
p, code = eval_lua(''' rp = eval_lua('''
local p = fs.combine(shell.dir(), {}) local p = fs.combine(shell.dir(), {})
if not fs.exists(p) then return nil end if not fs.exists(p) then return nil end
if fs.isDir(p) then return nil end if fs.isDir(p) then return nil end
@ -351,6 +352,8 @@ local code = f.readAll()
f.close() f.close()
return p, code return p, code
'''.lstrip().format(lua_string(program))) '''.lstrip().format(lua_string(program)))
p = rp.take_string()
code = rp.take_string()
cc = compile(code, p, 'exec') cc = compile(code, p, 'exec')
exec(cc, {'__file__': p}) exec(cc, {'__file__': p})

View file

@ -2,7 +2,6 @@ from types import ModuleType
from ..errors import LuaException from ..errors import LuaException
from ..lua import lua_string from ..lua import lua_string
from ..rproc import boolean, option_string
from ..sess import eval_lua from ..sess import eval_lua
@ -26,7 +25,7 @@ def import_file(path: str, relative_to: str = None):
lua_string(relative_to), lua_string(relative_to),
path_expr, path_expr,
) )
source = option_string(eval_lua(''' source = eval_lua('''
local p = {} local p = {}
if not fs.exists(p) then return nil end if not fs.exists(p) then return nil end
if fs.isDir(p) then return nil end if fs.isDir(p) then return nil end
@ -36,7 +35,7 @@ f.close()
return src return src
'''.lstrip().format( '''.lstrip().format(
path_expr, path_expr,
))) )).take_option_string()
if source is None: if source is None:
raise ImportError('File not found: {}'.format(path)) raise ImportError('File not found: {}'.format(path))
cc = compile(source, mod.__name__, 'exec') cc = compile(source, mod.__name__, 'exec')
@ -45,16 +44,16 @@ return src
def is_commands() -> bool: def is_commands() -> bool:
return boolean(eval_lua('return commands ~= nil')) return eval_lua('return commands ~= nil').take_bool()
def is_multishell() -> bool: def is_multishell() -> bool:
return boolean(eval_lua('return multishell ~= nil')) return eval_lua('return multishell ~= nil').take_bool()
def is_turtle() -> bool: def is_turtle() -> bool:
return boolean(eval_lua('return turtle ~= nil')) return eval_lua('return turtle ~= nil').take_bool()
def is_pocket() -> bool: def is_pocket() -> bool:
return boolean(eval_lua('return pocket ~= nil')) return eval_lua('return pocket ~= nil').take_bool()

View file

@ -1,6 +1,5 @@
from typing import Tuple from typing import Tuple
from ..rproc import boolean, integer, tuple3_number
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -55,23 +54,24 @@ black = 0x8000
# combine, subtract and test are mostly for redstone.setBundledOutput # combine, subtract and test are mostly for redstone.setBundledOutput
def combine(*colors: int) -> int: def combine(*colors: int) -> int:
return integer(method('combine', *colors)) return method('combine', *colors).take_int()
def subtract(color_set: int, *colors: int) -> 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: 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: 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]: 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 # use these chars for term.blit

View file

@ -1,10 +1,8 @@
from typing import Tuple, List, Optional 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 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.') method = eval_lua_method_factory('commands.')
@ -18,20 +16,25 @@ __all__ = (
def exec(command: str) -> Tuple[bool, List[str], Optional[int]]: 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]: def list() -> List[str]:
return array_string(method('list')) return method('list').take_list_of_strings()
def getBlockPosition() -> Tuple[int, int, int]: 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: 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]: 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()

View file

@ -1,6 +1,5 @@
from typing import Optional, Union from typing import Optional, Union
from ..rproc import boolean, nil, option_integer, option_string, option_string_bool
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -23,44 +22,44 @@ __all__ = (
def isPresent(side: str) -> bool: def isPresent(side: str) -> bool:
return boolean(method('isPresent', side)) return method('isPresent', side).take_bool()
def hasData(side: str) -> bool: def hasData(side: str) -> bool:
return boolean(method('hasData', side)) return method('hasData', side).take_bool()
def getMountPath(side: str) -> Optional[str]: 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): 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]: 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]: def getID(side: str) -> Optional[int]:
return option_integer(method('getID', side)) return method('getID', side).take_option_int()
def hasAudio(side: str) -> bool: def hasAudio(side: str) -> bool:
return boolean(method('hasAudio', side)) return method('hasAudio', side).take_bool()
def getAudioTitle(side: str) -> Optional[Union[bool, str]]: 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): def playAudio(side: str):
return nil(method('playAudio', side)) return method('playAudio', side).take_none()
def stopAudio(side: str): def stopAudio(side: str):
return nil(method('stopAudio', side)) return method('stopAudio', side).take_none()
def eject(side: str): def eject(side: str):
return nil(method('eject', side)) return method('eject', side).take_none()

View file

@ -1,55 +1,31 @@
import builtins
from contextlib import contextmanager from contextlib import contextmanager
from typing import Optional, List from typing import Optional, List
from .base import BaseSubAPI from .base import BaseSubAPI
from ..errors import LuaException from ..lua import lua_call
from ..lua import lua_call, lua_args, lua_string from ..sess import eval_lua_method_factory, lua_context_object
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,
}, {})
class SeekMixin: class SeekMixin:
def seek(self, whence: str = None, offset: int = None) -> int: def seek(self, whence: str = None, offset: int = None) -> int:
# whence: set, cur, end # whence: set, cur, end
r = self._method('seek', whence, offset) rp = self._method('seek', whence, offset)
if isinstance(r, builtins.list): rp.check_nil_error()
assert r[0] is False return rp.take_int()
raise LuaException(r[1])
return integer(r)
class ReadHandle(BaseSubAPI): class ReadMixin:
def _decode(self, b): def _take(self, rp):
return b.decode('utf-8') raise NotImplementedError
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))))
def read(self, count: int = 1) -> Optional[str]: 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]: def readLine(self, withTrailing: bool = False) -> Optional[str]:
return self._read('readLine', (), option_string) return self._take(self._method('readLine', withTrailing))
def readAll(self) -> str: def readAll(self) -> Optional[str]:
return self._read('readAll', (), string) return self._take(self._method('readAll'))
def __iter__(self): def __iter__(self):
return self return self
@ -61,39 +37,38 @@ return s
return line return line
class BinaryReadHandle(ReadHandle, SeekMixin): class WriteMixin:
def _decode(self, b): def _put(self, t):
return b raise NotImplementedError
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))
def write(self, text: str): def write(self, text: str):
return nil(self._method('write', text)) return self._method('write', self._put(text)).take_none()
def writeLine(self, text: str):
return nil(self._method('writeLine', text))
def flush(self): def flush(self):
return nil(self._method('flush')) return self._method('flush').take_none()
class BinaryWriteHandle(WriteHandle, SeekMixin): class ReadHandle(ReadMixin, BaseSubAPI):
def _encode(self, s): def _take(self, rp):
return s 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.') method = eval_lua_method_factory('fs.')
@ -124,55 +99,55 @@ __all__ = (
def list(path: str) -> List[str]: 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: def exists(path: str) -> bool:
return boolean(method('exists', path)) return method('exists', path).take_bool()
def isDir(path: str) -> bool: def isDir(path: str) -> bool:
return boolean(method('isDir', path)) return method('isDir', path).take_bool()
def isReadOnly(path: str) -> bool: def isReadOnly(path: str) -> bool:
return boolean(method('isReadOnly', path)) return method('isReadOnly', path).take_bool()
def getDrive(path: str) -> Optional[str]: def getDrive(path: str) -> Optional[str]:
return option_string(method('getDrive', path)) return method('getDrive', path).take_option_string()
def getSize(path: str) -> int: def getSize(path: str) -> int:
return integer(method('getSize', path)) return method('getSize', path).take_int()
def getFreeSpace(path: str) -> int: def getFreeSpace(path: str) -> int:
return integer(method('getFreeSpace', path)) return method('getFreeSpace', path).take_int()
def getCapacity(path: str) -> int: def getCapacity(path: str) -> int:
return integer(method('getCapacity', path)) return method('getCapacity', path).take_int()
def makeDir(path: str): def makeDir(path: str):
return nil(method('makeDir', path)) return method('makeDir', path).take_none()
def move(fromPath: str, toPath: str): 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): def copy(fromPath: str, toPath: str):
return nil(method('copy', fromPath, toPath)) return method('copy', fromPath, toPath).take_none()
def delete(path: str): def delete(path: str):
return nil(method('delete', path)) return method('delete', path).take_none()
def combine(basePath: str, localPath: str) -> str: def combine(basePath: str, localPath: str) -> str:
return string(method('combine', basePath, localPath)) return method('combine', basePath, localPath).take_string()
@contextmanager @contextmanager
@ -199,27 +174,39 @@ def open(path: str, mode: str):
def find(wildcard: str) -> List[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: def getDir(path: str) -> str:
return string(method('getDir', path)) return method('getDir', path).take_string()
def getName(path: str) -> str: def getName(path: str) -> str:
return string(method('getName', path)) return method('getName', path).take_string()
def isDriveRoot(path: str) -> bool: def isDriveRoot(path: str) -> bool:
return boolean(method('isDriveRoot', path)) return method('isDriveRoot', path).take_bool()
def complete( def complete(
partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None, partialName: str, path: str, includeFiles: bool = None, includeDirs: bool = None,
) -> List[str]: ) -> List[str]:
return array_string(method( return method(
'complete', partialName, path, includeFiles, includeDirs)) 'complete', partialName, path, includeFiles, includeDirs,
).take_list_of_strings()
def attributes(path: str) -> dict: 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

View file

@ -19,4 +19,7 @@ CHANNEL_GPS = 65534
def locate(timeout: LuaNum = None, debug: bool = None) -> Optional[Tuple[LuaNum, LuaNum, LuaNum]]: 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))

View file

@ -1,6 +1,5 @@
from typing import Optional, List from typing import Optional, List
from ..rproc import string, nil, array_string, option_string
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -17,20 +16,20 @@ __all__ = (
def path() -> str: def path() -> str:
return string(method('path')) return method('path').take_string()
def setPath(path: str): def setPath(path: str):
return nil(method('setPath', path)) return method('setPath', path).take_none()
def lookup(topic: str) -> Optional[str]: def lookup(topic: str) -> Optional[str]:
return option_string(method('lookup', topic)) return method('lookup', topic).take_option_string()
def topics() -> List[str]: def topics() -> List[str]:
return array_string(method('topics')) return method('topics').take_list_of_strings()
def completeTopic(topicPrefix: str) -> List[str]: def completeTopic(topicPrefix: str) -> List[str]:
return array_string(method('completeTopic', topicPrefix)) return method('completeTopic', topicPrefix).take_list_of_strings()

View file

@ -1,7 +1,6 @@
from typing import Optional from typing import Optional
from ..lua import lua_string from ..lua import lua_string
from ..rproc import option_integer, option_string
from ..sess import eval_lua, eval_lua_method_factory from ..sess import eval_lua, eval_lua_method_factory
@ -17,12 +16,12 @@ __all__ = (
def getCode(name: str) -> Optional[int]: def getCode(name: str) -> Optional[int]:
# replaces properties # replaces properties
# keys.space → keys.getCode('space') # keys.space → keys.getCode('space')
return option_integer(eval_lua(''' return eval_lua('''
if type(keys[{key}]) == 'number' then if type(keys[{key}]) == 'number' then
return keys[{key}] return keys[{key}]
end end
return nil'''.format(key=lua_string(name)))) return nil'''.format(key=lua_string(name))).take_option_int()
def getName(code: int) -> Optional[str]: def getName(code: int) -> Optional[str]:
return option_string(method('getName', code)) return method('getName', code).take_option_string()

View file

@ -1,60 +1,62 @@
from typing import Tuple from typing import Tuple
from ..lua import LuaExpr from ..lua import LuaExpr
from ..rproc import boolean, nil, integer, tuple3_number, tuple2_integer
class TermMixin: class TermMixin:
def write(self, text: str): 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): 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): def clear(self):
return nil(self._method('clear')) return self._method('clear').take_none()
def clearLine(self): def clearLine(self):
return nil(self._method('clearLine')) return self._method('clearLine').take_none()
def getCursorPos(self) -> Tuple[int, int]: 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): 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: def getCursorBlink(self) -> bool:
return boolean(self._method('getCursorBlink')) return self._method('getCursorBlink').take_bool()
def setCursorBlink(self, value: bool): def setCursorBlink(self, value: bool):
return nil(self._method('setCursorBlink', value)) return self._method('setCursorBlink', value).take_none()
def isColor(self) -> bool: def isColor(self) -> bool:
return boolean(self._method('isColor')) return self._method('isColor').take_bool()
def getSize(self) -> Tuple[int, int]: 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): def scroll(self, lines: int):
return nil(self._method('scroll', lines)) return self._method('scroll', lines).take_none()
def setTextColor(self, colorID: int): def setTextColor(self, colorID: int):
return nil(self._method('setTextColor', colorID)) return self._method('setTextColor', colorID).take_none()
def getTextColor(self) -> int: def getTextColor(self) -> int:
return integer(self._method('getTextColor')) return self._method('getTextColor').take_int()
def setBackgroundColor(self, colorID: int): def setBackgroundColor(self, colorID: int):
return nil(self._method('setBackgroundColor', colorID)) return self._method('setBackgroundColor', colorID).take_none()
def getBackgroundColor(self) -> int: def getBackgroundColor(self) -> int:
return integer(self._method('getBackgroundColor')) return self._method('getBackgroundColor').take_int()
def getPaletteColor(self, colorID: int) -> Tuple[float, float, float]: 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): 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): class TermTarget(LuaExpr):

View file

@ -1,6 +1,5 @@
from typing import Optional from typing import Optional
from ..rproc import integer, nil, boolean, option_string
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -19,28 +18,28 @@ __all__ = (
def getCurrent() -> int: def getCurrent() -> int:
return integer(method('getCurrent')) return method('getCurrent').take_int()
def getCount() -> int: def getCount() -> int:
return integer(method('getCount')) return method('getCount').take_int()
def launch(environment: dict, programPath: str, *args: str) -> 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): 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]: def getTitle(tabID: int) -> Optional[str]:
return option_string(method('getTitle', tabID)) return method('getTitle', tabID).take_option_string()
def setFocus(tabID: int) -> bool: def setFocus(tabID: int) -> bool:
return boolean(method('setFocus', tabID)) return method('setFocus', tabID).take_bool()
def getFocus() -> int: def getFocus() -> int:
return integer(method('getFocus')) return method('getFocus').take_int()

View file

@ -1,7 +1,6 @@
from typing import Optional, List from typing import Optional, List
from ..lua import LuaNum from ..lua import LuaNum
from ..rproc import nil, string, option_string, number, integer, boolean
from ..sess import eval_lua_method_factory, get_current_greenlet from ..sess import eval_lua_method_factory, get_current_greenlet
@ -31,23 +30,23 @@ __all__ = (
def version() -> str: def version() -> str:
return string(method('version')) return method('version').take_string()
def getComputerID() -> int: def getComputerID() -> int:
return integer(method('getComputerID')) return method('getComputerID').take_int()
def getComputerLabel() -> Optional[str]: def getComputerLabel() -> Optional[str]:
return option_string(method('getComputerLabel')) return method('getComputerLabel').take_option_string()
def setComputerLabel(label: Optional[str]): 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]): 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): def captureEvent(event: str):
@ -68,12 +67,12 @@ def captureEvent(event: str):
def queueEvent(event: str, *params): def queueEvent(event: str, *params):
return nil(method('queueEvent', event, *params)) return method('queueEvent', event, *params).take_none()
def clock() -> LuaNum: def clock() -> LuaNum:
# number of game ticks * 0.05, roughly seconds # number of game ticks * 0.05, roughly seconds
return number(method('clock')) return method('clock').take_number()
# regarding ingame parameter below: # regarding ingame parameter below:
@ -82,42 +81,42 @@ def clock() -> LuaNum:
def time() -> LuaNum: def time() -> LuaNum:
# in hours 0..24 # in hours 0..24
return number(method('time', 'ingame')) return method('time', 'ingame').take_number()
def day() -> int: def day() -> int:
return integer(method('day', 'ingame')) return method('day', 'ingame').take_int()
def epoch() -> int: def epoch() -> int:
return integer(method('epoch', 'ingame')) return method('epoch', 'ingame').take_int()
def sleep(seconds: LuaNum): def sleep(seconds: LuaNum):
return nil(method('sleep', seconds)) return method('sleep', seconds).take_none()
def startTimer(timeout: LuaNum) -> int: def startTimer(timeout: LuaNum) -> int:
return integer(method('startTimer', timeout)) return method('startTimer', timeout).take_int()
def cancelTimer(timerID: int): def cancelTimer(timerID: int):
return nil(method('cancelTimer', timerID)) return method('cancelTimer', timerID).take_none()
def setAlarm(time: LuaNum) -> int: def setAlarm(time: LuaNum) -> int:
# takes time of the day in hours 0..24 # takes time of the day in hours 0..24
# returns integer alarmID # returns integer alarmID
return integer(method('setAlarm', time)) return method('setAlarm', time).take_int()
def cancelAlarm(alarmID: int): def cancelAlarm(alarmID: int):
return nil(method('cancelAlarm', alarmID)) return method('cancelAlarm', alarmID).take_none()
def shutdown(): def shutdown():
return nil(method('shutdown')) return method('shutdown').take_none()
def reboot(): def reboot():
return nil(method('reboot')) return method('reboot').take_none()

View file

@ -1,10 +1,8 @@
from typing import List from typing import List
from ..rproc import nil, integer, fact_array
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
array_2d_integer = fact_array(fact_array(integer))
method = eval_lua_method_factory('paintutils.') method = eval_lua_method_factory('paintutils.')
@ -20,28 +18,28 @@ __all__ = (
def parseImage(data: str) -> List[List[int]]: 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]]: 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): 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): 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): 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): 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): 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()

View file

@ -2,12 +2,7 @@ from dataclasses import dataclass
from typing import Optional, List, Tuple, Any, Union from typing import Optional, List, Tuple, Any, Union
from .mixins import TermMixin, TermTarget from .mixins import TermMixin, TermTarget
from .turtle import craft_result
from ..lua import LuaNum, lua_args, return_lua_call 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 from ..sess import eval_lua, eval_lua_method_factory
@ -28,65 +23,65 @@ class BasePeripheral:
class CCDrive(BasePeripheral): class CCDrive(BasePeripheral):
def isDiskPresent(self) -> bool: def isDiskPresent(self) -> bool:
return boolean(self._method('isDiskPresent')) return self._method('isDiskPresent').take_bool()
def getDiskLabel(self) -> Optional[str]: def getDiskLabel(self) -> Optional[str]:
return option_string(self._method('getDiskLabel')) return self._method('getDiskLabel').take_option_string()
def setDiskLabel(self, label: str): def setDiskLabel(self, label: str):
return nil(self._method('setDiskLabel', label)) return self._method('setDiskLabel', label).take_none()
def hasData(self) -> bool: def hasData(self) -> bool:
return boolean(self._method('hasData')) return self._method('hasData').take_bool()
def getMountPath(self) -> Optional[str]: def getMountPath(self) -> Optional[str]:
return option_string(self._method('getMountPath')) return self._method('getMountPath').take_option_string()
def hasAudio(self) -> bool: def hasAudio(self) -> bool:
return boolean(self._method('hasAudio')) return self._method('hasAudio').take_bool()
def getAudioTitle(self) -> Optional[Union[bool, str]]: 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): def playAudio(self):
return nil(self._method('playAudio')) return self._method('playAudio').take_none()
def stopAudio(self): def stopAudio(self):
return nil(self._method('stopAudio')) return self._method('stopAudio').take_none()
def ejectDisk(self): def ejectDisk(self):
return nil(self._method('ejectDisk')) return self._method('ejectDisk').take_none()
def getDiskID(self) -> Optional[int]: def getDiskID(self) -> Optional[int]:
return option_integer(self._method('getDiskID')) return self._method('getDiskID').take_option_int()
class CCMonitor(BasePeripheral, TermMixin): class CCMonitor(BasePeripheral, TermMixin):
def getTextScale(self) -> int: def getTextScale(self) -> int:
return integer(self._method('getTextScale')) return self._method('getTextScale').take_int()
def setTextScale(self, scale: int): def setTextScale(self, scale: int):
return nil(self._method('setTextScale', scale)) return self._method('setTextScale', scale).take_none()
class ComputerMixin: class ComputerMixin:
def turnOn(self): def turnOn(self):
return nil(self._method('turnOn')) return self._method('turnOn').take_none()
def shutdown(self): def shutdown(self):
return nil(self._method('shutdown')) return self._method('shutdown').take_none()
def reboot(self): def reboot(self):
return nil(self._method('reboot')) return self._method('reboot').take_none()
def getID(self) -> int: def getID(self) -> int:
return integer(self._method('getID')) return self._method('getID').take_int()
def getLabel(self) -> Optional[str]: def getLabel(self) -> Optional[str]:
return option_string(self._method('getLabel')) return self._method('getLabel').take_option_string()
def isOn(self) -> bool: def isOn(self) -> bool:
return boolean(self._method('isOn')) return self._method('isOn').take_bool()
class CCComputer(BasePeripheral, ComputerMixin): class CCComputer(BasePeripheral, ComputerMixin):
@ -106,22 +101,22 @@ class ModemMessage:
class ModemMixin: class ModemMixin:
def isOpen(self, channel: int) -> bool: def isOpen(self, channel: int) -> bool:
return boolean(self._method('isOpen', channel)) return self._method('isOpen', channel).take_bool()
def open(self, channel: int): def open(self, channel: int):
return nil(self._method('open', channel)) return self._method('open', channel).take_none()
def close(self, channel: int): def close(self, channel: int):
return nil(self._method('close', channel)) return self._method('close', channel).take_none()
def closeAll(self): def closeAll(self):
return nil(self._method('closeAll')) return self._method('closeAll').take_none()
def transmit(self, channel: int, replyChannel: int, message: Any): 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: def isWireless(self) -> bool:
return boolean(self._method('isWireless')) return self._method('isWireless').take_bool()
@property @property
def _side(self): def _side(self):
@ -151,16 +146,16 @@ class CCWirelessModem(BasePeripheral, ModemMixin):
class CCWiredModem(BasePeripheral, ModemMixin): class CCWiredModem(BasePeripheral, ModemMixin):
def getNameLocal(self) -> Optional[str]: def getNameLocal(self) -> Optional[str]:
return option_string(self._method('getNameLocal')) return self._method('getNameLocal').take_option_string()
def getNamesRemote(self) -> List[str]: 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]: 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: 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]: def wrapRemote(self, peripheralName: str) -> Optional[BasePeripheral]:
# use instead getMethodsRemote and callRemote # use instead getMethodsRemote and callRemote
@ -180,31 +175,33 @@ class CCWiredModem(BasePeripheral, ModemMixin):
class CCPrinter(BasePeripheral): class CCPrinter(BasePeripheral):
def newPage(self) -> bool: def newPage(self) -> bool:
return boolean(self._method('newPage')) return self._method('newPage').take_bool()
def endPage(self) -> bool: def endPage(self) -> bool:
return boolean(self._method('endPage')) return self._method('endPage').take_bool()
def write(self, text: str): 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): 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]: 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]: 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): def setPageTitle(self, title: str):
return nil(self._method('setPageTitle', title)) return self._method('setPageTitle', title).take_none()
def getPaperLevel(self) -> int: def getPaperLevel(self) -> int:
return integer(self._method('getPaperLevel')) return self._method('getPaperLevel').take_int()
def getInkLevel(self) -> int: def getInkLevel(self) -> int:
return integer(self._method('getInkLevel')) return self._method('getInkLevel').take_int()
class CCSpeaker(BasePeripheral): class CCSpeaker(BasePeripheral):
@ -229,28 +226,28 @@ class CCSpeaker(BasePeripheral):
# volume 0..3 # volume 0..3
# pitch 0..24 # 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 # volume 0..3
# pitch 0..2 # pitch 0..2
return boolean(self._method('playSound', sound, volume, pitch)) return self._method('playSound', sound, volume, pitch).take_bool()
class CCCommandBlock(BasePeripheral): class CCCommandBlock(BasePeripheral):
def getCommand(self) -> str: def getCommand(self) -> str:
return string(self._method('getCommand')) return self._method('getCommand').take_string()
def setCommand(self, command: str): def setCommand(self, command: str):
return nil(self._method('setCommand', command)) return self._method('setCommand', command).take_none()
def runCommand(self): def runCommand(self):
return flat_try_result(self._method('runCommand')) return self._method('runCommand').check_bool_error()
class CCWorkbench(BasePeripheral): class CCWorkbench(BasePeripheral):
def craft(self, quantity: int = 64) -> bool: 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 = { TYPE_MAP = {
@ -278,15 +275,15 @@ __all__ = (
def isPresent(side: str) -> bool: def isPresent(side: str) -> bool:
return boolean(method('isPresent', side)) return method('isPresent', side).take_bool()
def getType(side: str) -> Optional[str]: def getType(side: str) -> Optional[str]:
return option_string(method('getType', side)) return method('getType', side).take_option_string()
def getNames() -> List[str]: def getNames() -> List[str]:
return array_string(method('getNames')) return method('getNames').take_list_of_strings()
# use instead getMethods and call # use instead getMethods and call
@ -298,7 +295,7 @@ def wrap(side: str) -> Optional[BasePeripheral]:
m = 'peripheral.call' m = 'peripheral.call'
if ptype == 'modem': if ptype == 'modem':
if boolean(method('call', side, 'isWireless')): if method('call', side, 'isWireless').take_bool():
return CCWirelessModem(m, side) return CCWirelessModem(m, side)
else: else:
return CCWiredModem(m, side) return CCWiredModem(m, side)

View file

@ -1,4 +1,3 @@
from ..rproc import flat_try_result
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -12,8 +11,8 @@ __all__ = (
def equipBack(): def equipBack():
return flat_try_result(method('equipBack')) return method('equipBack').check_bool_error()
def unequipBack(): def unequipBack():
return flat_try_result(method('unequipBack')) return method('unequipBack').check_bool_error()

View file

@ -1,16 +1,9 @@
from typing import Any, List, Optional, Tuple, Union from typing import Any, List, Optional, Tuple, Union
from ..lua import LuaNum 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 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.') method = eval_lua_method_factory('rednet.')
@ -34,46 +27,47 @@ CHANNEL_BROADCAST = 65535
def open(side: str): def open(side: str):
return nil(method('open', side)) return method('open', side).take_none()
def close(side: str = 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: 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): def broadcast(message: Any, protocol: str = None):
return nil(method('broadcast', message, protocol)) return method('broadcast', message, protocol).take_none()
def receive( def receive(
protocolFilter: str = None, timeout: LuaNum = None, protocolFilter: str = None, timeout: LuaNum = None,
) -> Optional[Tuple[int, Any, Optional[str]]]: ) -> 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: def isOpen(side: str = None) -> bool:
return boolean(method('isOpen', side)) return method('isOpen', side).take_bool()
def host(protocol: str, hostname: str): def host(protocol: str, hostname: str):
return nil(method('host', protocol, hostname)) return method('host', protocol, hostname).take_none()
def unhost(protocol: str): 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]]: 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 hostname is None:
if result is None: if rp.peek() is None:
return [] return []
if isinstance(result, list): return rp.take_list_of_ints()
return array_integer(result)
return [integer(result)]
else: else:
return option_integer(result) return rp.take_option_int()

View file

@ -1,6 +1,5 @@
from typing import List from typing import List
from ..rproc import boolean, nil, integer, array_string
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -23,46 +22,46 @@ __all__ = (
def getSides() -> List[str]: def getSides() -> List[str]:
return array_string(method('getSides')) return method('getSides').take_list_of_strings()
def getInput(side: str) -> bool: def getInput(side: str) -> bool:
return boolean(method('getInput', side)) return method('getInput', side).take_bool()
def setOutput(side: str, value: 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: def getOutput(side: str) -> bool:
return boolean(method('getOutput', side)) return method('getOutput', side).take_bool()
def getAnalogInput(side: str) -> int: def getAnalogInput(side: str) -> int:
return integer(method('getAnalogInput', side)) return method('getAnalogInput', side).take_int()
def setAnalogOutput(side: str, strength: 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: def getAnalogOutput(side: str) -> int:
return integer(method('getAnalogOutput', side)) return method('getAnalogOutput', side).take_int()
# bundled cables are not available in vanilla # bundled cables are not available in vanilla
def getBundledInput(side: str) -> int: def getBundledInput(side: str) -> int:
return integer(method('getBundledInput', side)) return method('getBundledInput', side).take_int()
def setBundledOutput(side: str, colors: 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: def getBundledOutput(side: str) -> int:
return integer(method('getBundledOutput', side)) return method('getBundledOutput', side).take_int()
def testBundledInput(side: str, color: int) -> bool: def testBundledInput(side: str, color: int) -> bool:
return boolean(method('testBundledInput', side, color)) return method('testBundledInput', side, color).take_bool()

View file

@ -1,17 +1,8 @@
from typing import Any, List from typing import Any, List
from ..rproc import nil, boolean, string, array_string, fact_scheme_dict
from ..sess import eval_lua_method_factory 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.') 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 options['default'] = default
if type is not None: if type is not None:
options['type'] = type options['type'] = type
return nil(method('define', name, options)) return method('define', name, options).take_none()
def undefine(name: str): def undefine(name: str):
return nil(method('undefine', name)) return method('undefine', name).take_none()
def getDetails(name: str) -> dict: 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): 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: def get(name: str, default: Any = None) -> Any:
return method('get', name, default) return method('get', name, default).take()
def unset(name: str): def unset(name: str):
return nil(method('unset', name)) return method('unset', name).take_none()
def clear(): def clear():
return nil(method('clear')) return method('clear').take_none()
def getNames() -> List[str]: def getNames() -> List[str]:
return array_string(method('getNames')) return method('getNames').take_list_of_strings()
def load(path: str = None) -> bool: def load(path: str = None) -> bool:
return boolean(method('load', path)) return method('load', path).take_bool()
def save(path: str = None) -> bool: def save(path: str = None) -> bool:
return boolean(method('save', path)) return method('save', path).take_bool()

View file

@ -1,10 +1,8 @@
from typing import List, Dict, Optional 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 from ..sess import eval_lua_method_factory
map_string_string = fact_mono_dict(string, string)
method = eval_lua_method_factory('shell.') method = eval_lua_method_factory('shell.')
@ -31,75 +29,76 @@ __all__ = (
def exit(): def exit():
return nil(method('exit')) return method('exit').take_none()
def dir() -> str: def dir() -> str:
return string(method('dir')) return method('dir').take_string()
def setDir(path: str): def setDir(path: str):
return nil(method('setDir', path)) return method('setDir', path).take_none()
def path() -> str: def path() -> str:
return string(method('path')) return method('path').take_string()
def setPath(path: str): def setPath(path: str):
return nil(method('setPath', path)) return method('setPath', path).take_none()
def resolve(localPath: str) -> str: def resolve(localPath: str) -> str:
return string(method('resolve', localPath)) return method('resolve', localPath).take_string()
def resolveProgram(name: str) -> Optional[str]: def resolveProgram(name: str) -> Optional[str]:
return option_string(method('resolveProgram', name)) return method('resolveProgram', name).take_option_string()
def aliases() -> Dict[str, str]: 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): def setAlias(alias: str, program: str):
return nil(method('setAlias', alias, program)) return method('setAlias', alias, program).take_none()
def clearAlias(alias: str): def clearAlias(alias: str):
return nil(method('clearAlias', alias)) return method('clearAlias', alias).take_none()
def programs(showHidden: bool = None) -> List[str]: def programs(showHidden: bool = None) -> List[str]:
return array_string(method('programs', showHidden)) return method('programs', showHidden).take_list_of_strings()
def getRunningProgram() -> str: def getRunningProgram() -> str:
return string(method('getRunningProgram')) return method('getRunningProgram').take_string()
def run(command: str, *args: str) -> bool: 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: 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: def openTab(command: str, *args: str) -> int:
return integer(method('openTab', command, *args)) return method('openTab', command, *args).take_int()
def switchTab(tabID: int): def switchTab(tabID: int):
return nil(method('switchTab', tabID)) return method('switchTab', tabID).take_none()
def complete(prefix: str) -> List[str]: 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]: def completeProgram(prefix: str) -> List[str]:
return array_string(method('completeProgram', prefix)) return method('completeProgram', prefix).take_list_of_strings()
# TODO: ? # TODO: ?
# these functions won't be implemented # these functions won't be implemented

View file

@ -4,7 +4,6 @@ from typing import Tuple
from .base import BaseSubAPI from .base import BaseSubAPI
from .mixins import TermMixin, TermTarget from .mixins import TermMixin, TermTarget
from ..lua import lua_call from ..lua import lua_call
from ..rproc import tuple3_number
from ..sess import eval_lua_method_factory, lua_context_object 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]: 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 @contextmanager

View file

@ -1,7 +1,6 @@
from typing import List, Union from typing import List, Union
from ..lua import LuaNum from ..lua import LuaNum
from ..rproc import nil, string, integer
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
@ -20,27 +19,27 @@ __all__ = (
def slowWrite(text: str, rate: LuaNum = None): 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): 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: 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]): 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]): 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: 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]: def complete(partial: str, possible: List[str]) -> List[str]:

View file

@ -1,41 +1,26 @@
from typing import Optional from typing import Optional
from ..errors import LuaException from ..errors import LuaException
from ..rproc import integer, boolean, fact_option, any_dict, flat_try_result
from ..sess import eval_lua_method_factory from ..sess import eval_lua_method_factory
method = eval_lua_method_factory('turtle.') method = eval_lua_method_factory('turtle.')
option_any_dict = fact_option(any_dict)
def inspect_result(r): def inspect_result(rp):
assert isinstance(r, list) success = rp.take_bool()
assert len(r) == 2
success, data = r
assert isinstance(success, bool)
if not success: if not success:
if data == 'No block to inspect': msg = rp.take_string()
if msg == 'No block to inspect':
return None return None
raise LuaException(data) raise LuaException(msg)
else: else:
return any_dict(data) return rp.take_dict()
def boolean_with_error_exclusion(exclude_msg): def boolean_with_error_exclusion(exclude_msg):
def proc(r): def proc(rp):
if r is True: return rp.bool_error_exclude(exclude_msg)
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 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') craft_result = boolean_with_error_exclusion('No matching recipes')
def always_true(r): def always_true(rp):
assert boolean(r) is True assert rp.take_bool() is True
# return value is useless # return value is useless
return None return None
@ -135,19 +120,22 @@ def select(slotNum: int):
def getSelectedSlot() -> int: def getSelectedSlot() -> int:
return integer(method('getSelectedSlot')) return method('getSelectedSlot').take_int()
def getItemCount(slotNum: int = None) -> int: def getItemCount(slotNum: int = None) -> int:
return integer(method('getItemCount', slotNum)) return method('getItemCount', slotNum).take_int()
def getItemSpace(slotNum: int = None) -> int: def getItemSpace(slotNum: int = None) -> int:
return integer(method('getItemSpace', slotNum)) return method('getItemSpace', slotNum).take_int()
def getItemDetail(slotNum: int = None) -> dict: def getItemDetail(slotNum: int = None) -> Optional[dict]:
return option_any_dict(method('getItemDetail', slotNum)) rp = method('getItemDetail', slotNum)
if rp.peek() is None:
return None
return rp.take_dict()
def equipLeft(): def equipLeft():
@ -195,15 +183,15 @@ def placeDown() -> bool:
def detect() -> bool: def detect() -> bool:
return boolean(method('detect')) return method('detect').take_bool()
def detectUp() -> bool: def detectUp() -> bool:
return boolean(method('detectUp')) return method('detectUp').take_bool()
def detectDown() -> bool: def detectDown() -> bool:
return boolean(method('detectDown')) return method('detectDown').take_bool()
def inspect() -> Optional[dict]: def inspect() -> Optional[dict]:
@ -219,19 +207,19 @@ def inspectDown() -> Optional[dict]:
def compare() -> bool: def compare() -> bool:
return boolean(method('compare')) return method('compare').take_bool()
def compareUp() -> bool: def compareUp() -> bool:
return boolean(method('compareUp')) return method('compareUp').take_bool()
def compareDown() -> bool: def compareDown() -> bool:
return boolean(method('compareDown')) return method('compareDown').take_bool()
def compareTo(slot: int) -> bool: def compareTo(slot: int) -> bool:
return boolean(method('compareTo', slot)) return method('compareTo', slot).take_bool()
def drop(count: int = None) -> bool: def drop(count: int = None) -> bool:
@ -259,15 +247,15 @@ def suckDown(amount: int = None) -> bool:
def refuel(quantity: int = None): def refuel(quantity: int = None):
return flat_try_result(method('refuel', quantity)) return method('refuel', quantity).check_bool_error()
def getFuelLevel() -> int: def getFuelLevel() -> int:
return integer(method('getFuelLevel')) return method('getFuelLevel').take_int()
def getFuelLimit() -> int: def getFuelLimit() -> int:
return integer(method('getFuelLimit')) return method('getFuelLimit').take_int()
def transferTo(slot: int, quantity: int = None) -> bool: def transferTo(slot: int, quantity: int = None) -> bool:

View file

@ -2,7 +2,6 @@ from contextlib import contextmanager
from typing import Tuple from typing import Tuple
from ..lua import lua_call from ..lua import lua_call
from ..rproc import nil, tuple2_integer, tuple3_string
from ..sess import eval_lua_method_factory, lua_context_object from ..sess import eval_lua_method_factory, lua_context_object
from .base import BaseSubAPI from .base import BaseSubAPI
from .mixins import TermMixin, TermTarget from .mixins import TermMixin, TermTarget
@ -10,22 +9,24 @@ from .mixins import TermMixin, TermTarget
class CCWindow(BaseSubAPI, TermMixin): class CCWindow(BaseSubAPI, TermMixin):
def setVisible(self, visibility: bool): def setVisible(self, visibility: bool):
return nil(self._method('setVisible', visibility)) return self._method('setVisible', visibility).take_none()
def redraw(self): def redraw(self):
return nil(self._method('redraw')) return self._method('redraw').take_none()
def restoreCursor(self): def restoreCursor(self):
return nil(self._method('restoreCursor')) return self._method('restoreCursor').take_none()
def getPosition(self) -> Tuple[int, int]: 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): 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]: 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: def get_term_target(self) -> TermTarget:
return TermTarget(self.get_expr_code()) return TermTarget(self.get_expr_code())

View file

@ -39,7 +39,7 @@ def step(text):
def get_object_table(objname): def get_object_table(objname):
r = eval_lua(f""" rp = eval_lua(f"""
local r = {{}} local r = {{}}
for k in pairs({objname}) do for k in pairs({objname}) do
local t = type({objname}[k]) local t = type({objname}[k])
@ -51,8 +51,12 @@ for k in pairs({objname}) do
end end
end end
return r""", immediate=True) return r""", immediate=True)
assert len(r) == 1 d = rp.take_dict()
return r[0] 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): def get_class_table(cls):

View file

@ -151,50 +151,32 @@ assert fs.complete('ap', 'tdir', includeFiles=False) == []
assert fs.getSize('tdir/banana') == 9 assert fs.getSize('tdir/banana') == 9
with fs.open('tdir/banana', 'r') as f: 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.read(4) == 'text'
assert f.readLine() == 'line' assert f.readLine() == 'line'
assert f.read(1) is None assert f.read(1) is None
assert f.readLine() is None assert f.readLine() is None
assert f.readAll() == '' assert f.readAll() is None
assert f.readAll() == '' assert f.readAll() is None
assert fs.getSize('tdir/banana') == 9 assert fs.getSize('tdir/banana') == 9
with fs.open('tdir/banana', 'a') as f: 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 f.write('x') is None
assert fs.getSize('tdir/banana') == 10 assert fs.getSize('tdir/banana') == 10
with fs.open('tdir/banana', 'w') as f: with fs.open('tdir/banana', 'w') as f:
pass pass
assert fs.getSize('tdir/banana') == 0 # truncate assert fs.getSize('tdir/banana') == 0 # truncate
with fs.open('tdir/banana', 'w') as f: 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.write('Bro') is None
assert f.writeLine('wn fox jumps') 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 f.flush() is None
assert fs.getSize('tdir/banana') == len('Brown fox jumps\n') assert fs.getSize('tdir/banana') == len('Brown fox jumps\n')
assert f.write('ov') is None assert f.write('ov') is None
assert f.write('er ') is None assert f.write('er ') is None
assert f.write('a lazy') 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 assert fs.getSize('tdir/banana') > 9
with fs.open('tdir/banana', 'r') as f: 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 assert_raises(LuaException):
with fs.open('tdir/banana', 'rw') as f: with fs.open('tdir/banana', 'rw') as f:
pass pass
@ -202,22 +184,19 @@ with assert_raises(LuaException):
assert fs.exists('tdir/banana') is True assert fs.exists('tdir/banana') is True
with fs.open('tdir/binfile', 'wb') as f: 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() == 9
assert f.seek('set', 0) == 0 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.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.seek('end') == 9
assert f.write('d' * 3) is None assert f.write(b'd' * 3) is None
with assert_raises(LuaException): with assert_raises(LuaException):
f.seek('set', -10) f.seek('set', -10)
with fs.open('tdir/binfile', 'rb') as f: with fs.open('tdir/binfile', 'rb') as f:
assert f.readAll() == 'bbcccaaaaddd' assert f.readAll() == b'bbcccaaaaddd'
with fs.open('tdir/binfile', 'rb') as f:
assert isinstance(f.read(), int)
with fs.open('tdir/binfile', 'r') as f: with fs.open('tdir/binfile', 'r') as f:
assert [line for line in f] == ['bbcccaaaaddd'] assert [line for line in f] == ['bbcccaaaaddd']

View file

@ -146,3 +146,5 @@ for _, v in ipairs(roundtrip_tables) do
end end
print('ALL OK') print('ALL OK')
print(serialize({true, false, 'Position is negative'}))