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