Skip to content

Instantly share code, notes, and snippets.

@chakrit
Created February 16, 2023 08:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chakrit/65cc1000c850a8b49df81278ac3d4b1c to your computer and use it in GitHub Desktop.
Save chakrit/65cc1000c850a8b49df81278ac3d4b1c to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"fmt"
"log"
"os"
"strconv"
"unicode"
)
type TokenType int
const (
NumberToken = iota
OperatorToken = iota
SpaceToken = iota
)
type Token struct {
Literal string
Type TokenType
}
func (t Token) String() string {
switch t.Type {
case NumberToken:
return "NUMBER: " + t.Literal
case OperatorToken:
return "OPERATOR: " + t.Literal
case SpaceToken:
return "SPACE: " + t.Literal
default:
return "(unknown)"
}
}
func main() {
tokens := Lex(bufio.NewReader(os.Stdin))
for _, t := range tokens {
fmt.Println(t)
}
answer := Parse(tokens)
fmt.Println("answer: ", answer)
}
func Parse(tokens []Token) int {
return ParseFirstNum(tokens)
}
func ParseFirstNum(tokens []Token) int {
tok := tokens[0]
if tok.Type != NumberToken {
log.Fatalln("ERROR! invalid syntax at: " + tok.Literal)
}
return ParseOp(tokens[1:], tok)
}
func ParseOp(tokens []Token, leftNum Token) int {
tok := tokens[0]
if tok.Type != OperatorToken {
log.Fatalln("ERROR! two numbers, no operator at: " + tok.Literal)
}
return ParseSecondNum(tokens[1:], leftNum, tok)
}
func ParseSecondNum(tokens []Token, leftNum, operator Token) int {
tok := tokens[0]
if tok.Type != NumberToken {
log.Fatalln("ERROR! invalid syntax, expect number at: " + tok.Literal)
}
left, _ := strconv.Atoi(leftNum.Literal)
right, _ := strconv.Atoi(tok.Literal)
result := 0
switch operator.Literal {
case "+":
result = left + right
case "-":
result = left - right
default:
log.Fatalln("ERROR! unsupported operator: " + tok.Literal)
}
tokens = tokens[1:]
if len(tokens) == 0 {
return result
}
if len(tokens) < 2 { // at least 1 more operator and 1 more number
log.Fatalln("ERROR! leftover token: " + tokens[0].Literal)
}
return ParseOp(tokens, Token{
Literal: strconv.Itoa(result),
Type: NumberToken,
})
}
func Lex(reader *bufio.Reader) (tokens []Token) {
for {
r, _, err := reader.ReadRune()
if err != nil {
fmt.Println(err)
break
}
if unicode.IsSpace(r) {
// automatically ignore space tokens
continue
}
switch r {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
reader.UnreadRune()
tokens = append(tokens, LexNumber(reader))
case '+', '-', '*', '/':
tokens = append(tokens, Token{
Literal: string(r),
Type: OperatorToken,
})
default:
fmt.Printf("invalid token: %#v\n", r)
break
}
}
return
}
func LexNumber(reader *bufio.Reader) Token {
scratch := ""
LexNum:
for {
r, _, err := reader.ReadRune()
if err != nil {
fmt.Println(err)
break
}
switch r {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
scratch = scratch + string(r)
default:
reader.UnreadRune()
break LexNum
}
}
return Token{
Literal: scratch,
Type: NumberToken,
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment