Intro
In this series I am creating a transpiler from Python to Golang called Pogo. In the last post we did some work to make sure that incorrect code didn't get translated into Go through the use of semantic analysis. This time we will be implementing functions.
Changing the Parser
I'm going to skip over the lexer since it's just adding the keyword def
and that's about it. But with the parser we have a major change, which will be the property called functions
, which will hold all the functions (other than main
) in the program.
else if p.curToken.code == tokenCode["K_DEF"] {
s = Structure{[]Structure{}, structureCode["ST_FUNCTION"], "ST_FUNCTION"}
s.children = append(s.children, Structure{[]Structure{}, structureCode["K_DEF"], "def"})
p.nextToken()
temp, err := p.checkToken("IDENTIFIER")
if err != nil {
return s, err
}
temp.code = structureCode["FUNC_NAME"]
s.children = append(s.children, temp)
p.nextToken()
temp, err = p.checkToken("L_PAREN")
if err != nil {
return s, err
}
s.children = append(s.children, temp)
p.nextToken()
for p.curToken.code == tokenCode["IDENTIFIER"] {
temps, err := p.checkTokenRange([]string{
"IDENTIFIER",
"COLON",
"IDENTIFIER",
})
if err != nil {
return s, err
}
s.children = append(s.children, temps...)
temp, err = p.checkToken("SEP")
if err != nil {
break
}
s.children = append(s.children, temp)
p.nextToken()
}
temp, err = p.checkToken("R_PAREN")
if err != nil {
return s, err
}
s.children = append(s.children, temp)
p.nextToken()
temps, err := p.checkTokenRange([]string{
"COLON",
"NEWLINE",
})
if err != nil {
return s, err
}
s.children = append(s.children, temps[0])
temp, err = p.block()
if err != nil {
return s, err
}
s.children = append(s.children, temp)
p.functions = append(p.functions, s)
return Structure{[]Structure{}, structureCode["NEWLINE"], "\n"}, nil
}
Most of this would look pretty standard except for the ending. Instead of adding this Structure
to the program, we add it to functions
and replace it with a newline (placeholder token). Now that we should have a bunch of functions in functions
we need to apply them correctly. We also need to make the main function smarter, and make it into a function logically instead of just being text manipultion.
Creating the Main Function
main_func := Structure{
[]Structure{
{[]Structure{}, structureCode["K_DEF"], "def"},
{[]Structure{}, structureCode["FUNC_NAME"], "main"},
{[]Structure{}, structureCode["L_PAREN"], "("},
{[]Structure{}, structureCode["R_PAREN"], ")"},
{[]Structure{}, structureCode["COLON"], ":"},
{append(ast.children, Structure{[]Structure{}, structureCode["ANTI_COLON"], ":"}), structureCode["BLOCK"], ""},
},
structureCode["ST_FUNCTION"],
"ST_FUNCTION",
}
ast.children = []Structure{main_func}
ast.children = append(ast.children, parser.functions...)
My least favorite design decision of the Structure
is having to explicitly make a slice of Structure
s every time I'm making one. At some point I'll probably cave in and make a function to make this easier, unless I find a better way.
The last line is all we need to add all the functions we found into the program.
Running our Compiler
Now there were a few extra steps such as making ST_CALL
and so on but I didn't find them that important (especially since it's so similar to IB_PRINT
. But now lets run some code!
from GoType import *
def blah():
print("Hello")
blah()
This turns into the following code.
package main
func main() {
blah()
}
func blah() { println("Hello") }
Honestly some weird formatting choices were made but other than that our compiler is functional, so I can't really complain too much.
Next
Currently, our errors are looking kind of bland, we should probably make them nicer by adding line numbers and which function threw the error [thanks Joshua (:]. In the end we want errors that are useful for the compiler designer, and the user.
You may have noticed that we didn't change the semantic analyzer, which is a huge oversight since any variables we have as function parameters won't be seen as declared, throwing an error. In a future post we will be working on fixing that issue.