You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
5.3 KiB
Go
246 lines
5.3 KiB
Go
package parser
|
|
|
|
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
|
|
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 {
|
|
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))
|
|
}
|
|
|
|
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()
|
|
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 {
|
|
t := p.scan()
|
|
var lcount int
|
|
|
|
if t.kind == Number {
|
|
lcount, _ = 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))
|
|
}
|
|
|
|
procedures := make([]Procedure,0, lcount * 2)
|
|
|
|
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))
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
fullLoop := make([]Procedure, 0, len(procedures) * lcount)
|
|
|
|
for i := lcount;i > 0; i-- {
|
|
fullLoop = append(fullLoop, procedures...)
|
|
}
|
|
|
|
return fullLoop
|
|
}
|
|
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
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":
|
|
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":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NewParser(r io.Reader) *Parser {
|
|
return &Parser{s: NewScanner(r)}
|
|
}
|
|
|
|
|