Intro
In this series I am creating a transpiler from Python to Golang called Pogo. In the last post we crudely implemented functions, which are very good at throwing errors. Since we are getting so many errors now it's a great time to fix them up and make them somewhat readable.
Where are we holding the line number?
The easiest way to hold the line number would be in the Token
s and Structure
s, since these are carried through all the stages of the compiler. We can just add a property called line
as an int
and give this number to the Token
. But we need to get this number from somewhere, and for the Structure
s this isn't so much of an issue, just get it from the Token
that is equivalent. This is done using p.curToken.line
. But where does the Token
get it from?
Getting the Line Number
Well the best place for this is the lexer. On the lexer we can also have a value keeping track of the current line, and every time we find a newline, we increment that value.
Testing
Here's a simple test case.
from GoType import *
1234.
This should throw an error from the lexer, since we have to end a number with a digit. And we got our error, here it is.
[Lex (lex)] Numbers must end with a digit on line 2
That's pretty good! The line number is very helpful here for the user, but what about the compiler designer? We definitely need to work on how useful those errors are.
Errors for the Compiler Designer
What would make errors better for us? Well it would be nice to know which function the error was created. This isn't that helpful though, since a lot of these errors would end up coming from checkToken
. Instead, what we should ask is what function line this error came from, so we would get an error such as this.
parse.go -> parse -> program -> statement:ST_IMPORT
Expected ASTERISK, got R_PAREN on line 5
That's a pretty good error, especially for such a simple transpiler like ours.
Keeping Track of the Function Line
To keep track of the function line we are going to have a slice of strings attached to the parser (we are only going to implement this in the parser, because every other file is way too small for this to matter, with the emitter being a single function).
Now, whenever we enter a function we need to add what function we are in to the slice of function names.
p.funcLine = append(p.funcLine, "program")
Now we have to do that for every function.
Deleting Functions from the Function Line
Now we only need to delete the last item in the slice when we want to do this, so we use this.
p.funcLine = p.funcLine[:len(p.funcLine)-1]
This will get rid of the last item. We just need to start placing these around our code. We want to put these at the end of every function, but most functions end at multiple places. However, we don't want to place this before we return an error, because then we erase the tracks we just made for this purpose. Instead, we want to place these when we don't return an error, when we return nil
. This (usually) means we only place it once for every function.
Using What We Made
We've done all this work implementing the function line, but we haven't used it yet, we need to actually show these in the errors. To help simplify everything, we'll also make a function to help create errors.
func createError(funcLine []string, message string, line int) error {
output := ""
for i := 0; i < len(funcLine); i++ {
output += funcLine[i]
output += " -> "
}
output += "line: " + strconv.Itoa(line) + "\n"
output += message
return errors.New(output)
}
This takes the work of creating errors out of the parser.
Checking the Error
Here is our input code.
from GoType import *
for 2 in range(2, 10):
print(2)
Our error here is that we used the number 2 instead of an identifier in line 2, right after for
. Now we can run our compiler and see what error it gives us.
parse.go -> parse -> program -> statement -> checkTokenRange -> checkToken -> line: 2
Expected IDENTIFIER got 2
Which is exactly what we want to see.
Next
I know I put it off for a bit but I should really work on that semantic analyzer and fix functions, we should be able to use functions just as we normally would without getting into errors.
Top comments (0)