Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
161 lines (144 sloc) 3.73 KB
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
package eval
import (
"fmt"
"strconv"
"strings"
"text/scanner"
)
// ---- lexer ----
// This lexer is similar to the one described in Chapter 13.
type lexer struct {
scan scanner.Scanner
token rune // current lookahead token
}
func (lex *lexer) next() { lex.token = lex.scan.Scan() }
func (lex *lexer) text() string { return lex.scan.TokenText() }
type lexPanic string
// describe returns a string describing the current token, for use in errors.
func (lex *lexer) describe() string {
switch lex.token {
case scanner.EOF:
return "end of file"
case scanner.Ident:
return fmt.Sprintf("identifier %s", lex.text())
case scanner.Int, scanner.Float:
return fmt.Sprintf("number %s", lex.text())
}
return fmt.Sprintf("%q", rune(lex.token)) // any other rune
}
func precedence(op rune) int {
switch op {
case '*', '/':
return 2
case '+', '-':
return 1
}
return 0
}
// ---- parser ----
// Parse parses the input string as an arithmetic expression.
//
// expr = num a literal number, e.g., 3.14159
// | id a variable name, e.g., x
// | id '(' expr ',' ... ')' a function call
// | '-' expr a unary operator (+-)
// | expr '+' expr a binary operator (+-*/)
//
func Parse(input string) (_ Expr, err error) {
defer func() {
switch x := recover().(type) {
case nil:
// no panic
case lexPanic:
err = fmt.Errorf("%s", x)
default:
// unexpected panic: resume state of panic.
panic(x)
}
}()
lex := new(lexer)
lex.scan.Init(strings.NewReader(input))
lex.scan.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats
lex.next() // initial lookahead
e := parseExpr(lex)
if lex.token != scanner.EOF {
return nil, fmt.Errorf("unexpected %s", lex.describe())
}
return e, nil
}
func parseExpr(lex *lexer) Expr { return parseBinary(lex, 1) }
// binary = unary ('+' binary)*
// parseBinary stops when it encounters an
// operator of lower precedence than prec1.
func parseBinary(lex *lexer, prec1 int) Expr {
lhs := parseUnary(lex)
for prec := precedence(lex.token); prec >= prec1; prec-- {
for precedence(lex.token) == prec {
op := lex.token
lex.next() // consume operator
rhs := parseBinary(lex, prec+1)
lhs = binary{op, lhs, rhs}
}
}
return lhs
}
// unary = '+' expr | primary
func parseUnary(lex *lexer) Expr {
if lex.token == '+' || lex.token == '-' {
op := lex.token
lex.next() // consume '+' or '-'
return unary{op, parseUnary(lex)}
}
return parsePrimary(lex)
}
// primary = id
// | id '(' expr ',' ... ',' expr ')'
// | num
// | '(' expr ')'
func parsePrimary(lex *lexer) Expr {
switch lex.token {
case scanner.Ident:
id := lex.text()
lex.next() // consume Ident
if lex.token != '(' {
return Var(id)
}
lex.next() // consume '('
var args []Expr
if lex.token != ')' {
for {
args = append(args, parseExpr(lex))
if lex.token != ',' {
break
}
lex.next() // consume ','
}
if lex.token != ')' {
msg := fmt.Sprintf("got %s, want ')'", lex.token)
panic(lexPanic(msg))
}
}
lex.next() // consume ')'
return call{id, args}
case scanner.Int, scanner.Float:
f, err := strconv.ParseFloat(lex.text(), 64)
if err != nil {
panic(lexPanic(err.Error()))
}
lex.next() // consume number
return literal(f)
case '(':
lex.next() // consume ')'
e := parseExpr(lex)
if lex.token != ')' {
msg := fmt.Sprintf("got %s, want ')'", lex.describe())
panic(lexPanic(msg))
}
lex.next() // consume ')'
return e
}
msg := fmt.Sprintf("unexpected %s", lex.describe())
panic(lexPanic(msg))
}