From cba06ef585f49eef4a87fd65d8a9e782ed82c2e4 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 13:11:12 -0500 Subject: [PATCH 01/16] rename struct to scratch in ObjectType --- tealish/expression_nodes.py | 4 ++-- tealish/nodes.py | 4 ++-- tealish/tealish_builtins.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 42db892..b3cde2d 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -49,7 +49,7 @@ def process(self) -> None: raise CompileError(e.args[0], node=self) # is it a struct or box? if type(self.type) == tuple: - if self.type[0] == ObjectType.struct: + if self.type[0] == ObjectType.scratch: self.type = AVMType.bytes elif self.type[0] == ObjectType.box: raise CompileError("Invalid use of a Box reference", node=self) @@ -479,7 +479,7 @@ def process(self) -> None: self.type = struct_field.data_type def write_teal(self, writer: "TealWriter") -> None: - if self.object_type == ObjectType.struct: + if self.object_type == ObjectType.scratch: writer.write(self, f"load {self.slot} // {self.name}") if self.type == AVMType.int: writer.write(self, f"pushint {self.offset}") diff --git a/tealish/nodes.py b/tealish/nodes.py index ec2dfa0..801da79 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -1608,7 +1608,7 @@ class StructDeclaration(LineStatement): def process(self) -> None: self.name.slot = self.declare_var( - self.name.value, (ObjectType.struct, self.struct_name) + self.name.value, (ObjectType.scratch, self.struct_name) ) if self.expression: self.expression.process() @@ -1667,7 +1667,7 @@ def process(self) -> None: ) def write_teal(self, writer: "TealWriter") -> None: - if self.object_type == ObjectType.struct: + if self.object_type == ObjectType.scratch: writer.write(self, f"// {self.line} [slot {self.name.slot}]") writer.write(self, f"load {self.name.slot} // {self.name.value}") writer.write(self, self.expression) diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index cd6aafe..28e88e0 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -21,7 +21,7 @@ class ObjectType(str, Enum): `box` - the field is in a box, use box_extract to get the bytes """ - struct = "struct" + scratch = "scratch" box = "box" From 811782a3c279052bb275782d4bfc2292223fc09e Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 14:25:00 -0500 Subject: [PATCH 02/16] adding handling of bigint --- examples/rich_types.tl | 9 +++++++++ tealish/base.py | 9 ++++++++- tealish/expression_nodes.py | 26 +++++++++++++++++++++----- tealish/nodes.py | 21 ++++++++++++++++++--- tealish/scope.py | 16 ++++++++++++---- tealish/tealish_builtins.py | 15 ++++++++++++--- 6 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 examples/rich_types.tl diff --git a/examples/rich_types.tl b/examples/rich_types.tl new file mode 100644 index 0000000..10cda13 --- /dev/null +++ b/examples/rich_types.tl @@ -0,0 +1,9 @@ +#pragma version 8 + +const int I = 123 +const bytes B = "abc" +const bigint Bi = 1231231231231231231231231231231231 +const bigint One = 1 +assert((Bi / Bi) == One) + +exit(1) \ No newline at end of file diff --git a/tealish/base.py b/tealish/base.py index a1e43ba..3e90cf4 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -1,6 +1,6 @@ from typing import cast, Any, Dict, List, Optional, Tuple, TYPE_CHECKING from tealish.errors import CompileError -from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType +from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType, TealishType from .langspec import get_active_langspec, Op from .scope import Scope @@ -160,6 +160,13 @@ def lookup_const(self, name: str) -> Tuple["AVMType", ConstValue]: def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: return lang_spec.lookup_avm_constant(name) + def tealish_type(self) -> TealishType: + if hasattr(self, "t_type"): + return getattr(self, "t_type") + print(self.__class__) + # TODO: cop out + return TealishType.int + # TODO: these attributes are only available on Node and other children types # we should either define them here or something else? @property diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index b3cde2d..7c605b4 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -2,7 +2,13 @@ from .base import BaseNode from .errors import CompileError -from .tealish_builtins import AVMType, get_struct, ObjectType +from .tealish_builtins import ( + AVMType, + get_struct, + ObjectType, + TealishType, + tealish_to_avm_type, +) from .langspec import Op, type_lookup @@ -64,8 +70,8 @@ def _tealish(self) -> str: class Constant(BaseNode): def __init__(self, name: str, parent: Optional[BaseNode] = None) -> None: self.name = name - self.type: AVMType = AVMType.none self.parent = parent + self.type: TealishType def process(self) -> None: type, value = None, None @@ -80,10 +86,11 @@ def process(self) -> None: raise CompileError( f'Constant "{self.name}" not declared in scope', node=self ) - if type not in (AVMType.int, AVMType.bytes): - raise CompileError(f"Unexpected const type {type}", node=self) + # if type not in (AVMType.int, AVMType.bytes): + # raise CompileError(f"Unexpected const type {type}", node=self) - self.type = type + self.t_type = type + self.type = tealish_to_avm_type(type) self.value = value def write_teal(self, writer: "TealWriter") -> None: @@ -130,6 +137,14 @@ def __init__( def process(self) -> None: self.a.process() self.b.process() + + print(self.a.tealish_type()) + print(self.b.tealish_type()) + print(self.op) + if self.a.tealish_type() == TealishType.bigint: + self.t_type = TealishType.bigint + self.op = "b" + self.op + self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) self.type = type_lookup(op.returns) @@ -154,6 +169,7 @@ def __init__( def process(self) -> None: self.expression.process() self.type = self.expression.type + self.t_type = self.expression.tealish_type() def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.expression) diff --git a/tealish/nodes.py b/tealish/nodes.py index 801da79..f717c4a 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -16,6 +16,7 @@ from .tx_expressions import parse_expression from .tealish_builtins import ( AVMType, + TealishType, ObjectType, define_struct, get_struct, @@ -373,23 +374,37 @@ def _tealish(self) -> str: class Const(LineStatement): + type_pattern = "|".join([f"\\b{tt.value}\\b" for tt in TealishType]) + print(type_pattern) + pattern = ( - r"const (?P\bint\b|\bbytes\b) " + rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" ) + t_type: TealishType type: AVMType name: str expression: Literal def process(self) -> None: scope = self.get_current_scope() - scope.declare_const(self.name, (self.type, self.expression.value)) + self.type = ( + AVMType.bytes if self.t_type != TealishType.int.value else AVMType.int + ) + + # TODO: have to compare against Enum.value ? + if self.t_type == TealishType.bigint.value: + # Hardcoded to uint256 or 32 bytes + new_value = cast(int, int(self.expression.value)).to_bytes(32, "big") + self.expression.value = f"0x{new_value.hex()}" + + scope.declare_const(self.name, (self.t_type, self.expression.value)) def write_teal(self, writer: "TealWriter") -> None: pass def _tealish(self) -> str: - s = f"const {self.type} {self.name}" + s = f"const {self.t_type} {self.name}" if self.expression: s += f" = {self.expression.tealish()}" return s + "\n" diff --git a/tealish/scope.py b/tealish/scope.py index 645593e..14a5842 100644 --- a/tealish/scope.py +++ b/tealish/scope.py @@ -1,8 +1,14 @@ -from typing import Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, Optional, Tuple, TYPE_CHECKING, Union if TYPE_CHECKING: - from .tealish_builtins import AVMType, VarType, ConstValue, ScratchRecord + from .tealish_builtins import ( + AVMType, + VarType, + ConstValue, + ScratchRecord, + TealishType, + ) from .nodes import Func, Block @@ -59,11 +65,13 @@ def delete_var(self, name: str) -> None: del self.slots[name] def declare_const( - self, name: str, const_data: Tuple["AVMType", "ConstValue"] + self, + name: str, + const_data: Tuple["TealishType", "ConstValue"], ) -> None: self.consts[name] = const_data - def lookup_const(self, name: str) -> Tuple["AVMType", "ConstValue"]: + def lookup_const(self, name: str) -> Tuple["TealishType", "ConstValue"]: if name not in self.consts: raise KeyError(f'Const "{name}" not declared in current scope') return self.consts[name] diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index 28e88e0..595e282 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -12,12 +12,10 @@ class AVMType(str, Enum): none = "" -# TODO: add frame ptr or stack? rename to something like `storage type?` -# I think `struct` here should probably just be `scratch`? class ObjectType(str, Enum): """ObjectType determines where to get the bytes for a struct field. - `struct` - the field is in a byte array in a scratch var, use extract to get bytes + `scratch` - the field is in a byte array in a scratch var, use extract to get bytes `box` - the field is in a box, use box_extract to get the bytes """ @@ -25,6 +23,17 @@ class ObjectType(str, Enum): box = "box" +class TealishType(str, Enum): + int = "int" + bytes = "bytes" + bigint = "bigint" + addr = "addr" + + +def tealish_to_avm_type(tt: TealishType) -> AVMType: + return AVMType.bytes if tt != TealishType.int.value else AVMType.int + + # TODO: for CustomType and ScratchRecord we should consider # making them dataclasses or something instead of a tuple # to make it more obvious what the fields are From cdf6ab4ff67d2aef7eee806724f756d4d1f35bfc Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 15:07:50 -0500 Subject: [PATCH 03/16] mypy hates it but it works --- tealish/base.py | 22 +++++++++---- tealish/expression_nodes.py | 44 ++++++++++++-------------- tealish/nodes.py | 63 +++++++++++++++++++------------------ tealish/scope.py | 5 ++- tealish/tealish_builtins.py | 41 ++++++++++++++---------- 5 files changed, 95 insertions(+), 80 deletions(-) diff --git a/tealish/base.py b/tealish/base.py index 3e90cf4..1591815 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -1,6 +1,13 @@ from typing import cast, Any, Dict, List, Optional, Tuple, TYPE_CHECKING from tealish.errors import CompileError -from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType, TealishType +from .tealish_builtins import ( + AVMType, + ConstValue, + ScratchRecord, + VarType, + TealishType, + stack_type, +) from .langspec import get_active_langspec, Op from .scope import Scope @@ -17,15 +24,18 @@ def check_arg_types(name: str, incoming_args: List["Node"]) -> None: expected_args = op.arg_types # TODO: for i, incoming_arg in enumerate(incoming_args): - if incoming_arg.type == AVMType.any: # type: ignore + tealish_type = incoming_arg.tealish_type() + avm_type = stack_type(tealish_type) + + if avm_type == AVMType.any: # type: ignore continue if expected_args[i] == AVMType.any: continue - if incoming_arg.type == expected_args[i]: # type: ignore + if avm_type == expected_args[i]: # type: ignore continue raise Exception( - f"Incorrect type {incoming_arg.type} " # type: ignore + f"Incorrect type {tealish_type} " # type: ignore + f"for arg {i} of {name}. Expected {expected_args[i]}" ) @@ -161,8 +171,8 @@ def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: return lang_spec.lookup_avm_constant(name) def tealish_type(self) -> TealishType: - if hasattr(self, "t_type"): - return getattr(self, "t_type") + if hasattr(self, "type"): + return getattr(self, "type") print(self.__class__) # TODO: cop out return TealishType.int diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 7c605b4..eeb20d8 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -7,7 +7,7 @@ get_struct, ObjectType, TealishType, - tealish_to_avm_type, + stack_type, ) from .langspec import Op, type_lookup @@ -20,7 +20,7 @@ class Integer(BaseNode): def __init__(self, value: str, parent: Optional[BaseNode] = None) -> None: self.value = int(value) - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -33,7 +33,7 @@ def _tealish(self) -> str: class Bytes(BaseNode): def __init__(self, value: str, parent: Optional[BaseNode] = None) -> None: self.value = value - self.type = AVMType.bytes + self.type = TealishType.bytes self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -56,7 +56,7 @@ def process(self) -> None: # is it a struct or box? if type(self.type) == tuple: if self.type[0] == ObjectType.scratch: - self.type = AVMType.bytes + self.type = TealishType.bytes elif self.type[0] == ObjectType.box: raise CompileError("Invalid use of a Box reference", node=self) @@ -89,14 +89,13 @@ def process(self) -> None: # if type not in (AVMType.int, AVMType.bytes): # raise CompileError(f"Unexpected const type {type}", node=self) - self.t_type = type - self.type = tealish_to_avm_type(type) + self.type = type self.value = value def write_teal(self, writer: "TealWriter") -> None: - if self.type == AVMType.int: + if stack_type(self.type) == AVMType.int: writer.write(self, f"pushint {self.value} // {self.name}") # type: ignore - elif self.type == AVMType.bytes: + elif stack_type(self.type) == AVMType.bytes: writer.write(self, f"pushbytes {self.value} // {self.name}") # type: ignore def _tealish(self) -> str: @@ -138,9 +137,6 @@ def process(self) -> None: self.a.process() self.b.process() - print(self.a.tealish_type()) - print(self.b.tealish_type()) - print(self.op) if self.a.tealish_type() == TealishType.bigint: self.t_type = TealishType.bigint self.op = "b" + self.op @@ -185,7 +181,7 @@ def __init__( self.name = name self.args = args self.parent = parent - self.type: Union[AVMType, List[AVMType]] = AVMType.none + self.type: Union[TealishType, List[TealishType]] = TealishType.none self.func_call_type: str = "" self.nodes = args self.immediate_args = "" @@ -251,7 +247,7 @@ def process_op_call(self, op: Op) -> None: def process_special_call(self) -> None: self.func_call_type = "special" if self.name == "pop": - self.type = AVMType.any + self.type = TealishType.any for arg in self.args: arg.process() @@ -291,7 +287,7 @@ def _tealish(self) -> str: class TxnField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -313,7 +309,7 @@ def __init__( ) -> None: self.field = field self.arrayIndex = arrayIndex - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -340,7 +336,7 @@ def __init__( ) -> None: self.field = field self.index = index - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -376,7 +372,7 @@ def __init__( self.field = field self.index = index self.arrayIndex = arrayIndex - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -419,7 +415,7 @@ def _tealish(self) -> str: class PositiveGroupIndex(BaseNode): def __init__(self, index: int, parent: Optional["BaseNode"] = None) -> None: self.index = index - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -434,7 +430,7 @@ def _tealish(self) -> str: class NegativeGroupIndex(BaseNode): def __init__(self, index: int, parent: Optional[BaseNode] = None) -> None: self.index = index - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -449,7 +445,7 @@ def _tealish(self) -> str: class GlobalField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -465,7 +461,7 @@ def _tealish(self) -> str: class InnerTxnField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -482,7 +478,7 @@ class StructOrBoxField(BaseNode): def __init__(self, name, field, parent=None) -> None: self.name = name self.field = field - self.type = AVMType.none + self.type = TealishType.none self.parent = parent def process(self) -> None: @@ -497,7 +493,7 @@ def process(self) -> None: def write_teal(self, writer: "TealWriter") -> None: if self.object_type == ObjectType.scratch: writer.write(self, f"load {self.slot} // {self.name}") - if self.type == AVMType.int: + if self.type == TealishType.int: writer.write(self, f"pushint {self.offset}") writer.write(self, f"extract_uint64 // {self.field}") else: @@ -507,7 +503,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.offset} // offset") writer.write(self, f"pushint {self.size} // size") writer.write(self, f"box_extract // {self.name}.{self.field}") - if self.type == AVMType.int: + if self.type == TealishType.int: writer.write(self, "btoi") else: raise Exception() diff --git a/tealish/nodes.py b/tealish/nodes.py index f717c4a..bf135c0 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -23,6 +23,7 @@ VarType, Struct, StructField, + stack_type, ) from .scope import Scope @@ -159,8 +160,8 @@ class LiteralInt(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.value}") - def type(self) -> AVMType: - return AVMType.int + def type(self) -> TealishType: + return TealishType.int def _tealish(self) -> str: return f"{self.value}" @@ -173,8 +174,8 @@ class LiteralBytes(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushbytes {self.value}") - def type(self) -> AVMType: - return AVMType.bytes + def type(self) -> TealishType: + return TealishType.bytes def _tealish(self) -> str: return f"{self.value}" @@ -378,33 +379,29 @@ class Const(LineStatement): print(type_pattern) pattern = ( - rf"const (?P{type_pattern}) " + rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" ) - t_type: TealishType - type: AVMType + type: TealishType name: str expression: Literal def process(self) -> None: scope = self.get_current_scope() - self.type = ( - AVMType.bytes if self.t_type != TealishType.int.value else AVMType.int - ) # TODO: have to compare against Enum.value ? - if self.t_type == TealishType.bigint.value: + if self.type == TealishType.bigint.value: # Hardcoded to uint256 or 32 bytes new_value = cast(int, int(self.expression.value)).to_bytes(32, "big") self.expression.value = f"0x{new_value.hex()}" - scope.declare_const(self.name, (self.t_type, self.expression.value)) + scope.declare_const(self.name, (self.type, self.expression.value)) def write_teal(self, writer: "TealWriter") -> None: pass def _tealish(self) -> str: - s = f"const {self.t_type} {self.name}" + s = f"const {self.type} {self.name}" if self.expression: s += f" = {self.expression.tealish()}" return s + "\n" @@ -470,7 +467,7 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() - if self.arg.type not in (AVMType.int, AVMType.any): + if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " + f"Expected int, got {self.arg.type} at line {self.line_no}.", @@ -501,10 +498,10 @@ class BytesDeclaration(LineStatement): expression: GenericExpression def process(self) -> None: - self.name.slot = self.declare_var(self.name.value, AVMType.bytes) + self.name.slot = self.declare_var(self.name.value, TealishType.bytes) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.bytes, AVMType.any): raise CompileError( "Incorrect type for bytes assignment. " + f"Expected bytes, got {self.expression.type}", @@ -530,10 +527,10 @@ class IntDeclaration(LineStatement): expression: GenericExpression def process(self) -> None: - self.name.slot = self.declare_var(self.name.value, AVMType.int) + self.name.slot = self.declare_var(self.name.value, TealishType.int) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.int, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for int assignment. " + f"Expected int, got {self.expression.type}", @@ -584,7 +581,10 @@ def process(self) -> None: ) slot, var_type = var_def - if not (incoming_types[i] == AVMType.any or incoming_types[i] == var_type): + if not ( + stack_type(incoming_types[i]) == AVMType.any + or stack_type(incoming_types[i]) == var_type + ): raise CompileError( f"Incorrect type for {var_type} assignment. " + f"Expected {var_type}, got {incoming_types[i]}", @@ -1292,7 +1292,7 @@ def consume(cls, compiler: "TealishCompiler", parent: Node) -> "ForStatement": return node def process(self) -> None: - self.var_slot = self.declare_var(self.var, AVMType.int) + self.var_slot = self.declare_var(self.var, TealishType.int) for n in self.nodes: n.process() self.del_var(self.var) @@ -1388,7 +1388,7 @@ def _tealish(self) -> str: class ArgsList(Expression): arg_pattern = r"(?P[a-z][a-z_0-9]*): (?Pint|bytes)" pattern = rf"(?P({arg_pattern}(, )?)*)" - args: List[Tuple[str, AVMType]] + args: List[Tuple[str, TealishType]] def __init__(self, line: str) -> None: super().__init__(line) @@ -1406,7 +1406,7 @@ class Func(InlineStatement): args: ArgsList return_type: str - returns: List[AVMType] + returns: List[TealishType] def __init__( self, @@ -1421,7 +1421,8 @@ def __init__( self.new_scope("func__" + self.name) self.returns = list( filter( - None, [cast(AVMType, s.strip()) for s in self.return_type.split(",")] + None, + [cast(TealishType, s.strip()) for s in self.return_type.split(",")], ) ) self.slots: Dict[str, int] = {} @@ -1516,7 +1517,7 @@ class StructFieldDefinition(InlineStatement): + r"(?P[a-z][A-Z-a-z0-9_]+)(\[(?P\d+)\])?" ) field_name: str - data_type: AVMType + data_type: TealishType data_length: int offset: int @@ -1590,7 +1591,7 @@ def process(self) -> None: data_length=field_node.data_length, offset=offset, size=8 - if field_node.data_type == AVMType.int + if stack_type(field_node.data_type) == AVMType.int else int(field_node.data_length), ) fields[field_node.field_name] = sf @@ -1627,7 +1628,7 @@ def process(self) -> None: ) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.bytes, AVMType.any): raise CompileError( "Incorrect type for struct assignment. " + f"Expected bytes, got {self.expression.type}", @@ -1672,9 +1673,9 @@ def process(self) -> None: struct_field = struct.fields[self.field_name] self.offset = struct_field.offset self.size = struct_field.size - self.data_type = struct_field.data_type + self.data_type = stack_type(struct_field.data_type) self.expression.process() - if self.expression.type not in (self.data_type, AVMType.any): + if stack_type(self.expression.type) not in (self.data_type, AVMType.any): raise CompileError( "Incorrect type for struct field assignment. " + f"Expected {self.data_type}, got {self.expression.type}", @@ -1686,7 +1687,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"// {self.line} [slot {self.name.slot}]") writer.write(self, f"load {self.name.slot} // {self.name.value}") writer.write(self, self.expression) - if self.data_type == AVMType.int: + if stack_type(self.data_type) == AVMType.int: writer.write(self, "itob") writer.write( self, f"replace {self.offset} // {self.name.value}.{self.field_name}" @@ -1697,7 +1698,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"load {self.name.slot} // box key {self.name.value}") writer.write(self, f"pushint {self.offset} // offset") writer.write(self, self.expression) - if self.data_type == AVMType.int: + if stack_type(self.data_type) == AVMType.int: writer.write(self, "itob") writer.write(self, f"box_replace // {self.name.value}.{self.field_name}") @@ -1732,7 +1733,7 @@ def process(self): self.name.value, (ObjectType.box, self.struct_name) ) self.key.process() - if self.key.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.key.type) not in (AVMType.bytes, AVMType.any): raise CompileError( f"Incorrect type for box key. Expected bytes, got {self.key.type}", node=self, diff --git a/tealish/scope.py b/tealish/scope.py index 14a5842..b1218c6 100644 --- a/tealish/scope.py +++ b/tealish/scope.py @@ -1,9 +1,8 @@ -from typing import Dict, Optional, Tuple, TYPE_CHECKING, Union +from typing import Dict, Optional, Tuple, TYPE_CHECKING if TYPE_CHECKING: from .tealish_builtins import ( - AVMType, VarType, ConstValue, ScratchRecord, @@ -27,7 +26,7 @@ def __init__( slot_range if slot_range is not None else (0, 200) ) - self.consts: Dict[str, Tuple["AVMType", "ConstValue"]] = {} + self.consts: Dict[str, Tuple["TealishType", "ConstValue"]] = {} self.blocks: Dict[str, "Block"] = {} self.functions: Dict[str, "Func"] = {} diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index 595e282..d404107 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -28,10 +28,19 @@ class TealishType(str, Enum): bytes = "bytes" bigint = "bigint" addr = "addr" + any = "any" + none = "" -def tealish_to_avm_type(tt: TealishType) -> AVMType: - return AVMType.bytes if tt != TealishType.int.value else AVMType.int +def stack_type(tt: TealishType) -> AVMType: + if tt == TealishType.int.value: + return AVMType.int + elif tt == TealishType.none: + return AVMType.none + elif tt == TealishType.any: + return AVMType.any + else: + return AVMType.bytes # TODO: for CustomType and ScratchRecord we should consider @@ -45,7 +54,7 @@ def tealish_to_avm_type(tt: TealishType) -> AVMType: @dataclass class StructField: - data_type: AVMType + data_type: TealishType data_length: int offset: int size: int @@ -63,7 +72,7 @@ def __init__(self, fields: Dict[str, StructField], size: int): # either AVM native type or a CustomType (only struct atm) definition -VarType = Union[AVMType, CustomType] +VarType = Union[TealishType, CustomType] # a constant value introduced in source ConstValue = Union[str, bytes, int] @@ -83,16 +92,16 @@ def get_struct(struct_name: str) -> Struct: return _structs[struct_name] -constants: Dict[str, Tuple[AVMType, ConstValue]] = { - "NoOp": (AVMType.int, 0), - "OptIn": (AVMType.int, 1), - "CloseOut": (AVMType.int, 2), - "ClearState": (AVMType.int, 3), - "UpdateApplication": (AVMType.int, 4), - "DeleteApplication": (AVMType.int, 5), - "Pay": (AVMType.int, 1), - "Acfg": (AVMType.int, 3), - "Axfer": (AVMType.int, 4), - "Afrz": (AVMType.int, 5), - "Appl": (AVMType.int, 6), +constants: Dict[str, Tuple[TealishType, ConstValue]] = { + "NoOp": (TealishType.int, 0), + "OptIn": (TealishType.int, 1), + "CloseOut": (TealishType.int, 2), + "ClearState": (TealishType.int, 3), + "UpdateApplication": (TealishType.int, 4), + "DeleteApplication": (TealishType.int, 5), + "Pay": (TealishType.int, 1), + "Acfg": (TealishType.int, 3), + "Axfer": (TealishType.int, 4), + "Afrz": (TealishType.int, 5), + "Appl": (TealishType.int, 6), } From 9729f7db4617a88fbd8ffb97b34eb1768711e227 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 15:38:04 -0500 Subject: [PATCH 04/16] twerkin --- tealish/base.py | 10 ++++------ tealish/expression_nodes.py | 14 ++++++++++---- tealish/langspec.py | 32 ++++++++++++++++---------------- tealish/nodes.py | 16 +++++++++------- tealish/tealish_builtins.py | 9 ++++++--- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/tealish/base.py b/tealish/base.py index 1591815..d1762dc 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -152,7 +152,7 @@ def check_arg_types(self, name: str, args: List["Node"]) -> None: except Exception as e: raise CompileError(str(e), node=self) # type: ignore - def get_field_type(self, namespace: str, name: str) -> AVMType: + def get_field_type(self, namespace: str, name: str) -> TealishType: return lang_spec.get_field_type(namespace, name) def lookup_op(self, name: str) -> Op: @@ -164,18 +164,16 @@ def lookup_func(self, name: str) -> "Func": def lookup_var(self, name: str) -> Any: return self.get_scope().lookup_var(name) - def lookup_const(self, name: str) -> Tuple["AVMType", ConstValue]: + def lookup_const(self, name: str) -> Tuple["TealishType", ConstValue]: return self.get_scope().lookup_const(name) - def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: + def lookup_avm_constant(self, name: str) -> Tuple["TealishType", Any]: return lang_spec.lookup_avm_constant(name) def tealish_type(self) -> TealishType: if hasattr(self, "type"): return getattr(self, "type") - print(self.__class__) - # TODO: cop out - return TealishType.int + return TealishType.any # TODO: these attributes are only available on Node and other children types # we should either define them here or something else? diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index eeb20d8..5160673 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -137,13 +137,20 @@ def process(self) -> None: self.a.process() self.b.process() - if self.a.tealish_type() == TealishType.bigint: - self.t_type = TealishType.bigint + print(self.a.tealish_type()) + if self.a.tealish_type() == TealishType.bigint.value: self.op = "b" + self.op + self.type = TealishType.bigint self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) + self.type = type_lookup(op.returns) + if ( + self.a.tealish_type() == TealishType.bigint.value + and self.type == TealishType.bytes + ): + self.type = TealishType.bigint def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.a) @@ -164,8 +171,7 @@ def __init__( def process(self) -> None: self.expression.process() - self.type = self.expression.type - self.t_type = self.expression.tealish_type() + self.type = self.expression.tealish_type() def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.expression) diff --git a/tealish/langspec.py b/tealish/langspec.py index 4605b50..e32259a 100644 --- a/tealish/langspec.py +++ b/tealish/langspec.py @@ -3,25 +3,25 @@ import requests import tealish import json -from .tealish_builtins import constants, AVMType +from .tealish_builtins import constants, TealishType from typing import List, Dict, Any, Tuple, Optional abc = "ABCDEFGHIJK" _opcode_type_map = { - ".": AVMType.any, - "B": AVMType.bytes, - "U": AVMType.int, - "": AVMType.none, + ".": TealishType.any, + "B": TealishType.bytes, + "U": TealishType.int, + "": TealishType.none, } -def type_lookup(a: str) -> AVMType: +def type_lookup(a: str) -> TealishType: return _opcode_type_map[a] -def convert_args_to_types(args: str) -> List[AVMType]: +def convert_args_to_types(args: str) -> List[TealishType]: return [type_lookup(args[idx]) for idx in range(len(args))] @@ -35,11 +35,11 @@ class Op: #: list of arg types this op takes off the stack, encoded as a string args: str #: decoded list of incoming args - arg_types: List[AVMType] + arg_types: List[TealishType] #: list of arg types this op puts on the stack, encoded as a string returns: str #: decoded list of outgoing args - returns_types: List[AVMType] + returns_types: List[TealishType] #: how many bytes this opcode takes up when assembled size: int #: describes the args to be passed as immediate arguments to this op @@ -47,9 +47,9 @@ class Op: #: describes the list of names that can be used as immediate arguments arg_enum: List[str] #: describes the types returned when each arg enum is used - arg_enum_types: List[AVMType] + arg_enum_types: List[TealishType] #: dictionary mapping the names in arg_enum to types in arg_enum_types - arg_enum_dict: Dict[str, AVMType] + arg_enum_dict: Dict[str, TealishType] #: informational string about the op doc: str @@ -91,7 +91,7 @@ def __init__(self, op_def: Dict[str, Any]): if "ArgEnumTypes" in op_def: self.arg_enum_types = convert_args_to_types(op_def["ArgEnumTypes"]) else: - self.arg_enum_types = [AVMType.int] * len(self.arg_enum) + self.arg_enum_types = [TealishType.int] * len(self.arg_enum) self.arg_enum_dict = dict(zip(self.arg_enum, self.arg_enum_types)) else: self.arg_enum = [] @@ -126,8 +126,8 @@ def __init__(self, spec: Dict[str, Any]) -> None: "Txn": self.ops["txn"].arg_enum_dict, } - self.global_fields: Dict[str, AVMType] = self.fields["Global"] - self.txn_fields: Dict[str, AVMType] = self.fields["Txn"] + self.global_fields: Dict[str, TealishType] = self.fields["Global"] + self.txn_fields: Dict[str, TealishType] = self.fields["Txn"] def as_dict(self) -> Dict[str, Any]: return self.spec @@ -141,12 +141,12 @@ def lookup_op(self, name: str) -> Op: raise KeyError(f'Op "{name}" does not exist!') return self.ops[name] - def lookup_avm_constant(self, name: str) -> Tuple[AVMType, Any]: + def lookup_avm_constant(self, name: str) -> Tuple[TealishType, Any]: if name not in constants: raise KeyError(f'Constant "{name}" does not exist!') return constants[name] - def get_field_type(self, namespace: str, name: str) -> AVMType: + def get_field_type(self, namespace: str, name: str) -> TealishType: if "txn" in namespace: return self.txn_fields[name] elif namespace == "global": diff --git a/tealish/nodes.py b/tealish/nodes.py index bf135c0..3fa6720 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -199,8 +199,7 @@ def type(self) -> Optional[VarType]: class GenericExpression(Expression): - # TODO: never set? - type: str + type: TealishType @classmethod def parse(cls, line: str, parent: Node, compiler: "TealishCompiler") -> Node: @@ -376,8 +375,6 @@ def _tealish(self) -> str: class Const(LineStatement): type_pattern = "|".join([f"\\b{tt.value}\\b" for tt in TealishType]) - print(type_pattern) - pattern = ( rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" @@ -467,6 +464,8 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() + print(self.arg.type) + print(stack_type(self.arg.type)) if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " @@ -559,7 +558,7 @@ class Assignment(LineStatement): def process(self) -> None: self.expression.process() t = self.expression.type - incoming_types = t if type(t) == list else [t] + incoming_types: List[TealishType] = t if type(t) == list else [t] # type: ignore names = [Name(s.strip()) for s in self.names.split(",")] self.name_nodes = names @@ -1673,9 +1672,12 @@ def process(self) -> None: struct_field = struct.fields[self.field_name] self.offset = struct_field.offset self.size = struct_field.size - self.data_type = stack_type(struct_field.data_type) + self.data_type = struct_field.data_type self.expression.process() - if stack_type(self.expression.type) not in (self.data_type, AVMType.any): + if stack_type(self.expression.type) not in ( + stack_type(self.data_type), + AVMType.any, + ): raise CompileError( "Incorrect type for struct field assignment. " + f"Expected {self.data_type}, got {self.expression.type}", diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index d404107..fc68758 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -24,12 +24,15 @@ class ObjectType(str, Enum): class TealishType(str, Enum): - int = "int" + # Mirrors AVM Types + any = "any" bytes = "bytes" + int = "int" + none = "" + + # New types for tealish bigint = "bigint" addr = "addr" - any = "any" - none = "" def stack_type(tt: TealishType) -> AVMType: From c32db45067c6b02b05839b8551f3fbf7109378cc Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 15:55:01 -0500 Subject: [PATCH 05/16] Add notes --- tealish/expression_nodes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 5160673..2c4e969 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -137,14 +137,18 @@ def process(self) -> None: self.a.process() self.b.process() - print(self.a.tealish_type()) + # Make sure they're the same type we're trying to binop + assert self.a.tealish_type() == self.b.tealish_type() + if self.a.tealish_type() == TealishType.bigint.value: + # TODO: remap op based on TealishType + # for bigint, / => b/, * => b*, ... + # for string, + => concat + # lazy punt is prepend b for bigints self.op = "b" + self.op - self.type = TealishType.bigint self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) - self.type = type_lookup(op.returns) if ( self.a.tealish_type() == TealishType.bigint.value From 4809a57726472523314cc1844bd13f7592b1f876 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 Jan 2023 06:27:01 -0500 Subject: [PATCH 06/16] rm print, rm assert --- tealish/expression_nodes.py | 4 ++-- tealish/nodes.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 2c4e969..8383b99 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -137,8 +137,8 @@ def process(self) -> None: self.a.process() self.b.process() - # Make sure they're the same type we're trying to binop - assert self.a.tealish_type() == self.b.tealish_type() + # TODO: Make sure they're the same type we're trying to binop + # assert self.a.tealish_type() == self.b.tealish_type() if self.a.tealish_type() == TealishType.bigint.value: # TODO: remap op based on TealishType diff --git a/tealish/nodes.py b/tealish/nodes.py index 3fa6720..a91f60b 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -464,8 +464,7 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() - print(self.arg.type) - print(stack_type(self.arg.type)) + if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " From bfe257cdf8f87cf6099610ccfd416ac78a295a23 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 13:11:12 -0500 Subject: [PATCH 07/16] rename struct to scratch in ObjectType --- tealish/expression_nodes.py | 4 ++-- tealish/nodes.py | 4 ++-- tealish/tealish_builtins.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 42db892..b3cde2d 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -49,7 +49,7 @@ def process(self) -> None: raise CompileError(e.args[0], node=self) # is it a struct or box? if type(self.type) == tuple: - if self.type[0] == ObjectType.struct: + if self.type[0] == ObjectType.scratch: self.type = AVMType.bytes elif self.type[0] == ObjectType.box: raise CompileError("Invalid use of a Box reference", node=self) @@ -479,7 +479,7 @@ def process(self) -> None: self.type = struct_field.data_type def write_teal(self, writer: "TealWriter") -> None: - if self.object_type == ObjectType.struct: + if self.object_type == ObjectType.scratch: writer.write(self, f"load {self.slot} // {self.name}") if self.type == AVMType.int: writer.write(self, f"pushint {self.offset}") diff --git a/tealish/nodes.py b/tealish/nodes.py index ec2dfa0..801da79 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -1608,7 +1608,7 @@ class StructDeclaration(LineStatement): def process(self) -> None: self.name.slot = self.declare_var( - self.name.value, (ObjectType.struct, self.struct_name) + self.name.value, (ObjectType.scratch, self.struct_name) ) if self.expression: self.expression.process() @@ -1667,7 +1667,7 @@ def process(self) -> None: ) def write_teal(self, writer: "TealWriter") -> None: - if self.object_type == ObjectType.struct: + if self.object_type == ObjectType.scratch: writer.write(self, f"// {self.line} [slot {self.name.slot}]") writer.write(self, f"load {self.name.slot} // {self.name.value}") writer.write(self, self.expression) diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index cd6aafe..28e88e0 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -21,7 +21,7 @@ class ObjectType(str, Enum): `box` - the field is in a box, use box_extract to get the bytes """ - struct = "struct" + scratch = "scratch" box = "box" From ad315722b3a1d754481d6d70766594616093c2f5 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 14:25:00 -0500 Subject: [PATCH 08/16] adding handling of bigint --- examples/rich_types.tl | 9 +++++++++ tealish/base.py | 9 ++++++++- tealish/expression_nodes.py | 26 +++++++++++++++++++++----- tealish/nodes.py | 21 ++++++++++++++++++--- tealish/scope.py | 16 ++++++++++++---- tealish/tealish_builtins.py | 15 ++++++++++++--- 6 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 examples/rich_types.tl diff --git a/examples/rich_types.tl b/examples/rich_types.tl new file mode 100644 index 0000000..10cda13 --- /dev/null +++ b/examples/rich_types.tl @@ -0,0 +1,9 @@ +#pragma version 8 + +const int I = 123 +const bytes B = "abc" +const bigint Bi = 1231231231231231231231231231231231 +const bigint One = 1 +assert((Bi / Bi) == One) + +exit(1) \ No newline at end of file diff --git a/tealish/base.py b/tealish/base.py index a1e43ba..3e90cf4 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -1,6 +1,6 @@ from typing import cast, Any, Dict, List, Optional, Tuple, TYPE_CHECKING from tealish.errors import CompileError -from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType +from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType, TealishType from .langspec import get_active_langspec, Op from .scope import Scope @@ -160,6 +160,13 @@ def lookup_const(self, name: str) -> Tuple["AVMType", ConstValue]: def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: return lang_spec.lookup_avm_constant(name) + def tealish_type(self) -> TealishType: + if hasattr(self, "t_type"): + return getattr(self, "t_type") + print(self.__class__) + # TODO: cop out + return TealishType.int + # TODO: these attributes are only available on Node and other children types # we should either define them here or something else? @property diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index b3cde2d..7c605b4 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -2,7 +2,13 @@ from .base import BaseNode from .errors import CompileError -from .tealish_builtins import AVMType, get_struct, ObjectType +from .tealish_builtins import ( + AVMType, + get_struct, + ObjectType, + TealishType, + tealish_to_avm_type, +) from .langspec import Op, type_lookup @@ -64,8 +70,8 @@ def _tealish(self) -> str: class Constant(BaseNode): def __init__(self, name: str, parent: Optional[BaseNode] = None) -> None: self.name = name - self.type: AVMType = AVMType.none self.parent = parent + self.type: TealishType def process(self) -> None: type, value = None, None @@ -80,10 +86,11 @@ def process(self) -> None: raise CompileError( f'Constant "{self.name}" not declared in scope', node=self ) - if type not in (AVMType.int, AVMType.bytes): - raise CompileError(f"Unexpected const type {type}", node=self) + # if type not in (AVMType.int, AVMType.bytes): + # raise CompileError(f"Unexpected const type {type}", node=self) - self.type = type + self.t_type = type + self.type = tealish_to_avm_type(type) self.value = value def write_teal(self, writer: "TealWriter") -> None: @@ -130,6 +137,14 @@ def __init__( def process(self) -> None: self.a.process() self.b.process() + + print(self.a.tealish_type()) + print(self.b.tealish_type()) + print(self.op) + if self.a.tealish_type() == TealishType.bigint: + self.t_type = TealishType.bigint + self.op = "b" + self.op + self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) self.type = type_lookup(op.returns) @@ -154,6 +169,7 @@ def __init__( def process(self) -> None: self.expression.process() self.type = self.expression.type + self.t_type = self.expression.tealish_type() def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.expression) diff --git a/tealish/nodes.py b/tealish/nodes.py index 801da79..f717c4a 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -16,6 +16,7 @@ from .tx_expressions import parse_expression from .tealish_builtins import ( AVMType, + TealishType, ObjectType, define_struct, get_struct, @@ -373,23 +374,37 @@ def _tealish(self) -> str: class Const(LineStatement): + type_pattern = "|".join([f"\\b{tt.value}\\b" for tt in TealishType]) + print(type_pattern) + pattern = ( - r"const (?P\bint\b|\bbytes\b) " + rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" ) + t_type: TealishType type: AVMType name: str expression: Literal def process(self) -> None: scope = self.get_current_scope() - scope.declare_const(self.name, (self.type, self.expression.value)) + self.type = ( + AVMType.bytes if self.t_type != TealishType.int.value else AVMType.int + ) + + # TODO: have to compare against Enum.value ? + if self.t_type == TealishType.bigint.value: + # Hardcoded to uint256 or 32 bytes + new_value = cast(int, int(self.expression.value)).to_bytes(32, "big") + self.expression.value = f"0x{new_value.hex()}" + + scope.declare_const(self.name, (self.t_type, self.expression.value)) def write_teal(self, writer: "TealWriter") -> None: pass def _tealish(self) -> str: - s = f"const {self.type} {self.name}" + s = f"const {self.t_type} {self.name}" if self.expression: s += f" = {self.expression.tealish()}" return s + "\n" diff --git a/tealish/scope.py b/tealish/scope.py index 645593e..14a5842 100644 --- a/tealish/scope.py +++ b/tealish/scope.py @@ -1,8 +1,14 @@ -from typing import Dict, Optional, Tuple, TYPE_CHECKING +from typing import Dict, Optional, Tuple, TYPE_CHECKING, Union if TYPE_CHECKING: - from .tealish_builtins import AVMType, VarType, ConstValue, ScratchRecord + from .tealish_builtins import ( + AVMType, + VarType, + ConstValue, + ScratchRecord, + TealishType, + ) from .nodes import Func, Block @@ -59,11 +65,13 @@ def delete_var(self, name: str) -> None: del self.slots[name] def declare_const( - self, name: str, const_data: Tuple["AVMType", "ConstValue"] + self, + name: str, + const_data: Tuple["TealishType", "ConstValue"], ) -> None: self.consts[name] = const_data - def lookup_const(self, name: str) -> Tuple["AVMType", "ConstValue"]: + def lookup_const(self, name: str) -> Tuple["TealishType", "ConstValue"]: if name not in self.consts: raise KeyError(f'Const "{name}" not declared in current scope') return self.consts[name] diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index 28e88e0..595e282 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -12,12 +12,10 @@ class AVMType(str, Enum): none = "" -# TODO: add frame ptr or stack? rename to something like `storage type?` -# I think `struct` here should probably just be `scratch`? class ObjectType(str, Enum): """ObjectType determines where to get the bytes for a struct field. - `struct` - the field is in a byte array in a scratch var, use extract to get bytes + `scratch` - the field is in a byte array in a scratch var, use extract to get bytes `box` - the field is in a box, use box_extract to get the bytes """ @@ -25,6 +23,17 @@ class ObjectType(str, Enum): box = "box" +class TealishType(str, Enum): + int = "int" + bytes = "bytes" + bigint = "bigint" + addr = "addr" + + +def tealish_to_avm_type(tt: TealishType) -> AVMType: + return AVMType.bytes if tt != TealishType.int.value else AVMType.int + + # TODO: for CustomType and ScratchRecord we should consider # making them dataclasses or something instead of a tuple # to make it more obvious what the fields are From becca56e566cf3503ba8cb5be59d9828783ec6a9 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 15:07:50 -0500 Subject: [PATCH 09/16] mypy hates it but it works --- tealish/base.py | 22 +++++++++---- tealish/expression_nodes.py | 44 ++++++++++++-------------- tealish/nodes.py | 63 +++++++++++++++++++------------------ tealish/scope.py | 5 ++- tealish/tealish_builtins.py | 41 ++++++++++++++---------- 5 files changed, 95 insertions(+), 80 deletions(-) diff --git a/tealish/base.py b/tealish/base.py index 3e90cf4..1591815 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -1,6 +1,13 @@ from typing import cast, Any, Dict, List, Optional, Tuple, TYPE_CHECKING from tealish.errors import CompileError -from .tealish_builtins import AVMType, ConstValue, ScratchRecord, VarType, TealishType +from .tealish_builtins import ( + AVMType, + ConstValue, + ScratchRecord, + VarType, + TealishType, + stack_type, +) from .langspec import get_active_langspec, Op from .scope import Scope @@ -17,15 +24,18 @@ def check_arg_types(name: str, incoming_args: List["Node"]) -> None: expected_args = op.arg_types # TODO: for i, incoming_arg in enumerate(incoming_args): - if incoming_arg.type == AVMType.any: # type: ignore + tealish_type = incoming_arg.tealish_type() + avm_type = stack_type(tealish_type) + + if avm_type == AVMType.any: # type: ignore continue if expected_args[i] == AVMType.any: continue - if incoming_arg.type == expected_args[i]: # type: ignore + if avm_type == expected_args[i]: # type: ignore continue raise Exception( - f"Incorrect type {incoming_arg.type} " # type: ignore + f"Incorrect type {tealish_type} " # type: ignore + f"for arg {i} of {name}. Expected {expected_args[i]}" ) @@ -161,8 +171,8 @@ def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: return lang_spec.lookup_avm_constant(name) def tealish_type(self) -> TealishType: - if hasattr(self, "t_type"): - return getattr(self, "t_type") + if hasattr(self, "type"): + return getattr(self, "type") print(self.__class__) # TODO: cop out return TealishType.int diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 7c605b4..eeb20d8 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -7,7 +7,7 @@ get_struct, ObjectType, TealishType, - tealish_to_avm_type, + stack_type, ) from .langspec import Op, type_lookup @@ -20,7 +20,7 @@ class Integer(BaseNode): def __init__(self, value: str, parent: Optional[BaseNode] = None) -> None: self.value = int(value) - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -33,7 +33,7 @@ def _tealish(self) -> str: class Bytes(BaseNode): def __init__(self, value: str, parent: Optional[BaseNode] = None) -> None: self.value = value - self.type = AVMType.bytes + self.type = TealishType.bytes self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -56,7 +56,7 @@ def process(self) -> None: # is it a struct or box? if type(self.type) == tuple: if self.type[0] == ObjectType.scratch: - self.type = AVMType.bytes + self.type = TealishType.bytes elif self.type[0] == ObjectType.box: raise CompileError("Invalid use of a Box reference", node=self) @@ -89,14 +89,13 @@ def process(self) -> None: # if type not in (AVMType.int, AVMType.bytes): # raise CompileError(f"Unexpected const type {type}", node=self) - self.t_type = type - self.type = tealish_to_avm_type(type) + self.type = type self.value = value def write_teal(self, writer: "TealWriter") -> None: - if self.type == AVMType.int: + if stack_type(self.type) == AVMType.int: writer.write(self, f"pushint {self.value} // {self.name}") # type: ignore - elif self.type == AVMType.bytes: + elif stack_type(self.type) == AVMType.bytes: writer.write(self, f"pushbytes {self.value} // {self.name}") # type: ignore def _tealish(self) -> str: @@ -138,9 +137,6 @@ def process(self) -> None: self.a.process() self.b.process() - print(self.a.tealish_type()) - print(self.b.tealish_type()) - print(self.op) if self.a.tealish_type() == TealishType.bigint: self.t_type = TealishType.bigint self.op = "b" + self.op @@ -185,7 +181,7 @@ def __init__( self.name = name self.args = args self.parent = parent - self.type: Union[AVMType, List[AVMType]] = AVMType.none + self.type: Union[TealishType, List[TealishType]] = TealishType.none self.func_call_type: str = "" self.nodes = args self.immediate_args = "" @@ -251,7 +247,7 @@ def process_op_call(self, op: Op) -> None: def process_special_call(self) -> None: self.func_call_type = "special" if self.name == "pop": - self.type = AVMType.any + self.type = TealishType.any for arg in self.args: arg.process() @@ -291,7 +287,7 @@ def _tealish(self) -> str: class TxnField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -313,7 +309,7 @@ def __init__( ) -> None: self.field = field self.arrayIndex = arrayIndex - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -340,7 +336,7 @@ def __init__( ) -> None: self.field = field self.index = index - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -376,7 +372,7 @@ def __init__( self.field = field self.index = index self.arrayIndex = arrayIndex - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -419,7 +415,7 @@ def _tealish(self) -> str: class PositiveGroupIndex(BaseNode): def __init__(self, index: int, parent: Optional["BaseNode"] = None) -> None: self.index = index - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -434,7 +430,7 @@ def _tealish(self) -> str: class NegativeGroupIndex(BaseNode): def __init__(self, index: int, parent: Optional[BaseNode] = None) -> None: self.index = index - self.type = AVMType.int + self.type = TealishType.int self.parent = parent def write_teal(self, writer: "TealWriter") -> None: @@ -449,7 +445,7 @@ def _tealish(self) -> str: class GlobalField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -465,7 +461,7 @@ def _tealish(self) -> str: class InnerTxnField(BaseNode): def __init__(self, field: str, parent: Optional[BaseNode] = None) -> None: self.field = field - self.type = AVMType.any + self.type = TealishType.any self.parent = parent def process(self) -> None: @@ -482,7 +478,7 @@ class StructOrBoxField(BaseNode): def __init__(self, name, field, parent=None) -> None: self.name = name self.field = field - self.type = AVMType.none + self.type = TealishType.none self.parent = parent def process(self) -> None: @@ -497,7 +493,7 @@ def process(self) -> None: def write_teal(self, writer: "TealWriter") -> None: if self.object_type == ObjectType.scratch: writer.write(self, f"load {self.slot} // {self.name}") - if self.type == AVMType.int: + if self.type == TealishType.int: writer.write(self, f"pushint {self.offset}") writer.write(self, f"extract_uint64 // {self.field}") else: @@ -507,7 +503,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.offset} // offset") writer.write(self, f"pushint {self.size} // size") writer.write(self, f"box_extract // {self.name}.{self.field}") - if self.type == AVMType.int: + if self.type == TealishType.int: writer.write(self, "btoi") else: raise Exception() diff --git a/tealish/nodes.py b/tealish/nodes.py index f717c4a..bf135c0 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -23,6 +23,7 @@ VarType, Struct, StructField, + stack_type, ) from .scope import Scope @@ -159,8 +160,8 @@ class LiteralInt(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.value}") - def type(self) -> AVMType: - return AVMType.int + def type(self) -> TealishType: + return TealishType.int def _tealish(self) -> str: return f"{self.value}" @@ -173,8 +174,8 @@ class LiteralBytes(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushbytes {self.value}") - def type(self) -> AVMType: - return AVMType.bytes + def type(self) -> TealishType: + return TealishType.bytes def _tealish(self) -> str: return f"{self.value}" @@ -378,33 +379,29 @@ class Const(LineStatement): print(type_pattern) pattern = ( - rf"const (?P{type_pattern}) " + rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" ) - t_type: TealishType - type: AVMType + type: TealishType name: str expression: Literal def process(self) -> None: scope = self.get_current_scope() - self.type = ( - AVMType.bytes if self.t_type != TealishType.int.value else AVMType.int - ) # TODO: have to compare against Enum.value ? - if self.t_type == TealishType.bigint.value: + if self.type == TealishType.bigint.value: # Hardcoded to uint256 or 32 bytes new_value = cast(int, int(self.expression.value)).to_bytes(32, "big") self.expression.value = f"0x{new_value.hex()}" - scope.declare_const(self.name, (self.t_type, self.expression.value)) + scope.declare_const(self.name, (self.type, self.expression.value)) def write_teal(self, writer: "TealWriter") -> None: pass def _tealish(self) -> str: - s = f"const {self.t_type} {self.name}" + s = f"const {self.type} {self.name}" if self.expression: s += f" = {self.expression.tealish()}" return s + "\n" @@ -470,7 +467,7 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() - if self.arg.type not in (AVMType.int, AVMType.any): + if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " + f"Expected int, got {self.arg.type} at line {self.line_no}.", @@ -501,10 +498,10 @@ class BytesDeclaration(LineStatement): expression: GenericExpression def process(self) -> None: - self.name.slot = self.declare_var(self.name.value, AVMType.bytes) + self.name.slot = self.declare_var(self.name.value, TealishType.bytes) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.bytes, AVMType.any): raise CompileError( "Incorrect type for bytes assignment. " + f"Expected bytes, got {self.expression.type}", @@ -530,10 +527,10 @@ class IntDeclaration(LineStatement): expression: GenericExpression def process(self) -> None: - self.name.slot = self.declare_var(self.name.value, AVMType.int) + self.name.slot = self.declare_var(self.name.value, TealishType.int) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.int, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for int assignment. " + f"Expected int, got {self.expression.type}", @@ -584,7 +581,10 @@ def process(self) -> None: ) slot, var_type = var_def - if not (incoming_types[i] == AVMType.any or incoming_types[i] == var_type): + if not ( + stack_type(incoming_types[i]) == AVMType.any + or stack_type(incoming_types[i]) == var_type + ): raise CompileError( f"Incorrect type for {var_type} assignment. " + f"Expected {var_type}, got {incoming_types[i]}", @@ -1292,7 +1292,7 @@ def consume(cls, compiler: "TealishCompiler", parent: Node) -> "ForStatement": return node def process(self) -> None: - self.var_slot = self.declare_var(self.var, AVMType.int) + self.var_slot = self.declare_var(self.var, TealishType.int) for n in self.nodes: n.process() self.del_var(self.var) @@ -1388,7 +1388,7 @@ def _tealish(self) -> str: class ArgsList(Expression): arg_pattern = r"(?P[a-z][a-z_0-9]*): (?Pint|bytes)" pattern = rf"(?P({arg_pattern}(, )?)*)" - args: List[Tuple[str, AVMType]] + args: List[Tuple[str, TealishType]] def __init__(self, line: str) -> None: super().__init__(line) @@ -1406,7 +1406,7 @@ class Func(InlineStatement): args: ArgsList return_type: str - returns: List[AVMType] + returns: List[TealishType] def __init__( self, @@ -1421,7 +1421,8 @@ def __init__( self.new_scope("func__" + self.name) self.returns = list( filter( - None, [cast(AVMType, s.strip()) for s in self.return_type.split(",")] + None, + [cast(TealishType, s.strip()) for s in self.return_type.split(",")], ) ) self.slots: Dict[str, int] = {} @@ -1516,7 +1517,7 @@ class StructFieldDefinition(InlineStatement): + r"(?P[a-z][A-Z-a-z0-9_]+)(\[(?P\d+)\])?" ) field_name: str - data_type: AVMType + data_type: TealishType data_length: int offset: int @@ -1590,7 +1591,7 @@ def process(self) -> None: data_length=field_node.data_length, offset=offset, size=8 - if field_node.data_type == AVMType.int + if stack_type(field_node.data_type) == AVMType.int else int(field_node.data_length), ) fields[field_node.field_name] = sf @@ -1627,7 +1628,7 @@ def process(self) -> None: ) if self.expression: self.expression.process() - if self.expression.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.expression.type) not in (AVMType.bytes, AVMType.any): raise CompileError( "Incorrect type for struct assignment. " + f"Expected bytes, got {self.expression.type}", @@ -1672,9 +1673,9 @@ def process(self) -> None: struct_field = struct.fields[self.field_name] self.offset = struct_field.offset self.size = struct_field.size - self.data_type = struct_field.data_type + self.data_type = stack_type(struct_field.data_type) self.expression.process() - if self.expression.type not in (self.data_type, AVMType.any): + if stack_type(self.expression.type) not in (self.data_type, AVMType.any): raise CompileError( "Incorrect type for struct field assignment. " + f"Expected {self.data_type}, got {self.expression.type}", @@ -1686,7 +1687,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"// {self.line} [slot {self.name.slot}]") writer.write(self, f"load {self.name.slot} // {self.name.value}") writer.write(self, self.expression) - if self.data_type == AVMType.int: + if stack_type(self.data_type) == AVMType.int: writer.write(self, "itob") writer.write( self, f"replace {self.offset} // {self.name.value}.{self.field_name}" @@ -1697,7 +1698,7 @@ def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"load {self.name.slot} // box key {self.name.value}") writer.write(self, f"pushint {self.offset} // offset") writer.write(self, self.expression) - if self.data_type == AVMType.int: + if stack_type(self.data_type) == AVMType.int: writer.write(self, "itob") writer.write(self, f"box_replace // {self.name.value}.{self.field_name}") @@ -1732,7 +1733,7 @@ def process(self): self.name.value, (ObjectType.box, self.struct_name) ) self.key.process() - if self.key.type not in (AVMType.bytes, AVMType.any): + if stack_type(self.key.type) not in (AVMType.bytes, AVMType.any): raise CompileError( f"Incorrect type for box key. Expected bytes, got {self.key.type}", node=self, diff --git a/tealish/scope.py b/tealish/scope.py index 14a5842..b1218c6 100644 --- a/tealish/scope.py +++ b/tealish/scope.py @@ -1,9 +1,8 @@ -from typing import Dict, Optional, Tuple, TYPE_CHECKING, Union +from typing import Dict, Optional, Tuple, TYPE_CHECKING if TYPE_CHECKING: from .tealish_builtins import ( - AVMType, VarType, ConstValue, ScratchRecord, @@ -27,7 +26,7 @@ def __init__( slot_range if slot_range is not None else (0, 200) ) - self.consts: Dict[str, Tuple["AVMType", "ConstValue"]] = {} + self.consts: Dict[str, Tuple["TealishType", "ConstValue"]] = {} self.blocks: Dict[str, "Block"] = {} self.functions: Dict[str, "Func"] = {} diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index 595e282..d404107 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -28,10 +28,19 @@ class TealishType(str, Enum): bytes = "bytes" bigint = "bigint" addr = "addr" + any = "any" + none = "" -def tealish_to_avm_type(tt: TealishType) -> AVMType: - return AVMType.bytes if tt != TealishType.int.value else AVMType.int +def stack_type(tt: TealishType) -> AVMType: + if tt == TealishType.int.value: + return AVMType.int + elif tt == TealishType.none: + return AVMType.none + elif tt == TealishType.any: + return AVMType.any + else: + return AVMType.bytes # TODO: for CustomType and ScratchRecord we should consider @@ -45,7 +54,7 @@ def tealish_to_avm_type(tt: TealishType) -> AVMType: @dataclass class StructField: - data_type: AVMType + data_type: TealishType data_length: int offset: int size: int @@ -63,7 +72,7 @@ def __init__(self, fields: Dict[str, StructField], size: int): # either AVM native type or a CustomType (only struct atm) definition -VarType = Union[AVMType, CustomType] +VarType = Union[TealishType, CustomType] # a constant value introduced in source ConstValue = Union[str, bytes, int] @@ -83,16 +92,16 @@ def get_struct(struct_name: str) -> Struct: return _structs[struct_name] -constants: Dict[str, Tuple[AVMType, ConstValue]] = { - "NoOp": (AVMType.int, 0), - "OptIn": (AVMType.int, 1), - "CloseOut": (AVMType.int, 2), - "ClearState": (AVMType.int, 3), - "UpdateApplication": (AVMType.int, 4), - "DeleteApplication": (AVMType.int, 5), - "Pay": (AVMType.int, 1), - "Acfg": (AVMType.int, 3), - "Axfer": (AVMType.int, 4), - "Afrz": (AVMType.int, 5), - "Appl": (AVMType.int, 6), +constants: Dict[str, Tuple[TealishType, ConstValue]] = { + "NoOp": (TealishType.int, 0), + "OptIn": (TealishType.int, 1), + "CloseOut": (TealishType.int, 2), + "ClearState": (TealishType.int, 3), + "UpdateApplication": (TealishType.int, 4), + "DeleteApplication": (TealishType.int, 5), + "Pay": (TealishType.int, 1), + "Acfg": (TealishType.int, 3), + "Axfer": (TealishType.int, 4), + "Afrz": (TealishType.int, 5), + "Appl": (TealishType.int, 6), } From 4e94c047655a49b58b4d2a3c436b981115e0c607 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 15:38:04 -0500 Subject: [PATCH 10/16] twerkin --- tealish/base.py | 10 ++++------ tealish/expression_nodes.py | 14 ++++++++++---- tealish/langspec.py | 32 ++++++++++++++++---------------- tealish/nodes.py | 16 +++++++++------- tealish/tealish_builtins.py | 9 ++++++--- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/tealish/base.py b/tealish/base.py index 1591815..d1762dc 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -152,7 +152,7 @@ def check_arg_types(self, name: str, args: List["Node"]) -> None: except Exception as e: raise CompileError(str(e), node=self) # type: ignore - def get_field_type(self, namespace: str, name: str) -> AVMType: + def get_field_type(self, namespace: str, name: str) -> TealishType: return lang_spec.get_field_type(namespace, name) def lookup_op(self, name: str) -> Op: @@ -164,18 +164,16 @@ def lookup_func(self, name: str) -> "Func": def lookup_var(self, name: str) -> Any: return self.get_scope().lookup_var(name) - def lookup_const(self, name: str) -> Tuple["AVMType", ConstValue]: + def lookup_const(self, name: str) -> Tuple["TealishType", ConstValue]: return self.get_scope().lookup_const(name) - def lookup_avm_constant(self, name: str) -> Tuple["AVMType", Any]: + def lookup_avm_constant(self, name: str) -> Tuple["TealishType", Any]: return lang_spec.lookup_avm_constant(name) def tealish_type(self) -> TealishType: if hasattr(self, "type"): return getattr(self, "type") - print(self.__class__) - # TODO: cop out - return TealishType.int + return TealishType.any # TODO: these attributes are only available on Node and other children types # we should either define them here or something else? diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index eeb20d8..5160673 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -137,13 +137,20 @@ def process(self) -> None: self.a.process() self.b.process() - if self.a.tealish_type() == TealishType.bigint: - self.t_type = TealishType.bigint + print(self.a.tealish_type()) + if self.a.tealish_type() == TealishType.bigint.value: self.op = "b" + self.op + self.type = TealishType.bigint self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) + self.type = type_lookup(op.returns) + if ( + self.a.tealish_type() == TealishType.bigint.value + and self.type == TealishType.bytes + ): + self.type = TealishType.bigint def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.a) @@ -164,8 +171,7 @@ def __init__( def process(self) -> None: self.expression.process() - self.type = self.expression.type - self.t_type = self.expression.tealish_type() + self.type = self.expression.tealish_type() def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.expression) diff --git a/tealish/langspec.py b/tealish/langspec.py index 4605b50..e32259a 100644 --- a/tealish/langspec.py +++ b/tealish/langspec.py @@ -3,25 +3,25 @@ import requests import tealish import json -from .tealish_builtins import constants, AVMType +from .tealish_builtins import constants, TealishType from typing import List, Dict, Any, Tuple, Optional abc = "ABCDEFGHIJK" _opcode_type_map = { - ".": AVMType.any, - "B": AVMType.bytes, - "U": AVMType.int, - "": AVMType.none, + ".": TealishType.any, + "B": TealishType.bytes, + "U": TealishType.int, + "": TealishType.none, } -def type_lookup(a: str) -> AVMType: +def type_lookup(a: str) -> TealishType: return _opcode_type_map[a] -def convert_args_to_types(args: str) -> List[AVMType]: +def convert_args_to_types(args: str) -> List[TealishType]: return [type_lookup(args[idx]) for idx in range(len(args))] @@ -35,11 +35,11 @@ class Op: #: list of arg types this op takes off the stack, encoded as a string args: str #: decoded list of incoming args - arg_types: List[AVMType] + arg_types: List[TealishType] #: list of arg types this op puts on the stack, encoded as a string returns: str #: decoded list of outgoing args - returns_types: List[AVMType] + returns_types: List[TealishType] #: how many bytes this opcode takes up when assembled size: int #: describes the args to be passed as immediate arguments to this op @@ -47,9 +47,9 @@ class Op: #: describes the list of names that can be used as immediate arguments arg_enum: List[str] #: describes the types returned when each arg enum is used - arg_enum_types: List[AVMType] + arg_enum_types: List[TealishType] #: dictionary mapping the names in arg_enum to types in arg_enum_types - arg_enum_dict: Dict[str, AVMType] + arg_enum_dict: Dict[str, TealishType] #: informational string about the op doc: str @@ -91,7 +91,7 @@ def __init__(self, op_def: Dict[str, Any]): if "ArgEnumTypes" in op_def: self.arg_enum_types = convert_args_to_types(op_def["ArgEnumTypes"]) else: - self.arg_enum_types = [AVMType.int] * len(self.arg_enum) + self.arg_enum_types = [TealishType.int] * len(self.arg_enum) self.arg_enum_dict = dict(zip(self.arg_enum, self.arg_enum_types)) else: self.arg_enum = [] @@ -126,8 +126,8 @@ def __init__(self, spec: Dict[str, Any]) -> None: "Txn": self.ops["txn"].arg_enum_dict, } - self.global_fields: Dict[str, AVMType] = self.fields["Global"] - self.txn_fields: Dict[str, AVMType] = self.fields["Txn"] + self.global_fields: Dict[str, TealishType] = self.fields["Global"] + self.txn_fields: Dict[str, TealishType] = self.fields["Txn"] def as_dict(self) -> Dict[str, Any]: return self.spec @@ -141,12 +141,12 @@ def lookup_op(self, name: str) -> Op: raise KeyError(f'Op "{name}" does not exist!') return self.ops[name] - def lookup_avm_constant(self, name: str) -> Tuple[AVMType, Any]: + def lookup_avm_constant(self, name: str) -> Tuple[TealishType, Any]: if name not in constants: raise KeyError(f'Constant "{name}" does not exist!') return constants[name] - def get_field_type(self, namespace: str, name: str) -> AVMType: + def get_field_type(self, namespace: str, name: str) -> TealishType: if "txn" in namespace: return self.txn_fields[name] elif namespace == "global": diff --git a/tealish/nodes.py b/tealish/nodes.py index bf135c0..3fa6720 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -199,8 +199,7 @@ def type(self) -> Optional[VarType]: class GenericExpression(Expression): - # TODO: never set? - type: str + type: TealishType @classmethod def parse(cls, line: str, parent: Node, compiler: "TealishCompiler") -> Node: @@ -376,8 +375,6 @@ def _tealish(self) -> str: class Const(LineStatement): type_pattern = "|".join([f"\\b{tt.value}\\b" for tt in TealishType]) - print(type_pattern) - pattern = ( rf"const (?P{type_pattern}) " + r"(?P[A-Z][a-zA-Z0-9_]*) = (?P.*)$" @@ -467,6 +464,8 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() + print(self.arg.type) + print(stack_type(self.arg.type)) if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " @@ -559,7 +558,7 @@ class Assignment(LineStatement): def process(self) -> None: self.expression.process() t = self.expression.type - incoming_types = t if type(t) == list else [t] + incoming_types: List[TealishType] = t if type(t) == list else [t] # type: ignore names = [Name(s.strip()) for s in self.names.split(",")] self.name_nodes = names @@ -1673,9 +1672,12 @@ def process(self) -> None: struct_field = struct.fields[self.field_name] self.offset = struct_field.offset self.size = struct_field.size - self.data_type = stack_type(struct_field.data_type) + self.data_type = struct_field.data_type self.expression.process() - if stack_type(self.expression.type) not in (self.data_type, AVMType.any): + if stack_type(self.expression.type) not in ( + stack_type(self.data_type), + AVMType.any, + ): raise CompileError( "Incorrect type for struct field assignment. " + f"Expected {self.data_type}, got {self.expression.type}", diff --git a/tealish/tealish_builtins.py b/tealish/tealish_builtins.py index d404107..fc68758 100644 --- a/tealish/tealish_builtins.py +++ b/tealish/tealish_builtins.py @@ -24,12 +24,15 @@ class ObjectType(str, Enum): class TealishType(str, Enum): - int = "int" + # Mirrors AVM Types + any = "any" bytes = "bytes" + int = "int" + none = "" + + # New types for tealish bigint = "bigint" addr = "addr" - any = "any" - none = "" def stack_type(tt: TealishType) -> AVMType: From b59984c298073da357635944882e78b0aef26777 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Mon, 30 Jan 2023 15:55:01 -0500 Subject: [PATCH 11/16] Add notes --- tealish/expression_nodes.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 5160673..2c4e969 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -137,14 +137,18 @@ def process(self) -> None: self.a.process() self.b.process() - print(self.a.tealish_type()) + # Make sure they're the same type we're trying to binop + assert self.a.tealish_type() == self.b.tealish_type() + if self.a.tealish_type() == TealishType.bigint.value: + # TODO: remap op based on TealishType + # for bigint, / => b/, * => b*, ... + # for string, + => concat + # lazy punt is prepend b for bigints self.op = "b" + self.op - self.type = TealishType.bigint self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) - self.type = type_lookup(op.returns) if ( self.a.tealish_type() == TealishType.bigint.value From 4ec260ec16597e16766020e37caf900b8f1222c3 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 Jan 2023 06:27:01 -0500 Subject: [PATCH 12/16] rm print, rm assert --- tealish/expression_nodes.py | 4 ++-- tealish/nodes.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 2c4e969..8383b99 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -137,8 +137,8 @@ def process(self) -> None: self.a.process() self.b.process() - # Make sure they're the same type we're trying to binop - assert self.a.tealish_type() == self.b.tealish_type() + # TODO: Make sure they're the same type we're trying to binop + # assert self.a.tealish_type() == self.b.tealish_type() if self.a.tealish_type() == TealishType.bigint.value: # TODO: remap op based on TealishType diff --git a/tealish/nodes.py b/tealish/nodes.py index 3fa6720..a91f60b 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -464,8 +464,7 @@ class Assert(LineStatement): def process(self) -> None: self.arg.process() - print(self.arg.type) - print(stack_type(self.arg.type)) + if stack_type(self.arg.type) not in (AVMType.int, AVMType.any): raise CompileError( "Incorrect type for assert. " From 9682d185b243402643b7c0b2a2e35cc954183401 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 Jan 2023 14:27:25 -0500 Subject: [PATCH 13/16] remove op mapping and type replacement --- examples/rich_types.tl | 2 +- tealish/expression_nodes.py | 16 ---------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/examples/rich_types.tl b/examples/rich_types.tl index 10cda13..24f4940 100644 --- a/examples/rich_types.tl +++ b/examples/rich_types.tl @@ -4,6 +4,6 @@ const int I = 123 const bytes B = "abc" const bigint Bi = 1231231231231231231231231231231231 const bigint One = 1 -assert((Bi / Bi) == One) +assert((Bi b/ Bi) b== One) exit(1) \ No newline at end of file diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index 8383b99..aa035ff 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -136,25 +136,9 @@ def __init__( def process(self) -> None: self.a.process() self.b.process() - - # TODO: Make sure they're the same type we're trying to binop - # assert self.a.tealish_type() == self.b.tealish_type() - - if self.a.tealish_type() == TealishType.bigint.value: - # TODO: remap op based on TealishType - # for bigint, / => b/, * => b*, ... - # for string, + => concat - # lazy punt is prepend b for bigints - self.op = "b" + self.op - self.check_arg_types(self.op, [self.a, self.b]) op = self.lookup_op(self.op) self.type = type_lookup(op.returns) - if ( - self.a.tealish_type() == TealishType.bigint.value - and self.type == TealishType.bytes - ): - self.type = TealishType.bigint def write_teal(self, writer: "TealWriter") -> None: writer.write(self, self.a) From 0d00e3feeeca19a48910ca887b4ca9e4fddf91fa Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 Jan 2023 14:28:47 -0500 Subject: [PATCH 14/16] add back check --- tealish/expression_nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tealish/expression_nodes.py b/tealish/expression_nodes.py index aa035ff..2c060e0 100644 --- a/tealish/expression_nodes.py +++ b/tealish/expression_nodes.py @@ -86,8 +86,8 @@ def process(self) -> None: raise CompileError( f'Constant "{self.name}" not declared in scope', node=self ) - # if type not in (AVMType.int, AVMType.bytes): - # raise CompileError(f"Unexpected const type {type}", node=self) + if stack_type(type) not in (AVMType.int, AVMType.bytes): + raise CompileError(f"Unexpected const type {type}", node=self) self.type = type self.value = value From 134bf884332fafd4e7a1f54c8be2014f1467e6a4 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 Jan 2023 14:34:57 -0500 Subject: [PATCH 15/16] wiggle tealish_type method --- tealish/base.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tealish/base.py b/tealish/base.py index d1762dc..029db55 100644 --- a/tealish/base.py +++ b/tealish/base.py @@ -22,16 +22,15 @@ def check_arg_types(name: str, incoming_args: List["Node"]) -> None: op = lang_spec.lookup_op(name) expected_args = op.arg_types - # TODO: for i, incoming_arg in enumerate(incoming_args): tealish_type = incoming_arg.tealish_type() avm_type = stack_type(tealish_type) - if avm_type == AVMType.any: # type: ignore + if avm_type == AVMType.any: continue if expected_args[i] == AVMType.any: continue - if avm_type == expected_args[i]: # type: ignore + if tealish_type == expected_args[i]: continue raise Exception( @@ -172,8 +171,11 @@ def lookup_avm_constant(self, name: str) -> Tuple["TealishType", Any]: def tealish_type(self) -> TealishType: if hasattr(self, "type"): - return getattr(self, "type") - return TealishType.any + return cast(TealishType, getattr(self, "type")) + return TealishType.none + + def stack_type(self) -> AVMType: + return stack_type(self.tealish_type()) # TODO: these attributes are only available on Node and other children types # we should either define them here or something else? From d1bc9908a4d37866ba2bc45c8db49899493bf737 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Tue, 31 Jan 2023 14:38:12 -0500 Subject: [PATCH 16/16] remove unused methods --- tealish/nodes.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tealish/nodes.py b/tealish/nodes.py index a91f60b..32194fd 100644 --- a/tealish/nodes.py +++ b/tealish/nodes.py @@ -160,9 +160,6 @@ class LiteralInt(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushint {self.value}") - def type(self) -> TealishType: - return TealishType.int - def _tealish(self) -> str: return f"{self.value}" @@ -174,9 +171,6 @@ class LiteralBytes(Literal): def write_teal(self, writer: "TealWriter") -> None: writer.write(self, f"pushbytes {self.value}") - def type(self) -> TealishType: - return TealishType.bytes - def _tealish(self) -> str: return f"{self.value}" @@ -193,9 +187,6 @@ def __init__(self, line: str) -> None: def _tealish(self) -> str: return f"{self.value}" - def type(self) -> Optional[VarType]: - return self._type - class GenericExpression(Expression):