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)} }