DEV Community

Cover image for Returning From Functions (Pogo Pt:14)
Chig Beef
Chig Beef

Posted on

Returning From Functions (Pogo Pt:14)

Intro

In this series I am creating a transpiler from Python to Golang called Pogo.
In the last post I thought we were mostly finished with functions, then I forgot that most of the time we want to return a value from a function. Not having this functionality seems kind of like cheating, so we're going to implement that right now.

Function Typing

Lucky for us, return types are available in Python's type annotations, so we can add that in.

def test(x: int, y: int) -> None:
Enter fullscreen mode Exit fullscreen mode

And we also have to add None because we forgot to add that in (my bad). So now we have 2 tokens to add, None, and ->. Furthermore, we have to add return statements (which I think is mostly done), and the ability to use calls as any literal would be able to be used.

Lexing

Adding None won't be that hard in the lexer, neither will the arrow.

if word == "None" {
    token = Token{tokenCode["L_NULL"], word, l.line}
}
Enter fullscreen mode Exit fullscreen mode
else if l.curChar == '-' {
    if l.peek() == '>' {
        l.nextChar()
        token = Token{tokenCode["ARROW"], "->", l.line}
    } else {
        token = Token{tokenCode["MO_SUB"], "-", l.line}
    }
}
Enter fullscreen mode Exit fullscreen mode

Of course, we have to make sure that we are looking at an arrow, and not just the subtract sign.

Parsing

In the parser, it should be mandatory to express what type a function returns (void functions return None). We currently only need to change the definition. The arrow and type appear before the colon and after the right bracket, so we need to add these lines in-between the checking for these tokens.

temps, err := p.checkTokenRange([]string{
    "R_PAREN",
    "ARROW",
})
if err != nil {
    return s, err
}
s.children = append(s.children, temps...)
p.nextToken()

temp, err = p.checkTokenChoices([]string{
    "IDENTIFIER",
    "L_NULL",
})
if err != nil {
    return s, err
}
s.children = append(s.children, temp)
p.nextToken()
Enter fullscreen mode Exit fullscreen mode

Modularizing Calls

Since we are going to be using calls in more places than one we should make the logic for calls another function, similar to if statements. This is simply a copy paste job.

Using Calls

Now that we have modularized calls we can start using them in places such as declarations. We're going to have to work on the parser for this one.

p.setMarker()
temp, err := p.call()
if err != nil {
    p.gotoMarker()
    temp, err = p.checkTokenChoices([]string{
        "L_BOOL",
        "L_INT",
        "L_STRING",
    })
    if err != nil {
        return s, err
    }
}
s.children = append(s.children, temp)
Enter fullscreen mode Exit fullscreen mode

We're making great use of setMarker here. Now we need to fix our semantic analyzer, as we're getting an error whenever we use a call here.

Semantic Analysis

First I had to give Functions a varType, so we can keep our types in check (print can be given the None type). If you remember from last post we made a bit of a switch statement to deal with what could be in the call if the current Structure isn't an identifier. Calls start with a function name, which is different from an identifier, so we need to make a new case for that, and well, deal with it. I accidentally fixed it for declarations (without type-checking) so now we are going to put calls in calls.

case structureCode["ST_CALL"]:
    var insideCall Function

    name := s.children[i].children[1].text
    var valid bool
    for i := 0; i < len(funcs); i++ {
        valid = false
        if name == funcs[i].name {
            valid = true
            insideCall = funcs[i]
            break
        }
    }
    if !valid {
        return createError([]string{"analyze.go", "analyze:ST_CALL"}, "An attempt to call the non-existent function ""+s.children[0].text+"" was made", s.line)
    }

    if fn.params[pIndex] != insideCall.varType {
        return createError([]string{"analyze.go", "analyze:ST_CALL"}, """+s.children[0].text+"" is the wrong type, expected "+fn.params[pIndex]+" got "+insideCall.varType, s.line)
    }
Enter fullscreen mode Exit fullscreen mode

We first have to check whether we are making a valid call, then we can type check, which turned out to be pretty simple.

Back In The Parser

Of course, because of how I've done this we're back in the parser so we can implement calls in calls.

if p.peek().code == tokenCode["L_PAREN"] { // We're dealing with a call
    temp, err = p.call()
    if err != nil {
        return s, err
    }
}
Enter fullscreen mode Exit fullscreen mode

Test Case

Let's give our compiler some Python, and see what it manages to do.

from GoType import *

def printNum(x: int) -> None:
    print(x)

def someNum() -> int:
    return 7

printNum(someNum())
Enter fullscreen mode Exit fullscreen mode

Here we are just printing the number 7 but in a complicated way to test our compiler, and here is what it managed to get from it.

package main

func printNum(x int) { println(x) }
func someNum() int   { return 7 }
func main() {

    printNum(someNum())
}
Enter fullscreen mode Exit fullscreen mode

That's pretty good, except for the formatting though, as always (did gofmt really just set the blocks of printNum and someNum to the same column??). Now we have to do a small error check of types, and we don't get the result we want. If we change the type someNum returns to string and change nothing else we don't get a compiler error, which leads to incorrect Go code, so we definitely need to be fixing this. The issue to this was honestly just a few bugs from my bad code, so nothing to hard to fix.

Next

I know I said something about classes in the last post but then I realized that I still had quite a bit of work to do on functions, and with it being really close to the end of the month it definitely isn't feasible to add classes now. I honestly don't know what to add, I don't think an optimizer is a good idea because it doesn't create Go from Python as exactly, which is what we want. I do have to do type checking for calls in declarations still, and their use in expressions. Furthermore, comparisons should be able to be a single literal or identifier, which would allow code such as if valid:. I think small clean ups will be the theme of the next post, just to make the compiler as a whole a bit more usable, instead of adding a bunch of new features.

Top comments (0)