342 lines
7.1 KiB
Go
342 lines
7.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"text/scanner"
|
|
)
|
|
|
|
// Parser parses a string into an AST.
|
|
type Parser struct {
|
|
s scanner.Scanner
|
|
}
|
|
|
|
func NewParser(expr string) *Parser {
|
|
// Preprocess multi-char operators to single chars so the scanner reads them easily
|
|
expr = strings.ReplaceAll(expr, "**", "\x01")
|
|
expr = strings.ReplaceAll(expr, "<<", "\x02")
|
|
expr = strings.ReplaceAll(expr, ">>", "\x03")
|
|
expr = strings.ReplaceAll(expr, "==", "\x04")
|
|
expr = strings.ReplaceAll(expr, "!=", "\x05")
|
|
expr = strings.ReplaceAll(expr, "<=", "\x06")
|
|
expr = strings.ReplaceAll(expr, ">=", "\x07")
|
|
p := &Parser{}
|
|
p.s.Init(strings.NewReader(expr))
|
|
p.s.Mode = scanner.ScanInts | scanner.ScanFloats | scanner.ScanIdents | scanner.ScanStrings
|
|
p.s.Error = func(s *scanner.Scanner, msg string) {}
|
|
return p
|
|
}
|
|
|
|
func (p *Parser) skipWhitespace() {
|
|
for {
|
|
ch := p.s.Peek()
|
|
if ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n' {
|
|
p.s.Next()
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Parse parses the entire expression.
|
|
func (p *Parser) Parse() (Expr, error) {
|
|
expr, err := p.parseExpression()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.skipWhitespace()
|
|
if p.s.Peek() != scanner.EOF {
|
|
return nil, fmt.Errorf("unexpected trailing characters")
|
|
}
|
|
return expr, nil
|
|
}
|
|
|
|
func (p *Parser) parseExpression() (Expr, error) {
|
|
return p.parseEquality()
|
|
}
|
|
|
|
func (p *Parser) parseEquality() (Expr, error) {
|
|
res, err := p.parseRelational()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
ch := p.s.Peek()
|
|
if ch == '\x04' {
|
|
p.s.Next()
|
|
rhs, err := p.parseRelational()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "==", Left: res, Right: rhs}
|
|
} else if ch == '\x05' {
|
|
p.s.Next()
|
|
rhs, err := p.parseRelational()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "!=", Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseRelational() (Expr, error) {
|
|
res, err := p.parseXor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
ch := p.s.Peek()
|
|
if ch == '\x06' {
|
|
p.s.Next()
|
|
rhs, err := p.parseXor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "<=", Left: res, Right: rhs}
|
|
} else if ch == '\x07' {
|
|
p.s.Next()
|
|
rhs, err := p.parseXor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: ">=", Left: res, Right: rhs}
|
|
} else if ch == '<' {
|
|
p.s.Next()
|
|
rhs, err := p.parseXor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "<", Left: res, Right: rhs}
|
|
} else if ch == '>' {
|
|
p.s.Next()
|
|
rhs, err := p.parseXor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: ">", Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseXor() (Expr, error) {
|
|
res, err := p.parseAnd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
if p.s.Peek() == '|' || p.s.Peek() == '^' {
|
|
op := string(p.s.Next())
|
|
rhs, err := p.parseAnd()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: op, Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseAnd() (Expr, error) {
|
|
res, err := p.parseShift()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
if p.s.Peek() == '&' {
|
|
p.s.Next()
|
|
rhs, err := p.parseShift()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "&", Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseShift() (Expr, error) {
|
|
res, err := p.parseSum()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
ch := p.s.Peek()
|
|
if ch == '\x02' {
|
|
p.s.Next()
|
|
rhs, err := p.parseSum()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "<<", Left: res, Right: rhs}
|
|
} else if ch == '\x03' {
|
|
p.s.Next()
|
|
rhs, err := p.parseSum()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: ">>", Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseSum() (Expr, error) {
|
|
res, err := p.parseTerm()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
ch := p.s.Peek()
|
|
if ch == '+' || ch == '-' {
|
|
op := string(p.s.Next())
|
|
rhs, err := p.parseTerm()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: op, Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parseTerm() (Expr, error) {
|
|
res, err := p.parsePower()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for {
|
|
p.skipWhitespace()
|
|
ch := p.s.Peek()
|
|
if ch == '*' || ch == '/' || ch == '%' {
|
|
op := string(p.s.Next())
|
|
rhs, err := p.parsePower()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: op, Left: res, Right: rhs}
|
|
} else if (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '$' || ch == '€' || ch == '£' || ch == '¥' || ch == '(' {
|
|
// Implicit multiplication
|
|
rhs, err := p.parseFactor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "implicit_mult", Left: res, Right: rhs}
|
|
} else {
|
|
return res, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Parser) parsePower() (Expr, error) {
|
|
res, err := p.parseFactor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.skipWhitespace()
|
|
if p.s.Peek() == '\x01' {
|
|
p.s.Next()
|
|
rhs, err := p.parsePower()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = &BinaryExpr{Op: "**", Left: res, Right: rhs}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (p *Parser) parseFactor() (Expr, error) {
|
|
tok := p.s.Scan()
|
|
switch tok {
|
|
case '(':
|
|
res, err := p.parseExpression()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if p.s.Scan() != ')' {
|
|
return nil, fmt.Errorf("missing )")
|
|
}
|
|
return res, nil
|
|
case '-':
|
|
res, err := p.parseFactor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &UnaryExpr{Op: "-", Expr: res}, nil
|
|
case '~', '!':
|
|
op := string(tok)
|
|
res, err := p.parseFactor()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &UnaryExpr{Op: op, Expr: res}, nil
|
|
case '$':
|
|
return &IdentExpr{Name: "$"}, nil
|
|
case '€':
|
|
return &IdentExpr{Name: "€"}, nil
|
|
case '£':
|
|
return &IdentExpr{Name: "£"}, nil
|
|
case '¥':
|
|
return &IdentExpr{Name: "¥"}, nil
|
|
case scanner.String:
|
|
text := p.s.TokenText()
|
|
text = text[1 : len(text)-1]
|
|
return &StringExpr{Value: text}, nil
|
|
case scanner.Int, scanner.Float, scanner.Ident:
|
|
text := p.s.TokenText()
|
|
if tok == scanner.Ident {
|
|
p.skipWhitespace()
|
|
if p.s.Peek() == '(' {
|
|
p.s.Next() // consume '('
|
|
var args []Expr
|
|
for {
|
|
p.skipWhitespace()
|
|
if p.s.Peek() == ')' {
|
|
p.s.Next()
|
|
break
|
|
}
|
|
arg, err := p.parseExpression()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args = append(args, arg)
|
|
p.skipWhitespace()
|
|
nextChar := p.s.Peek()
|
|
if nextChar == ',' {
|
|
p.s.Next()
|
|
continue
|
|
} else if nextChar == ')' {
|
|
p.s.Next()
|
|
break
|
|
} else {
|
|
return nil, fmt.Errorf("missing ) or comma in function arguments")
|
|
}
|
|
}
|
|
return &CallExpr{Func: text, Args: args}, nil
|
|
}
|
|
return &IdentExpr{Name: text}, nil
|
|
}
|
|
return &NumberExpr{Value: text}, nil
|
|
default:
|
|
txt := p.s.TokenText()
|
|
if tok == scanner.EOF {
|
|
return nil, fmt.Errorf("unexpected end of expression")
|
|
}
|
|
return nil, fmt.Errorf("unexpected %s", txt)
|
|
}
|
|
}
|