Skip to content

Commit

Permalink
feat: Added listen command for Interpreter
Browse files Browse the repository at this point in the history
  • Loading branch information
beneboy committed Oct 17, 2019
1 parent d4757db commit 32d70c9
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
11 changes: 9 additions & 2 deletions py/stencila/schema/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from sys import argv, stderr, stdout

from .interpreter import execute_from_cli
from .listener import start_stdio_interpreter


def cli_execute():
Expand All @@ -25,16 +26,22 @@ def cli_compile():
execute_from_cli(argv[2:], True)


def interpreter_listen():
start_stdio_interpreter()


def main():
"""The main entry point to this module, read the first CLI arg and call out to the corresponding function."""
command = argv[1] if len(argv) > 1 else ''

logging.basicConfig(stream=stdout, level=logging.DEBUG)

if command == 'execute':
logging.basicConfig(stream=stdout, level=logging.DEBUG)
cli_execute()
elif command == 'compile':
logging.basicConfig(stream=stdout, level=logging.DEBUG)
cli_compile()
elif command == 'listen':
interpreter_listen()
else:
stderr.write('Unknown command "{}"\n'.format(command))

Expand Down
15 changes: 15 additions & 0 deletions py/stencila/schema/code_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,21 @@ def set_code_error(code: typing.Union[CodeChunk, CodeExpression],
code.errors.append(error)


def simple_code_chunk_parse(code: CodeChunk) -> CodeChunkExecution:
"""
"Build a CodeChunkExecution from CodeChunk.
This is the most basic information that is needed to execute a CodeChunk in the interpreter.
"""
parser = CodeChunkParser()
cc_result = parser.parse(code)

if cc_result.error:
set_code_error(code, cc_result.error)

return CodeChunkExecution(code, cc_result)


class CodeChunkParser:
"""Parse a `CodeChunk` by parsing its `text` into an AST and traversing it."""

Expand Down
106 changes: 106 additions & 0 deletions py/stencila/schema/listener.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import json
import logging
import sys
import typing
from io import BytesIO

from stencila.schema.code_parsing import simple_code_chunk_parse
from stencila.schema.interpreter import Interpreter
from stencila.schema.types import CodeChunk
from stencila.schema.util import to_json, from_dict


def _byte(b: typing.Any) -> bytes:
return bytes((b,))


def encode(number: int) -> bytes:
"""Pack `number` into varint bytes"""
buf = b''
while True:
towrite = number & 0x7f
number >>= 7
if number:
buf += _byte(towrite | 0x80)
else:
buf += _byte(towrite)
break
return buf


def decode_stream(stream: typing.IO) -> int:
"""Read a varint from `stream`"""
shift = 0
result = 0
while True:
i = _read_one(stream)
result |= (i & 0x7f) << shift
shift += 7
if not (i & 0x80):
break

return result


def decode_bytes(buf: bytes) -> int:
"""Read a varint from from `buf` bytes"""
return decode_stream(BytesIO(buf))


def _read_one(stream: typing.IO) -> int:
"""Read a byte from the file (as an integer)
raises EOFError if the stream ends while reading bytes.
"""
if hasattr(stream, 'recv'):
c = stream.recv(1)
elif hasattr(stream, 'buffer'):
c = stream.buffer.read(1)
else:
c = stream.read(1)
if c == b'':
raise EOFError("Unexpected EOF while reading bytes")
return ord(c)


class InterpreterListener:
input_stream: typing.IO
output_stream: typing.IO
interpreter: Interpreter

def __init__(self, input_stream: typing.IO, output_stream: typing.IO, interpreter: Interpreter) -> None:
self.input_stream = input_stream
self.output_stream = output_stream
self.interpreter = interpreter

def read_message(self) -> typing.Iterable[bytes]:
while True:
message_len = decode_stream(self.input_stream)
yield self.input_stream.read(message_len)

def write_message(self, s: bytes) -> None:
self.output_stream.write(encode(len(s)))
self.output_stream.write(s)
self.output_stream.flush()

def run_interpreter(self) -> None:
for message in self.read_message():
envelope = json.loads(message.decode('utf8'))

code = from_dict(envelope['body'])

if isinstance(code, CodeChunk):
code = simple_code_chunk_parse(code)

self.interpreter.execute([code], {})
envelope['body'] = code
self.write_message(to_json(envelope).encode('utf8'))


def start_stdio_interpreter() -> None:
il = InterpreterListener(sys.stdin.buffer, sys.stdout.buffer, Interpreter())
il.run_interpreter()


if __name__ == '__main__':
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
start_stdio_interpreter()

0 comments on commit 32d70c9

Please sign in to comment.