Initial commit

This commit is contained in:
avitex 2019-03-13 23:10:46 -07:00
commit 9e9fed8384
8 changed files with 306 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode
__pycache__

56
run.py Normal file
View 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
View File

92
tango/parser.py Normal file
View 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
View 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
View 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
View 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
View 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)