-
Notifications
You must be signed in to change notification settings - Fork 131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add subroutine support #99
Conversation
def __str__(self): | ||
return "slot#{}".format(self.id) | ||
|
||
def __eq__(self, other): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to remove this so that ScratchSlot(1) != ScratchSlot(1)
, which is required in order to detect duplicate requested slot IDs when I add all slots to a set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds fine to me -- I think I previously checked for the id of the object e.g. id(slot1) == id(slot2)
for detecting duplicates, but with this change it seems like we can remove this check
@@ -0,0 +1,20 @@ | |||
class LabelReference: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is basically a pass-by-reference string. It was added so that flattenSubroutines
can easily prefix all the labels in a given subroutine in order to differentiate them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looking good, I really like the recursive test coverage as well. Just minor comments/questions.
# from the main routine, and "subX_" (the subroutine label) to the | ||
# ones from each subroutine | ||
|
||
mainRoutine = subroutineMapping[None] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an interesting trick, using None
as a key for a dict
.
def __str__(self): | ||
return "slot#{}".format(self.id) | ||
|
||
def __eq__(self, other): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds fine to me -- I think I previously checked for the id of the object e.g. id(slot1) == id(slot2)
for detecting duplicates, but with this change it seems like we can remove this check
] = dict() | ||
subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]] = dict() | ||
subroutineBlocks: Dict[Optional[SubroutineDefinition], TealBlock] = dict() | ||
compileSubroutine( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe this should be named compile() since it's not a compiler specific to subroutines only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have mostly been treating the main routine as a special subroutine which gets invoked first (and doesn't have a SubroutineDefinition
object). So I think compileSubroutine
is an ok name. Would compileRoutine
be better?
The only reason I want to avoid compile()
is because that's already a builtin Python function: https://docs.python.org/3/library/functions.html#compile
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in that case, compileSubroutine
is better.
Jason, can you please elaborate on what has_return() do? It's not always clear to me why it's True/False in some cases. For example, in If(), why is it False when there's no else branch? |
Yes, I have updated the docstring to be more specific, and added comments to each complex class which overrides it. The docstring is now says "Check if this expression always returns from the current subroutine or program." That check is needed in order to insert pyteal/pyteal/compiler/compiler.py Line 124 in d28ada2
I thought this was a better solution than requiring every subroutine and main routine to end with a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
self.continueBlocksStack[-1].append(block) | ||
|
||
def exitLoop(self) -> Tuple[List[TealSimpleBlock], List[TealSimpleBlock]]: | ||
return (self.breakBlocksStack.pop(), self.continueBlocksStack.pop()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can remove redundant parentheses, i.e., return self.breakBlocksStack.pop(), self.continueBlocksStack.pop()
should work.
if self.value is not None: | ||
args.append(self.value) | ||
|
||
return TealBlock.FromOp(options, TealOp(self, op), *args) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: can be done as a one-liner:
return TealBlock.FromOp(options, TealOp(self, op),
*([] if self.value is None else [self.value])
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, but I think I'll stick with the current code since it's a little easier to see what's happening
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes look good to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This PR implements subroutines in PyTeal. Subroutines may be declared as functions with the
Subroutine
function decorator, like so:Subroutine functions may accept any number of arguments, but the arguments must not be keyword-only Python arguments, must not have default values, and must not be variable-length (e.g.
*args
). The argument in theSubroutine
decorator function is the return type of the subroutine, so in this example the subroutine must return an expression that evaluates toTealType.uint64
. Subroutine functions are executed only once during the compilation process in order to extract their AST.Since there can now be multiple "functions" in a program, I changed how the
Return
expression works. Now,Return
means return from the current subroutine (retsub
op), or if the main routine is executing, end the program with thereturn
op. However, if you'd like to exit the program from within a subroutine, I've introducedApprove()
andReject()
expressions which always evaluate to the opsint 1; return
andint 0; return
, respectively.In the compiler, each subroutine is compiled independently, then they are all combined into a single series of
TealComponent
s with theflattenSubroutines
function. I had to make changes to the way scratch slots and labels are resolved in order to make this work.Closes #71