Skip to content
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

12b Procedure calls #22

Merged
merged 23 commits into from
Apr 21, 2022
Merged

12b Procedure calls #22

merged 23 commits into from
Apr 21, 2022

Conversation

ngjunsiang
Copy link
Contributor

@ngjunsiang ngjunsiang commented Apr 20, 2022

Now we figure out how to call procedures and execute those stored statements.

First, let's rectify an unfortunate choice of term: when defining procedures, we declare parameters not arguments; we will later call the procedure and pass arguments.

[c47a42a]

@ngjunsiang ngjunsiang marked this pull request as draft April 20, 2022 12:51
@ngjunsiang
Copy link
Contributor Author

Parsing CALL statements

CALLing a procedure is easy, since we don't have to deal with remembering types:

https://github.com/nyjc-computing/pseudo/blob/ff0fb8f3009bd7398f1f9de29e69cb83c235dcfb/parser.py#L357-L358

https://github.com/nyjc-computing/pseudo/blob/ff0fb8f3009bd7398f1f9de29e69cb83c235dcfb/parser.py#L320-L336

Notice that we store args in a list instead of a dict, since they no longer have names unlike params. We will do the matching later when the CALL statement gets interpreted.

Bugfix

We should have used value() instead of identifier() to parse the name of the procedure, which would get us a get expr instead of a 'name' token. Remember that the procedure is stored in the frame and needs to be retrieved from it.

[783e918]

@ngjunsiang
Copy link
Contributor Author

Resolving CALL statements

Now let's resolve the call:
https://github.com/nyjc-computing/pseudo/blob/21a0ad29e0fc640a914bcc3babc3f4dab62fd719/resolver.py#L114-L115

A short todo-list for us to keep track of what needs to be checked. This is where the work of implementing procedures is:
https://github.com/nyjc-computing/pseudo/blob/be5dc8ab55ba93a651a0f8f507b366164bb192c3/resolver.py#L90-L94

@ngjunsiang
Copy link
Contributor Author

First, we check that the CALL has actually called a valid procedure:
https://github.com/nyjc-computing/pseudo/blob/76bd6d59121fa7a7e0b36fc49be8b59b3debb14d/resolver.py#L90-L94

We do a preliminary check for number of arguments, so we don't run into iteration problems in the next part:
https://github.com/nyjc-computing/pseudo/blob/76bd6d59121fa7a7e0b36fc49be8b59b3debb14d/resolver.py#L95-L98

Finally, we match up the args with the defined params:

@ngjunsiang
Copy link
Contributor Author

Bug

Code:

DECLARE Person : STRING
PROCEDURE SayHi(Person : STRING)
    OUTPUT "Hi, ", Person, "!"
ENDPROCEDURE
CALL SayHi("John")
  File "resolver.py", line 92, in verifyCall
    proc = frame[stmt['name']]
TypeError: unhashable type: 'dict'

Hmm ... that's because stmt['name'] is a get expression, from our fix in [783e918]. We haven't actually defined our procedure yet, i.e. it does not exist in the frame!

@ngjunsiang
Copy link
Contributor Author

Refactoring

To do static type-checking in the resolver (instead of the interpreter), we will have to declare variables and procedures in the resolver:
https://github.com/nyjc-computing/pseudo/blob/ef8facfc8f84a7ab9f4f92384e910720e76a2ada/resolver.py#L48-L51

https://github.com/nyjc-computing/pseudo/blob/ef8facfc8f84a7ab9f4f92384e910720e76a2ada/resolver.py#L86-L94

And we take this functionality out from the interpreter: [b52360e]

@ngjunsiang
Copy link
Contributor Author

Bugfix

stmt['name'] is a get expression, but we cannot resolve() it to get the name since that will get us the type instead. We'll have to manually resolve() the 'right' of the get expression instead:
[7350401]

And remember to resolve() the defined param to get its type:
[1bd9e7e]

@ngjunsiang
Copy link
Contributor Author

Pretty-printing

Our frame returns are getting pretty difficult to read. Let's make it easier: [9ff9076]

Result:

{ 'Person': {'type': 'STRING', 'value': None},
  'SayHi': { 'params': { 'Person': { 'type': { 'type': 'name',
                                               'value': None,
                                               'word': 'STRING'},
                                     'value': None}},
             'stmts': [ { 'exprs': [ { 'type': 'string',
                                       'value': 'Hi, ',
                                       'word': '"Hi, "'},
                                     { 'left': <Recursion on dict with id=139945527931968>,
                                       'oper': { 'type': 'symbol',
                                                 'value': <function get at 0x7f479b7a30d0>,
                                                 'word': ''},
                                       'right': { 'type': 'name',
                                                  'value': None,
                                                  'word': 'Person'}},
                                     { 'type': 'string',
                                       'value': '!',
                                       'word': '"!"'}],
                          'rule': 'output'}],
             'type': 'procedure'}}

@ngjunsiang
Copy link
Contributor Author

Now we can see: our param type is a token while expr type is a string! Let's make that more consistent: [b58c10d]

Then we can undo [1bd9e7e].

@ngjunsiang
Copy link
Contributor Author

Interpreting CALL statements

Let's implement the interpreter execute() for CALL statements:
https://github.com/nyjc-computing/pseudo/blob/0524a61a2defd89cd205cbc24d9375db86d09fb9/interpreter.py#L98-L99

https://github.com/nyjc-computing/pseudo/blob/0524a61a2defd89cd205cbc24d9375db86d09fb9/interpreter.py#L69-L77

Before this can work, we have to add some missing code to do frame insertion in resolver.py for CALL statements:
[85f3941]

Quite similar to conditionals and loops, except we have to assign the args to variable names in the frame before we call the statements.

@ngjunsiang
Copy link
Contributor Author

Bug

Immediately we run into a bug:

Result:

  File "builtin.py", line 44, in get
    return frame[name]['value']
KeyError: 'value'

Remember that frame is a dict, with the variable name as the key and another dict, {'type': ..., 'value': ...} as its value?

Well, when we assigned the procedure into the frame, we flouted this rule:
https://github.com/nyjc-computing/pseudo/blob/0524a61a2defd89cd205cbc24d9375db86d09fb9/resolver.py#L90-L94

@ngjunsiang
Copy link
Contributor Author

Bugfix

So we follow that format, putting 'params' and 'stmts' inside 'value':
[196b1de]

And update the places where this affects our code:
[7772433]

Result:

Hi, John!

@ngjunsiang
Copy link
Contributor Author

More refactoring

In the process I ran into another annoying bug: [4c327b3]

evaluate() does not follow the same argument order as resolve(), execute(), verify(), etc. It is the only one that takes an exprfirst, followed byframe`. Time to make it play nice: [1b91827]

And we are done with procedures ... for now.

Next steps: to support BYREF and BYVALUE keywords, we need to allow procedures to run with their own frame instead of the global frame, so that we can pass some arguments by value (in a local frame).

@ngjunsiang ngjunsiang marked this pull request as ready for review April 21, 2022 09:37
@ngjunsiang ngjunsiang merged commit e837c5c into main Apr 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant