TDL/parser.go

281 lines
5.2 KiB
Go

package main
import (
"fmt"
"io"
"strconv"
"strings"
)
func DebugParser(input []rune, writer io.Writer) {
p := NewParser(input)
tdl := p.Parse()
fmt.Fprintln(writer, string(tdl.JSON()))
}
type Parser struct {
lexer *Lexer
sym Sym
}
func NewParser(input []rune) *Parser {
p := &Parser{lexer: NewLexer(input)}
p.get()
return p
}
func (p *Parser) exitWith(v ...interface{}) {
v = append([]interface{}{"syntax error in line", p.lexer.line, ":"}, v...)
panic(fmt.Sprintln(v...))
}
func (p *Parser) invalid(what string) {
p.exitWith("invalid", what, ">", p.sym.Value, "<")
}
func (p *Parser) string() string {
val := strings.TrimPrefix(strings.TrimSuffix(p.sym.Value, "\""), "\"")
if p.sym.Type != SymString {
p.invalid("string")
}
p.get()
return val
}
func (p *Parser) ident() string {
val := p.sym.Value
if p.sym.Type != SymIdent {
p.invalid("identifier")
}
p.get()
return val
}
func (p *Parser) notice() string {
val := p.sym.Value
if p.sym.Type != SymNotice {
p.invalid("notice")
}
p.get()
return val
}
func (p *Parser) float() float64 {
val := p.sym.Value
f, err := strconv.ParseFloat(val, 64)
if err != nil || (p.sym.Type != SymFloat && p.sym.Type != SymInteger) {
p.invalid("float")
}
p.get()
return f
}
func (p *Parser) int() int {
val := p.sym.Value
i, err := strconv.Atoi(val)
if err != nil || p.sym.Type != SymInteger {
p.invalid("integer")
}
p.get()
return i
}
func (p *Parser) get() {
p.sym = p.lexer.Lex()
}
func (p *Parser) expect(symTypes ...SymType) {
expected := []string{}
for _, symType := range symTypes {
if p.sym.Type == symType {
p.get()
return
}
expected = append(expected, symType.String())
}
p.exitWith("expected >", strings.Join(expected, " or "), "< but got:", p.sym)
}
func (p *Parser) hasPreamble(s string) bool {
return p.sym.Type == SymPreamble && p.sym.Value == s
}
func (p *Parser) expectPreamble(s string) {
if p.hasPreamble(s) {
p.get()
} else {
p.exitWith("expected preamble >", s, "< but got:", p.sym)
}
}
/*---------------------*/
func (p *Parser) Parse() (r TDL) {
r = TDL{}
r.Lifts = p.Lifts()
if p.hasPreamble("Plates:") {
r.Plates = p.Plates()
}
if p.hasPreamble("SetTemplates:") {
r.SetTemplates = p.SetTemplates()
}
r.TrainingDays = p.TrainingDays()
p.expect(SymEOI)
return
}
func (p *Parser) Lifts() (r []Lift) {
p.expectPreamble("Lifts:")
r = append(r, p.Lift())
for p.sym.Type == SymComma {
p.get()
r = append(r, p.Lift())
}
return
}
func (p *Parser) Lift() (r Lift) {
r.Name = p.ident()
p.expectPreamble("Max:")
r.Max = p.float()
p.expectPreamble("Increment:")
r.Increment = p.float()
if p.sym.Type == SymPercent {
p.get()
r.IncrementPercent = true
}
if p.hasPreamble("Bar:") {
p.expectPreamble("Bar:")
r.Bar = p.float()
}
return
}
func (p *Parser) Plates() (r []Plate) {
p.expectPreamble("Plates:")
r = append(r, p.Plate())
for p.sym.Type == SymComma {
p.get()
r = append(r, p.Plate())
}
return r
}
func (p *Parser) Plate() (r Plate) {
r.Weight = p.float()
p.expect(SymTimes)
r.Amount = p.int()
return
}
func (p *Parser) SetTemplates() (r []SetTemplate) {
p.expectPreamble("SetTemplates:")
p.expect(SymDash)
r = append(r, p.SetTemplate())
for p.sym.Type == SymDash {
p.get()
r = append(r, p.SetTemplate())
}
return
}
func (p *Parser) SetTemplate() (r SetTemplate) {
if p.sym.Type == SymPreamble {
r.Name = strings.TrimSuffix(p.sym.Value, ":")
p.get()
} else {
p.expect(SymPreamble)
}
r.Items = append(r.Items, p.SetTemplateItem())
for p.sym.Type == SymComma {
p.get()
r.Items = append(r.Items, p.SetTemplateItem())
}
return
}
func (p *Parser) SetTemplateItem() (r SetTemplateItem) {
if p.sym.Type == SymIdent {
r.ReferenceName = p.ident()
} else {
r.Set = p.Set()
}
if p.sym.Type == SymTimes {
p.expect(SymTimes)
r.Amount = p.int()
}
return
}
func (p *Parser) Set() (r Set) {
r.Reps = p.int()
p.expect(SymReps)
p.expect(SymAt)
r.Percentage = p.float()
p.expect(SymPercent)
if p.sym.Type == SymPlus {
p.expect(SymPlus)
r.PlusWeight = p.float()
}
if p.sym.Type == SymTimes {
p.expect(SymTimes)
r.Amount = p.int()
}
if p.sym.Type == SymNotice {
r.Notice = p.notice()
}
return
}
func (p *Parser) TrainingDays() (r []TrainingDay) {
p.expectPreamble("TrainingDays:")
p.expect(SymDash)
r = append(r, p.TrainingDay())
for p.sym.Type == SymDash {
p.get()
r = append(r, p.TrainingDay())
}
return
}
func (p *Parser) TrainingDay() (r TrainingDay) {
for p.sym.Type != SymDash && p.sym.Type != SymEOI {
r.Items = append(r.Items, p.TrainingDayItem())
}
return
}
func (p *Parser) TrainingDayItem() (r TrainingDayItem) {
if p.sym.Type == SymString {
r.Raw = p.string()
} else {
r.LiftSchedule = p.LiftSchedule()
}
return
}
func (p *Parser) LiftSchedule() (r LiftSchedule) {
if p.sym.Type == SymPreamble {
r.LiftName = strings.TrimSuffix(p.sym.Value, ":")
p.get()
} else {
p.expect(SymPreamble)
}
r.Items = append(r.Items, p.SetTemplateItem())
for p.sym.Type == SymComma {
p.get()
r.Items = append(r.Items, p.SetTemplateItem())
}
if p.sym.Type == SymIncrease {
r.Increase = true
p.get()
if p.sym.Type == SymFloat || p.sym.Type == SymInteger {
r.IncreaseAmount = p.float()
if p.sym.Type == SymPercent {
r.IncreasePercent = true
}
}
}
return
}