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