diff --git a/Justfile b/Justfile index b676118..49f7016 100644 --- a/Justfile +++ b/Justfile @@ -1,2 +1,2 @@ watch: - watchexec -ce py,lark,ag -i gen 'mypy *.py && python gen.py && mypy gen/*.py' + watchexec -ce py,lark,ag -i gen 'mypy *.py && python agmain.py && mypy gen/*.py' diff --git a/agast.py b/agast.py index 19a249f..cfe79ef 100644 --- a/agast.py +++ b/agast.py @@ -3,28 +3,50 @@ from lark import Transformer, Tree, Token T = TypeVar("T") +class Ast: + # def __new__(cls: Type[Ast], name: str, bases: Tuple[type], namespace: Dict[str, Any]) -> Ast: + # x = super().__new__(cls, name, bases, namespace) + # x.id = cls.__gen() + # return x + id: str + n = 0 + @classmethod + def __gen(cls, name: str = "") -> str: + newid = cls.n + cls.n += 1 + return f"_a{newid}{name}" + def __init__(self) -> None: + self.id = self.__gen() + class Decl: name: str -class Iface(Decl): - def __init__(self, name: str): - self.name = name -class IfaceField: pass -class IfaceRef: pass -class Expr: pass +class IfaceRef(str): pass +class IfaceField: + def __init__(self, name: str, ty: str): + self.name = name + self.ty = ty +class Iface(Decl): + def __init__(self, name: str, fields: List[IfaceField]): + self.name = name + self.fields = fields + +class Expr(Ast): + def __init__(self) -> None: + super().__init__() class NodeRef: pass -class NodeRefByName(NodeRef): +class NodeRefByName(NodeRef, str): def __init__(self, name: str): self.name = name def __repr__(self) -> str: return f"NodeRefByName({self.name})" class Sym: pass class SymRename(Sym): - def __init__(self, name: str, node_ref: NodeRef): + def __init__(self, name: str, ty: NodeRef): self.name = name - self.node_ref = node_ref - def __repr__(self) -> str: return f"SymRename({self.name} : {self.node_ref})" + self.ty = ty + def __repr__(self) -> str: return f"SymRename({self.name} : {self.ty})" class Equation: def __init__(self, lhs: Expr, rhs: Expr): self.lhs = lhs @@ -45,26 +67,31 @@ class Node(Decl): class ExprDot(Expr): def __init__(self, left: Expr, right: str): + super().__init__() self.left = left self.right = right def __repr__(self) -> str: return f"{self.left}.{self.right}" class ExprAdd(Expr): def __init__(self, left: Expr, right: Expr): + super().__init__() self.left = left self.right = right def __repr__(self) -> str: return f"{self.left} + {self.right}" class ExprMul(Expr): def __init__(self, left: Expr, right: Expr): + super().__init__() self.left = left self.right = right def __repr__(self) -> str: return f"{self.left} * {self.right}" class ExprCall(Expr): def __init__(self, func: Expr, args: List[Expr]): + super().__init__() self.func = func self.args = args def __repr__(self) -> str: return f"{self.func}({self.args})" class ExprName(Expr): def __init__(self, name: str): + super().__init__() self.name = name def __repr__(self) -> str: return f"{self.name}" @@ -74,10 +101,10 @@ class Parser(Transformer[List[Decl]]): # interfaces def iface(self, items: List[Any]) -> Iface: [name, fields] = items - return Iface(name) + return Iface(name, fields) def iface_field(self, items: List[str]) -> IfaceField: [name, ty] = items - return IfaceField() + return IfaceField(name, ty) def iface_ref(self, items: List[str]) -> str: return items[0] def iface_refs(self, items: List[IfaceRef]) -> List[IfaceRef]: return items @@ -98,7 +125,7 @@ class Parser(Transformer[List[Decl]]): # equations def equations(self, items: List[Equation]) -> List[Equation]: return items - def equation_(self, items: List[Equation]) -> Equation: return items[0] + def equation_semi(self, items: List[Equation]) -> Equation: return items[0] def equation(self, items: List[Expr]) -> Equation: return Equation(items[0], items[1]) # expr diff --git a/aggen.py b/aggen.py new file mode 100644 index 0000000..d6b0b22 --- /dev/null +++ b/aggen.py @@ -0,0 +1,140 @@ +from typing import * +import textwrap +import re +import copy + +from agast import * + +global i +i = 0 + +class GenResult: + def __init__(self, pd: str = "", ex: str = ""): + self.parser_data = pd + self.extra = ex + +def gen(program: List[Decl]) -> GenResult: + res = GenResult() + def gen(prefix: str = "", suffix: str = "") -> str: + global i + presan = re.sub("[^0-9a-zA-Z]+", "_", prefix) + sufsan = re.sub("[^0-9a-zA-Z]+", "_", suffix) + i += 1 + return f"{presan}{i}{sufsan}" + def v(name: str) -> str: + return f"__ag_{name}" + + # builtins + builtins: Dict[str, str] = { + "parseInt": "", + } + + # collect a list of name -> iface declarations + ifaces: Dict[str, Iface] = dict( + map(lambda c: (c.name, cast(Iface, c)), + filter(lambda c: isinstance(c, Iface), + program))) + + # list of node -> iface mappings + what_ifaces: Dict[str, Set[str]] = dict() + what_fields: Dict[str, Dict[str, str]] = dict() + for node in filter(lambda c: isinstance(c, Node), program): + node = cast(Node, node) + # all_fields = dict() + what_ifaces[node.name] = set(node.ifaces) + this_fields = dict() + for iface in node.ifaces: + fields = ifaces[iface].fields + for field in fields: + if field.name in this_fields: + raise Exception("duplicate field name") + this_fields[field.name] = field.ty + what_fields[node.name] = this_fields + print("what_ifaces:", what_ifaces) + print("what_fields:", what_fields) + + # a high-level dictionary of productions; this has sub-productions + # that should be further expanded at a later step before converting + # into lark code + productions_hi: Dict[str, Union[str, List[str]]] = dict() + + # TODO: this should probably not be inlined here, but i'll move it + # out once i get more info into the 'env' + def collect_required_thunks(env: List[Tuple[str, NodeRef]], expr: Expr) -> Dict[str, str]: + names = dict(env) + print(f"collect_required_thunks({expr})", expr.__class__) + if isinstance(expr, ExprDot): + return collect_required_thunks(env, expr.left) + elif isinstance(expr, ExprMul): + a = collect_required_thunks(env, expr.left) + b = collect_required_thunks(env, expr.right) + a.update(b) + return a + elif isinstance(expr, ExprAdd): + a = collect_required_thunks(env, expr.left) + b = collect_required_thunks(env, expr.right) + a.update(b) + return a + elif isinstance(expr, ExprCall): + return collect_required_thunks(env, expr.func) + elif isinstance(expr, ExprName): + if expr.name not in names and expr.name not in builtins: + raise Exception(f"unbound name '{expr.name}'") + return dict() + raise Exception(f"unhandled {expr.__class__}") + + for node in filter(lambda c: isinstance(c, Node), program): + node = cast(Node, node) + n_class_name = gen(node.name) + class_decl = textwrap.dedent(f""" + class {v(n_class_name)}: pass + """) + res.extra += class_decl + + print(node.name, node.ifaces) + + for variant in node.variants: + v_class_name = gen(f"{n_class_name}_var") + class_decl = textwrap.dedent(f""" + class {v(v_class_name)}({v(n_class_name)}): + ''' ''' + pass + """) + res.extra += class_decl + + prod_name = gen(node.name) + print(prod_name) + + # create an environment for checking the equations based on + # the production + env: List[Tuple[str, NodeRef]] = list() + for sym in variant.prod: + if isinstance(sym, SymRename): + env.append((sym.name, sym.ty)) + print(env) + + # for each of the equations, find out what the equation is + # trying to compute, and generate a thunk corresponding to + # that value. + for eq in variant.equations: + eq_name = gen(f"eq_{node.name}") + thunk_name = gen(f"thunk_{node.name}") + + print("RHS", eq.rhs, eq.rhs.id) + collect_required_thunks(copy.deepcopy(env), eq.rhs) + + func_impl = textwrap.dedent(f""" + def {eq_name}() -> None: + ''' {repr(eq)} ''' + pass + def {thunk_name}() -> Thunk[None]: + return Thunk({eq_name}) + """) + print(f"```py\n{func_impl}\n```") + res.extra += func_impl + + # this is a "type alias" that connects it to one of the generated + # names above + res.extra += f"{node.name} = {v(n_class_name)}" + + return res \ No newline at end of file diff --git a/gen.py b/agmain.py similarity index 86% rename from gen.py rename to agmain.py index abb860a..4eb5614 100644 --- a/gen.py +++ b/agmain.py @@ -4,7 +4,7 @@ import importlib from lark import Lark from agast import * -from agtypeck import * +from aggen import * p = Lark(open("grammar.lark").read(), start="program", parser="lalr") @@ -13,22 +13,24 @@ if __name__ == "__main__": data = f.read() cst = p.parse(data) - # print("cst", cst) trans = Parser() ast = trans.transform(cst) print("ast", ast) - res = typecheck(ast) + res = gen(ast) if not os.path.exists("gen"): os.makedirs("gen") with open("gen/arith.py", "w") as f: fmt_str = textwrap.dedent(""" __all__ = ["parse"] - from typing import Generic, TypeVar, Optional, Callable + from typing import Generic, TypeVar, Optional, Callable, Dict, Any from lark import Lark, Transformer T = TypeVar('T') + builtins: Dict[str, Any] = {{ + "parseInt": lambda s: int(s) + }} class Thunk(Generic[T]): ''' A thunk represents a value that may be computed lazily. ''' value: Optional[T] diff --git a/agtypeck.py b/agtypeck.py deleted file mode 100644 index 70b9e2c..0000000 --- a/agtypeck.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import * -import textwrap -import re -from agast import * - -global i -i = 0 - -class TypecheckResult: - def __init__(self, pd: str = "", ex: str = ""): - self.parser_data = pd - self.extra = ex - -def typecheck(program: List[Decl]) -> TypecheckResult: - res = TypecheckResult() - def gen(prefix: str = "", suffix: str = "") -> str: - global i - presan = re.sub("[^0-9a-zA-Z]+", "_", prefix) - sufsan = re.sub("[^0-9a-zA-Z]+", "_", suffix) - i += 1 - return f"{presan}{i}{sufsan}" - def v(name: str) -> str: - return f"__ag_{name}" - - # collect a list of name -> iface declarations - ifaces: Dict[str, Decl] = dict( - map(lambda c: (c.name, c), - filter(lambda c: isinstance(c, Iface), - program))) - print(ifaces) - - # a high-level dictionary of productions; this has sub-productions - # that should be further expanded at a later step before converting - # into lark code - productions_hi: Dict[str, Union[str, List[str]]] = dict() - - for node in filter(lambda c: isinstance(c, Node), program): - node = cast(Node, node) - n_class_name = gen(node.name) - class_decl = textwrap.dedent(f""" - class {v(n_class_name)}: pass - """) - res.extra += class_decl - - print(node.name, node.ifaces) - - for variant in node.variants: - v_class_name = gen(f"{n_class_name}_var") - class_decl = textwrap.dedent(f""" - class {v(v_class_name)}({v(n_class_name)}): - ''' ''' - pass - """) - res.extra += class_decl - - prod_name = gen(node.name) - print(prod_name) - - for sym in variant.prod: - print("sym", sym) - - # for each of the equations, find out what the equation is - # trying to compute, and generate a thunk corresponding to - # that value. - for eq in variant.equations: - print("--eq", eq) - print(eq.lhs) - eq_name = gen(f"eq_{node.name}") - thunk_name = gen(f"thunk_{node.name}") - - func_impl = textwrap.dedent(f""" - def {eq_name}() -> None: - ''' {repr(eq)} ''' - pass - def {thunk_name}() -> Thunk[None]: - return Thunk({eq_name}) - """) - print(f"```py\n{func_impl}\n```") - res.extra += func_impl - - # this is a "type alias" that connects it to one of the generated - # names above - res.extra += f"{node.name} = {v(n_class_name)}" - - return res \ No newline at end of file diff --git a/arith.ag b/arith.ag index 90867bc..a1008ac 100644 --- a/arith.ag +++ b/arith.ag @@ -4,7 +4,7 @@ iface HasValue { node Expr : HasValue { "+" => { - self.val = l.val + r.val; + self.val = l.val + r.val * l.val; } "*" => { self.val = l.val * r.val; diff --git a/grammar.lark b/grammar.lark index 008d538..a876b6c 100644 --- a/grammar.lark +++ b/grammar.lark @@ -1,8 +1,8 @@ program: decl* ?decl: iface - | node - | func + | node + | func sep_trail{item, punc}: item (punc item)? punc? @@ -17,28 +17,35 @@ node: "node" ident ":" iface_refs "{" variants "}" variants: variant* variant: prod "=>" "{" equations "}" prod: sym* -sym: sym_rename - | STRING +?sym: sym_rename + | STRING sym_rename: "<" ident ":" node_ref ">" -node_ref: node_ref_name - | STRING +?node_ref: node_ref_name + | STRING node_ref_name: ident -equations: equation_* -equation_: equation ";" +equations: equation_semi* +equation_semi: equation ";" // TODO: the left side should really be a separate type // called lvalue, and should NOT include literals equation: expr "=" expr -?expr: expr_dot - | expr_add - | expr_mul - | expr_call - | expr_name -expr_dot: expr "." expr -expr_add: expr "+" expr -expr_mul: expr "*" expr -expr_call: expr "(" args ")" +// Expressions +?expr: expr2 + | expr_add +expr_add: expr "+" expr2 + +?expr2: expr3 + | expr_mul + | expr_call +expr_mul: expr2 "*" expr3 +expr_call: expr2 "(" args ")" + +?expr3: "(" expr ")" + | expr_dot + | expr_name +expr_dot: expr3 "." ident expr_name: ident + args: sep_trail{expr, ","} ty: ident diff --git a/main.py b/old.py similarity index 100% rename from main.py rename to old.py diff --git a/run.ps1 b/run.ps1 index 896b876..4ceda14 100644 --- a/run.ps1 +++ b/run.ps1 @@ -12,5 +12,5 @@ function Invoke-Call { } Invoke-Call -ScriptBlock {mypy (get-item *.py) } -ErrorAction Stop -Invoke-Call -ScriptBlock {python gen.py } -ErrorAction Stop +Invoke-Call -ScriptBlock {python agmain.py } -ErrorAction Stop Invoke-Call -ScriptBlock {mypy (get-item gen/*.py) } -ErrorAction Stop \ No newline at end of file