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