Initial commit
This commit is contained in:
commit
9e9fed8384
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.vscode
|
||||||
|
__pycache__
|
56
run.py
Normal file
56
run.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from tango.vm import TangoVM
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
code = """
|
||||||
|
(
|
||||||
|
(def hello "world")
|
||||||
|
(def foo "bar")
|
||||||
|
(def dance (
|
||||||
|
(def other "world")
|
||||||
|
(print (if (eq hello other) "the world is ok" "the world is broken"))
|
||||||
|
))
|
||||||
|
(on "click" dance)
|
||||||
|
(dance)
|
||||||
|
(def hello "moon")
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
event_handlers = []
|
||||||
|
|
||||||
|
def builtin_on(vm, scope, event, handler):
|
||||||
|
handler = vm.eval_in_scope(scope, handler)
|
||||||
|
event_handlers.append((event, handler, scope))
|
||||||
|
|
||||||
|
def trigger_event(vm, trigger_event):
|
||||||
|
results = []
|
||||||
|
for event, handler, scope in event_handlers:
|
||||||
|
if trigger_event == event:
|
||||||
|
results.append(vm.eval_in_scope(scope, handler))
|
||||||
|
return results
|
||||||
|
|
||||||
|
def builtin_def(vm, scope, ident, val):
|
||||||
|
scope.set_def(ident, val)
|
||||||
|
|
||||||
|
def builtin_print(vm, scope, *args):
|
||||||
|
print(*vm.multi_eval_in_scope(scope, args))
|
||||||
|
|
||||||
|
def builtin_if(vm, scope, predictate, truthy, falsy = None):
|
||||||
|
if vm.eval_in_scope(scope, predictate):
|
||||||
|
return vm.eval_in_scope(scope, truthy)
|
||||||
|
elif falsy:
|
||||||
|
return vm.eval_in_scope(scope, falsy)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def builtin_eq(vm, scope, a, b):
|
||||||
|
a, b = vm.multi_eval_in_scope(scope, (a, b))
|
||||||
|
return a == b
|
||||||
|
|
||||||
|
vm = TangoVM()
|
||||||
|
vm.def_builtin('def', builtin_def)
|
||||||
|
vm.def_builtin('print', builtin_print)
|
||||||
|
vm.def_builtin('eq', builtin_eq)
|
||||||
|
vm.def_builtin('if', builtin_if)
|
||||||
|
vm.def_builtin('on', builtin_on)
|
||||||
|
vm.eval_string(code)
|
||||||
|
trigger_event(vm, 'click')
|
0
tango/__init__.py
Normal file
0
tango/__init__.py
Normal file
92
tango/parser.py
Normal file
92
tango/parser.py
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
from tango.value import TangoIdent, TangoExpr
|
||||||
|
|
||||||
|
PAREN_OPEN = '('
|
||||||
|
PAREN_CLOSE = ')'
|
||||||
|
DOUBLE_QUOTE = '"'
|
||||||
|
WHITESPACE = ' \t\n\r'
|
||||||
|
|
||||||
|
|
||||||
|
class StringReader:
|
||||||
|
def __init__(self, s):
|
||||||
|
self._src = s
|
||||||
|
self._len = len(s)
|
||||||
|
self._cur = -1
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
if self._len == self._cur + 1:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self._cur += 1
|
||||||
|
return self._src[self._cur]
|
||||||
|
|
||||||
|
|
||||||
|
class TangoParser:
|
||||||
|
def __init__(self, src):
|
||||||
|
self._cur = None
|
||||||
|
self._src = src
|
||||||
|
self._next()
|
||||||
|
|
||||||
|
def parse(self):
|
||||||
|
while self._cur is not None:
|
||||||
|
self._skip_whitespace()
|
||||||
|
if self._cur is PAREN_OPEN:
|
||||||
|
return self._parse_expr()
|
||||||
|
elif self._cur is DOUBLE_QUOTE:
|
||||||
|
return self._parse_str()
|
||||||
|
elif self._cur.isalpha():
|
||||||
|
return self._parse_ident()
|
||||||
|
elif self._cur.isnumeric():
|
||||||
|
return self._parse_int()
|
||||||
|
else:
|
||||||
|
self._raise_invalid_char()
|
||||||
|
|
||||||
|
def _parse_expr(self):
|
||||||
|
expr = TangoExpr()
|
||||||
|
self._next()
|
||||||
|
while self._cur is not None:
|
||||||
|
self._skip_whitespace()
|
||||||
|
if self._cur is PAREN_CLOSE:
|
||||||
|
self._next()
|
||||||
|
return expr
|
||||||
|
else:
|
||||||
|
expr.append(self.parse())
|
||||||
|
|
||||||
|
self._raise_unexpected_end()
|
||||||
|
|
||||||
|
def _parse_ident(self):
|
||||||
|
ident = self._read_until(WHITESPACE + PAREN_CLOSE)
|
||||||
|
return TangoIdent(ident)
|
||||||
|
|
||||||
|
def _parse_str(self):
|
||||||
|
self._next()
|
||||||
|
s = self._read_until(DOUBLE_QUOTE)
|
||||||
|
self._next()
|
||||||
|
return s
|
||||||
|
|
||||||
|
def _parse_int(self):
|
||||||
|
s = self._read_until(WHITESPACE + PAREN_CLOSE)
|
||||||
|
return int(s)
|
||||||
|
|
||||||
|
def _read_until(self, delims):
|
||||||
|
s = ''
|
||||||
|
|
||||||
|
while self._cur is not None and self._cur not in delims:
|
||||||
|
s += self._cur
|
||||||
|
self._next()
|
||||||
|
|
||||||
|
return s
|
||||||
|
|
||||||
|
def _next(self):
|
||||||
|
self._cur = self._src.next()
|
||||||
|
return self._cur
|
||||||
|
|
||||||
|
def _raise_unexpected_end(self):
|
||||||
|
raise ValueError('Unexpected end of stream')
|
||||||
|
|
||||||
|
def _raise_invalid_char(self):
|
||||||
|
raise ValueError('Invalid chr: ' + self._cur)
|
||||||
|
|
||||||
|
def _skip_whitespace(self):
|
||||||
|
if self._cur in WHITESPACE:
|
||||||
|
while self._next() in WHITESPACE:
|
||||||
|
continue
|
28
tango/scope.py
Normal file
28
tango/scope.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
class TangoScope:
|
||||||
|
def __init__(self):
|
||||||
|
self.defs = {}
|
||||||
|
|
||||||
|
def set_def(self, ident, val):
|
||||||
|
self.defs[ident] = val
|
||||||
|
|
||||||
|
def get_def(self, ident):
|
||||||
|
return self.defs.get(ident)
|
||||||
|
|
||||||
|
class ChildTangoScope(TangoScope):
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
def get_def_local(self, ident):
|
||||||
|
return self.defs.get(ident)
|
||||||
|
|
||||||
|
def get_def(self, ident):
|
||||||
|
local = self.get_def_local(ident)
|
||||||
|
if local is None:
|
||||||
|
return self.parent.get_def(ident)
|
||||||
|
else:
|
||||||
|
return local
|
||||||
|
|
||||||
|
|
||||||
|
class RootTangoScope(TangoScope):
|
||||||
|
pass
|
26
tango/utils.py
Normal file
26
tango/utils.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from tango.value import TangoIdent, TangoExpr
|
||||||
|
|
||||||
|
def is_callable(fn):
|
||||||
|
return hasattr(fn, '__call__')
|
||||||
|
|
||||||
|
|
||||||
|
def is_ident(ident):
|
||||||
|
return isinstance(ident, TangoIdent)
|
||||||
|
|
||||||
|
|
||||||
|
def is_expr(expr):
|
||||||
|
return isinstance(expr, TangoExpr)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_if_ident(scope, val):
|
||||||
|
if is_ident(val):
|
||||||
|
scope.get_def(val)
|
||||||
|
else:
|
||||||
|
val
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_idents(scope, vals):
|
||||||
|
resolved = []
|
||||||
|
for val in vals:
|
||||||
|
resolved.append(resolve_if_ident(scope, val))
|
||||||
|
return resolved
|
19
tango/value.py
Normal file
19
tango/value.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
class TangoExpr(list):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_unit(self):
|
||||||
|
return len(self) == 0
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_block(self):
|
||||||
|
return len(self) > 0 and isinstance(self[0], TangoExpr)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_call(self):
|
||||||
|
return len(self) > 0 and isinstance(self[0], TangoIdent)
|
||||||
|
|
||||||
|
|
||||||
|
class TangoIdent(str):
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return super().__str__()
|
83
tango/vm.py
Normal file
83
tango/vm.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import tango.utils as utils
|
||||||
|
from tango.value import TangoExpr, TangoIdent
|
||||||
|
from tango.scope import RootTangoScope, ChildTangoScope
|
||||||
|
from tango.parser import StringReader, TangoParser
|
||||||
|
|
||||||
|
|
||||||
|
class TangoVM:
|
||||||
|
def __init__(self):
|
||||||
|
self.root_scope = RootTangoScope()
|
||||||
|
|
||||||
|
def eval_string(self, s):
|
||||||
|
r = StringReader(s)
|
||||||
|
p = TangoParser(r)
|
||||||
|
e = p.parse()
|
||||||
|
return self.eval(e)
|
||||||
|
|
||||||
|
def eval(self, expr):
|
||||||
|
if utils.is_expr(expr):
|
||||||
|
return self.eval_in_scope(self.root_scope, expr)
|
||||||
|
else:
|
||||||
|
raise ValueError('Root was not Tango Expression')
|
||||||
|
|
||||||
|
def def_builtin(self, ident, builtin):
|
||||||
|
if isinstance(ident, str):
|
||||||
|
ident = TangoIdent(ident)
|
||||||
|
if utils.is_ident(ident):
|
||||||
|
self.root_scope.set_def(ident, builtin)
|
||||||
|
else:
|
||||||
|
raise ValueError('Not ident')
|
||||||
|
|
||||||
|
def eval_in_scope(self, scope, val):
|
||||||
|
if utils.is_expr(val):
|
||||||
|
if val.is_unit:
|
||||||
|
return None
|
||||||
|
elif val.is_block:
|
||||||
|
return self._do_block(scope, val)
|
||||||
|
elif val.is_call:
|
||||||
|
return self._do_call(scope, val)
|
||||||
|
if utils.is_ident(val):
|
||||||
|
return scope.get_def(val)
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
|
def multi_eval_in_scope(self, scope, vals):
|
||||||
|
return [self.eval_in_scope(scope, val) for val in vals]
|
||||||
|
|
||||||
|
def _do_eval_child(self, scope, expr, args = TangoExpr()):
|
||||||
|
child_scope = ChildTangoScope(scope)
|
||||||
|
child_scope.set_def('$', args)
|
||||||
|
return self.eval_in_scope(child_scope, expr)
|
||||||
|
|
||||||
|
|
||||||
|
def _do_block(self, scope, block_expr):
|
||||||
|
for expr in block_expr:
|
||||||
|
res = self.eval_in_scope(scope, expr)
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _do_call(self, scope, call_expr):
|
||||||
|
fn_ident, *fn_args = call_expr
|
||||||
|
fn_args = TangoExpr(fn_args)
|
||||||
|
fn_body = scope.get_def(fn_ident)
|
||||||
|
|
||||||
|
if fn_body is None:
|
||||||
|
TangoVM._rt_error('ident `{}` is undefined'.format(fn_ident))
|
||||||
|
|
||||||
|
if utils.is_callable(fn_body):
|
||||||
|
return fn_body(self, scope, *fn_args)
|
||||||
|
|
||||||
|
if utils.is_expr(fn_body):
|
||||||
|
if fn_body.is_unit:
|
||||||
|
return None
|
||||||
|
if fn_body.is_block:
|
||||||
|
return self._do_eval_child(scope, fn_body, fn_args)
|
||||||
|
elif fn_body.is_call:
|
||||||
|
fn_body = self._do_call(scope, fn_body)
|
||||||
|
return self._do_eval_child(scope, fn_body, fn_args)
|
||||||
|
|
||||||
|
TangoVM._rt_error('fatal error')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _rt_error(msg):
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user