Calling Functions (Pogo Pt:13)
Tags: python, go, compiler, transpiler
Published: 2024-01-29 02:06:27

## Intro
In this series I am creating a transpiler from Python to Golang called [Pogo](https://github.com/Chig-Beef/Pogo). In the last post we made it possible to use parameters in functions. This was a great addition, but we weren't able to properly use it because we aren't able to call functions with parameters.

## Current Error
Currently, the error we are getting about this is coming from the parser. Now the error only occurs when we try to use literals in the function, so we need to change it to allow them along with identifiers.
```golang
temp, err = p.checkTokenChoices([]string{
    "IDENTIFIER",
    "L_BOOL",
    "L_INT",
    "L_STRING",
})

for err == nil {
    s.children = append(s.children, temp)
    p.nextToken()

    temp, err = p.checkToken("SEP")
    if err != nil {
        break
    }
    s.children = append(s.children, temp)
    p.nextToken()

    temp, err = p.checkTokenChoices([]string{
        "IDENTIFIER",
        "L_BOOL",
        "L_INT",
        "L_STRING",
    })
}
```
This code now allows for any literal or identifier.

## We Need Type Checking
Everything is great so far, but right now I can put a string where a function expects an integer. Since this is checking whether the code makes sense we are going to do this in the semantic analyzer. Currently we keep track of what variables are available to use in scope, and we can add a similar feature for functions. First we need to create a struct to store this information.

## The Functions
```golang
type Function struct {
    name   string
    params []string
}
```
The `name` property holds the name, which can be used to see if we are even allowed to call the function here. Then, `params` holds the types of the parameters. We don't need to hold the names of the parameters, just the types.

## Scoping
Now we can pass a slice of these `Function`s to `analyze`.
```golang
func (a *Analyzer) analyze(s Structure, vars []Variable, funcs []Function) error
```
Now when we come across a function declaration, we add it to `funcs`. After this we can start working on the logic of calling a function.
First, we check the function exists.
```golang
name := s.children[0].text
var valid bool
for i := 0; i < len(funcs); i++ {
    valid = false
    if name == funcs[i].name {
        valid = true
        break
    }
}
if !valid {
    return createError([]string{"analyze.go", "analyze:ST_CALL"}, "An attempt to call a non-existent function was made", s.line)
}
```
Now we can start checking each parameter's type, but first we check that they're all valid variable names (that is, if they're not literals). I lost how I fixed this because of all the weird coding over multiple files, figured out my issue was that I wasn't implying that `print` was a valid function. But this does mean I have to add another type, the `any` type, since `print` can take in pretty much anything. This actually turned out to be pretty simple, a one line fix.
```golang
if variable.varType != fn.params[pIndex] && fn.params[pIndex] != "any" {
    return createError([]string{"analyze.go", "analyze:ST_CALL"}, "Wrong type used in function call", s.line)
}
```
By using and we can override if the type is any.

## Working Out Literals
Here is the current code for when we call a function with literals.
```golang
if s.children[i].code != structureCode["IDENTIFIER"] {
    continue
}
```
We just skip over for now, but we don't want to do that, we want to implement it correctly. We have three types in the compiler, `string`, `int`, and `bool`, so we are going to have cases for all three of those. If we get a `string` then we only need to check whether the type is a `string`, same with `bool`. However, with `int` we have to check all the `int` types, including `float`, and `uint`.
```golang
if s.children[i].code != structureCode["IDENTIFIER"] {
	switch s.children[i].code {
	case structureCode["L_STRING"]:
		if fn.params[pIndex] != "string" {
			return createError([]string{"analyze.go", "analyze:ST_CALL"}, "Excpected "+fn.params[pIndex]+" got string in function call", s.line)
		}
	case structureCode["L_BOOL"]:
		if fn.params[pIndex] != "bool" {
			return createError([]string{"analyze.go", "analyze:ST_CALL"}, "Excpected "+fn.params[pIndex]+" got bool in function call", s.line)
		}
	case structureCode["L_INT"]:
		valid := false
		types := []string{"int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "uintptr", "byte", "float32", "float64"}
		for j := 0; j < len(types); j++ {
			if types[j] == fn.params[pIndex] {
				valid = true
				break
			}
		}
		if !valid {
			return createError([]string{"analyze.go", "analyze:ST_CALL"}, "Excpected "+fn.params[pIndex]+" got int in function call", s.line)
		}
	default:
		return createError([]string{"analyze.go", "analyze:ST_CALL"}, "How did you even...?", s.line)
	}
	i += 2
	pIndex++
	continue
}
```
The `string` and `bool` cases are quite simple. The default case shouldn't ever run because those are the only implemented types of literals. Lastly, when we find an `int` we have to look through every `int` type and see if there is any match. If there isn't, we throw an error.

## Next
We are very close to the end of the month, so we are almost done Pogo. For the few days left I don't think classes would be feasible, however, we can try our best to implement them. It might be a bit crude however, since there is such a difference between structs and classes. But it's still worth a try.