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 }