filtress/parser/parser.go

246 lines
5.3 KiB
Go
Raw Permalink Normal View History

package parser
2019-07-16 23:36:06 +00:00
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 {
Procedures []Procedure
2019-07-16 23:36:06 +00:00
Loops []Loop
}
func (p *Parser) scan() (current Token) {
if p.buffer.size != 0 {
p.buffer.size = 0
return p.buffer.token
}
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 if t.kind == Number || t.kind == Text {
2019-07-16 23:36:06 +00:00
ex.Opperator = '='
} else if t.kind == Newline {
p.unscan()
break
} else {
panic(fmt.Sprintf("Parse error: Invalid token %q on \033[1mline %d\033[0m", t.val, p.row))
2019-07-16 23:36:06 +00:00
}
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)
t = p.scan()
2019-07-16 23:36:06 +00:00
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))
}
return out
}
func (p *Parser) parseLoop() []Procedure {
2019-07-16 23:36:06 +00:00
t := p.scan()
var lcount int
2019-07-16 23:36:06 +00:00
if t.kind == Number {
lcount, _ = strconv.Atoi(t.val)
2019-07-16 23:36:06 +00:00
} else {
panic(fmt.Sprintf("Parse error: Loop declared, but not followed by a number on \033[1mline %d\033[0m", p.row))
2019-07-16 23:36:06 +00:00
}
procedures := make([]Procedure,0, lcount * 2)
2019-07-16 23:36:06 +00:00
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:
panic(fmt.Sprintf("Parse error: Encountered EOF before END of loop on \033[1mline %d\033[0m", p.row))
2019-07-16 23:36:06 +00:00
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:
if strings.ToUpper(t.val) == "END" {
break L
}
if strings.ToUpper(t.val) == "BEG" {
subloop := p.parseLoop()
procedures = append(procedures, subloop...)
} else {
p.unscan()
procedure := p.parseProcedure()
procedures = append(procedures, procedure)
}
2019-07-16 23:36:06 +00:00
}
}
fullLoop := make([]Procedure, 0, len(procedures) * lcount)
for i := lcount;i > 0; i-- {
fullLoop = append(fullLoop, procedures...)
}
return fullLoop
2019-07-16 23:36:06 +00:00
}
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:
if strings.ToUpper(t.val) == "BEG" {
loopVal := p.parseLoop()
tree.Procedures = append(tree.Procedures, loopVal...)
} else {
p.unscan()
procedure := p.parseProcedure()
tree.Procedures = append(tree.Procedures, procedure)
}
2019-07-16 23:36:06 +00:00
}
}
return tree
}
func isValidProcedure(p string) bool {
switch p {
case "RG1", "RG2", "LOC", "BEG", "END", "RED", "GET",
"GRN", "BLU", "APH", "COL", "APY", "MOD", "LOX", "LOY":
2019-07-16 23:36:06 +00:00
return true
}
return false
}
func isVariable(v string) bool {
v = strings.ToUpper(v)
switch v {
case "WID", "HIG", "RG1", "RG2", "LOX", "LOY",
"RED", "BLU", "GRN", "APH":
2019-07-16 23:36:06 +00:00
return true
}
return false
}
func NewParser(r io.Reader) *Parser {
return &Parser{s: NewScanner(r)}
}