3
\$\begingroup\$

My program 'Monkey', written in Go, can take a file like this:

+ 9 13
/ 10 2

And prints the output (in the case for the 'file' above, '22' and '5'.) Variables can also be declared to an expression. In this 'file':

* 5 4 : a
+ a 2 : a

The variable a ends up as 22, because at first we assign it to 5 * 4 (20), and then add 2 (22).

Here's the source for the program, and here it is with syntax highlighting.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "strconv"
)

// error management
func check(e error) {
    if e != nil {
        panic(e)
    }
}

func openfile(filename string) (lines []string) {
    // safely open file
    source, err := os.Open(filename)
    check(err)

    scanner := bufio.NewScanner(source)
    // equivalent to a while loop
    for scanner.Scan() {
        lines = append(lines, scanner.Text())
    }

    return
}

var variables map[string]float32

// determine whether a token is a variable reference or
// an integer literal, and return it.
func getvalue(item string) float32 {
    // if item is number
    if num, err := strconv.Atoi(item); err == nil {
        return float32(num)
    }
    return variables[item]
}

func operate(operands []string, op string) float32 {
    var result float32 = 0
    // o = index in operands[]
    for o := 0; o < len(operands); o++ {
        if op == "+" {
            if o == 0 {
                result = getvalue(operands[0])
            } else {
                result += getvalue(operands[o])
            }
        } else if op == "-" {
            if o == 0 {
                result = getvalue(operands[0])
            } else {
                result -= getvalue(operands[o])
            }
        } else if op == "*" {
            if o == 0 {
                result = getvalue(operands[0])
            } else {
                result *= getvalue(operands[o])
            }
        } else if op == "/" {
            if o == 0 {
                result = getvalue(operands[0])
            } else {
                result /= getvalue(operands[o])
            }
        } else {
            return 0.0
        }
    }
    return result
}

var x, y int64 // these will be assigned to function params
var xerr, yerr error
func main() {
    variables = make(map[string]float32)
    lines := openfile(os.Args[1])
    for lineNumber, line := range lines {
        words := strings.Split(line, " ")
        operands := 0 // count of numbers that the operation will be applied to
        for _, word := range words {
            if _, err := strconv.Atoi(word); err == nil {
                operands += 1
            }
        }
        // line needs to end in ';' or ': varName' to be correct
        if len(words) >= operands + 2 {
            if words[operands + 1] == ";" {
                fmt.Println(operate(words[1:operands+1], words[0]))
            } else if words[operands + 1] == ":" {
                fmt.Println("var", words[operands + 2], "=", operate(words[1:operands+1], words[0]))
                variables[words[operands + 2]] = operate(words[1:operands+1], words[0])
            }
        } else if len(words) < 0 && len(words) < operands + 2 {
            fmt.Println("error: not enough arguments at line", lineNumber)
        }
    }
}
\$\endgroup\$
2
\$\begingroup\$

As a general naming convention, go methods are either named LikeThis (for public methods) or likeThis (for package private methods). For a small, single file project like this it doesn't really matter too much, but it is a good habit to get into regardless.

Your openfile method should probably be named readFile, as it doesn't just open the file, it also reads every line in it. Further, you're forgetting to close the file when you are done with it. Go has a fairly clean way to ensure this will always happen: the defer keyword:

func readFile(filename string) (lines []string) {
    source, err := os.Open(filename)
    defer source.Close()  // Make sure we close the file

    ...
}

Your operate function can be written more cleanly; firstly, by recognising that the case if o == 0 is exactly the same in each branch, secondly by using a switch statement (which is more idiomatic in go):

func operate(operands []string, op string) float32 {
    result := getvalue(operands[0])
    for o := range operands[1:] {
        switch op {
        case "+": result += getvalue(o)
        case "-": result -= getvalue(o)
        case "*": result *= getvalue(o)
        case "/": result /= getvalue(o)
        default: return 0.0
        }
    }
    return result
}

Here, we get initialise result to the first value in operands. Then, we range over the rest of the elements in operands. This uses range, which should be preferred to manual for loops when you can use them, and also slicing off the first element of the slice.

Note that go does not need break statements in a switch, rather, it requires an explicit fallthrough (which stops probably the largest class of bugs when using switch statements).

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.