4
\$\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)
 }
 }
}
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Jun 27, 2014 at 19:38
\$\endgroup\$

1 Answer 1

3
\$\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).

answered Jul 2, 2014 at 6:01
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.