filtress/parser/parser.go

227 lines
4.5 KiB
Go

package parse
import (
"io"
"fmt"
"strings"
"strconv"
)
type Parser struct {
s *scanner
row int
buffer struct {
token Token
size int
}
}
type Expression struct {
Opperator rune
Opperand int
Variable string
}
type Procedure struct {
Kind string
Expressions []Expression
}
type Loop struct {
Counter int
Procedures []Procedure
}
type AST struct {
Loops []Loop
}
func (p *Parser) scan() (current Token) {
if p.buffer.size != 0 {
p.buffer.size = 0
return p.buffer.token
}
for {
if current = p.s.scan(); current.kind != Whitespace && current.kind != Comment {
break
}
}
current = p.s.scan()
p.buffer.token = current
return
}
func (p *Parser) unscan() {
p.buffer.size = 1
}
func (p *Parser) parseExpressions(equals bool) []Expression {
var exprs []Expression = make([]Expression, 0, 4)
for {
t := p.scan()
ex := Expression{}
if t.kind == Opperator {
ex.Opperator = rune(t.val[0])
t = p.scan()
} else {
ex.Opperator = '='
}
if t.kind == Number {
ex.Opperand, _ = strconv.Atoi(t.val)
} else if t.kind == Text {
if isVariable(t.val) {
ex.Variable = strings.ToUpper(t.val)
} else {
panic(fmt.Sprintf("Parse error: Invalid variable %q on \033[1mline %d\033[0m", t.val, p.row))
}
}
exprs = append(exprs, ex)
if t.kind == Separator {
continue
}
if t.kind == Newline || t.kind == End {
p.unscan()
break
}
}
return exprs
}
func (p *Parser) parseProcedure() Procedure {
out := Procedure{}
t := p.scan()
t.val = strings.ToUpper(t.val)
if isValidProcedure(t.val) {
out.Kind = t.val
} else {
panic(fmt.Sprintf("Parse error: Invalid procedure call %q on \033[1mline %d\033[0m", t.val, p.row))
}
t = p.scan()
switch t.kind {
case Number, Text:
p.unscan()
out.Expressions = p.parseExpressions(true)
case Opperator:
p.unscan()
out.Expressions = p.parseExpressions(false)
case End, Newline:
p.unscan()
default:
panic(fmt.Sprintf("Parse error: Invalid token %q on \033[1mline %d\033[0m", t.val, p.row))
}
// isVariable(t.val)
return out
}
func (p *Parser) parseLoop() Loop {
t := p.scan()
l := Loop{}
if strings.ToUpper(t.val) == "LOOP" {
t = p.scan()
if t.kind == Number {
l.Counter, _ = strconv.Atoi(t.val)
} else {
panic(fmt.Sprintf("Parse error: Loop declared, but not followed by a number on \033[1mline %d\033[0m", p.row))
}
} else {
p.unscan()
l.Counter = 1
}
l.Procedures = make([]Procedure,0, 5)
L:
for {
t = p.scan()
switch t.kind {
case Newline:
p.row++
case Illegal:
panic(fmt.Sprintf("Parse error: Illegal token %q on \033[1mline %d\033[0m", t.val, p.row))
case Opperator:
panic(fmt.Sprintf("Parse error: Opperator %q outside of a procedure call on \033[1mline %d\033[0m", t.val, p.row))
case Separator:
panic(fmt.Sprintf("Parse error: Illegal field separator ':' on \033[1mline %d\033[0m", p.row))
case End:
break L
case Number:
panic(fmt.Sprintf("Parse error: Number %q outside of procedure call on \033[1mline %d\033[0m", t.val, p.row))
case Text:
p.unscan()
procedure := p.parseProcedure()
l.Procedures = append(l.Procedures, procedure)
}
}
return l
}
func (p *Parser) Parse() AST {
p.row = 1
tree := AST{}
L:
for {
t := p.scan()
switch t.kind {
case Newline:
p.row++
case Illegal:
panic(fmt.Sprintf("Parse error: Illegal token %q on \033[1mline %d\033[0m", t.val, p.row))
case Opperator:
panic(fmt.Sprintf("Parse error: Opperator %q outside of a procedure call on \033[1mline %d\033[0m", t.val, p.row))
case Separator:
panic(fmt.Sprintf("Parse error: Illegal field separator ':' on \033[1mline %d\033[0m", p.row))
case End:
break L
case Number:
panic(fmt.Sprintf("Parse error: Number %q outside of procedure call on \033[1mline %d\033[0m", t.val, p.row))
case Text:
p.unscan()
loop := p.parseLoop()
tree.Loops = append(tree.Loops, loop)
}
}
return tree
}
func isValidProcedure(p string) bool {
switch p {
case "REG", "LOC", "BEG", "END", "RED",
"GRN", "BLU", "APH", "COL", "APY", "MOD",
"FAD":
return true
}
return false
}
func isVariable(v string) bool {
v = strings.ToUpper(v)
switch v {
case "WID", "HIG", "REG":
return true
}
return false
}
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}