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

08 Static typing #15

Merged
merged 13 commits into from
Apr 17, 2022
Merged

08 Static typing #15

merged 13 commits into from
Apr 17, 2022

Conversation

ngjunsiang
Copy link
Contributor

Static typing

9608 pseudocode is a statically typed language. Notice that when declaring variables, we declare their type as well. And the pseudocode operators and functions expect specific types in most cases:

  • the arithmetic operators expect only numeric types
  • the comparison operators expect numeric types and always return a boolean
  • (somewhat annoyingly) you cannot assign a value to a variable if the value type is different from the variable's declared type.

Static typing implies that the value and variable types should be known at compile time. This means that before we step into interpret(), the interpreter should already know what it is dealing with, and be reasonably sure it can execute statements safely in most cases.

In other words, we shouldn't be seeing much type-checking code like

    if frametype == 'INTEGER' and valuetype != int:
        raise LogicError(f'Expected {frametype}, got {valuetype}')
    elif frametype == 'REAL' and valuetype != float:
        raise LogicError(f'Expected {frametype}, got {valuetype}')
    elif frametype == 'STRING' and valuetype != str:
        raise LogicError(f'Expected {frametype}, got {valuetype}')

It is telling that this is a LogicError: it could have been checked before we attempted interpret()ing.

It sure seems like this is a job our resolver should take on while it is inserting frames into gets; it feels right, too, that as it inserts frames it also checks types.

@ngjunsiang
Copy link
Contributor Author

Testing

Result:

  File "interpreter.py", line 39, in execAssign
    raise LogicError(f'Expected {frametype}, got {valuetype}')
builtin.LogicError: Expected INTEGER, got <class 'str'>

The debug message doesn't look too pretty, but we are getting the expected LogicError. Let's try to keep it this way as we shift type-checking from the interpreter to the resolver.

The resolver does not actually interpret/execute any code (besides DECLARE statements), so it only raises LogicError. The interpreter does, and it only raises RuntimeError. Let's try to keep those imports separate in the respective modules.

@ngjunsiang
Copy link
Contributor Author

ngjunsiang commented Apr 16, 2022

Name declaration

To carry out type checking, the resolver will have to handle name declarations:
https://github.com/nyjc-computing/pseudo/blob/c5c621bc25527da463465054d84d1302b2687fc6/resolver.py#L21-L24

And that means resolve() will have to handle name tokens:
https://github.com/nyjc-computing/pseudo/blob/c5c621bc25527da463465054d84d1302b2687fc6/resolver.py#L7-L11

There is definitely overlap with the interpreter here. I suspect this responsibility will eventually be transferred from interpreter to resolver.

@ngjunsiang
Copy link
Contributor Author

Resolving expressions

Our resolver does not need to interpret and execute statements. Besides resolving frames, we also want it to do some type checking. For a statically typed language like 9608 pseudocode, we should technically know the data type that each expression resolve()s to. So we should aim to make resolve() return a data type.

In the meantime, let's have verifyAssign() extract the name and value type from the statement:
https://github.com/nyjc-computing/pseudo/blob/3a214219d2223dca48d57be74682acdba0d622d2/resolver.py#L26-L28

@ngjunsiang
Copy link
Contributor Author

Name and type checking

https://github.com/nyjc-computing/pseudo/blob/26713911a341ded30c666a3abf92925510cd8f05/resolver.py#L32-L36
We insert checks to ensure that the name has been declared, and the value type matches the declared type.

https://github.com/nyjc-computing/pseudo/blob/26713911a341ded30c666a3abf92925510cd8f05/resolver.py#L7-L12
We also change the return value for resolve(): instead of returning the token value, it now returns the token type. And we put in a temporary check for tokens whose type we can't yet handle.

@ngjunsiang
Copy link
Contributor Author

Resolving expressions

https://github.com/nyjc-computing/pseudo/blob/3739cf576e6093e46bde38b947260e6ccfbfb128/resolver.py#L19-L32
Here we implement the code in resolve() that returns appropriate types for each expression. For now we handle arithmetic operators and comparators. We can simplify further when we apply OOP.

@ngjunsiang
Copy link
Contributor Author

Testing

Took out the try-except block that prevents LogicErrors from resolver reaching main.

[739469c]

Result:

Expected INTEGER, got STRING

@ngjunsiang
Copy link
Contributor Author

Further testing

After bugfixes [4bdc0d2] and [f85068c]

Test:

DECLARE Result : STRING
Result <- 1 + 5
OUTPUT Result

Result:

Expected STRING, got INTEGER

Test:

DECLARE Index : STRING
Result <- 1 + 5
OUTPUT Result

Result:

Result: Name not declared

@ngjunsiang
Copy link
Contributor Author

Refactoring

Now we can take out type-checking from the interpreter:
[fa351cb]

We have name-checking in a few places, but it is ultimately carried out in resolve(). We might as well do it only once, in there:
[ee1c9cf]

@ngjunsiang
Copy link
Contributor Author

Bug

Index: Name not declared

Now we can't declare names: they get caught by our name check. We should carry out the name check only for get operator:
https://github.com/nyjc-computing/pseudo/blob/407b1f0e557bb6ec1e290111c610cff826dc3f29/resolver.py#L19-L24

@ngjunsiang
Copy link
Contributor Author

We now have the infrastructure in place for checking names before they are retrieved from the frame, and checking value types before they are assigned to names. All this, done before the interpreter carries out any interpreting.

@ngjunsiang ngjunsiang merged commit 8b77651 into main Apr 17, 2022
@ngjunsiang ngjunsiang deleted the resolver branch April 17, 2022 07:19
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