package grammar_test import ( "bytes" "fmt" "strconv" "strings" "code.betamike.com/mediocregopher/ginger/gg/grammar" "golang.org/x/exp/slices" ) /* This example demonstrates how to describe the following EBNF using the grammar package: ``` ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ::= + ::= "-" ::= | ::= ::= ")" | "," ::= ")" | ::= "(" ::= | ``` */ // Element represents an element of a list, which can be either a number or a // sub-list. type Element struct { Number int64 List []Element } func (e Element) String() string { if e.List != nil { listElStrs := make([]string, len(e.List)) for i := range e.List { listElStrs[i] = e.List[i].String() } return fmt.Sprintf("(%s)", strings.Join(listElStrs, ",")) } return fmt.Sprint(e.Number) } func s(str string) fmt.Stringer { return grammar.Stringer{S: str} } var ( digit = grammar.RuneFunc( s("digit"), func(r rune) bool { return '0' <= r && r <= '9' }, ) positiveNumber = grammar.StringFromRunes(grammar.OneOrMore(digit)) negativeNumber = grammar.Reduction( s("negative-number"), grammar.Rune('-'), positiveNumber, func( neg grammar.Located[rune], posNum grammar.Located[string], ) grammar.Located[string] { return grammar.Locate(neg.Location, string(neg.Value)+posNum.Value) }, ) number = grammar.Mapping( s("number"), grammar.FirstOf(negativeNumber, positiveNumber), func(str grammar.Located[string]) Element { i, err := strconv.ParseInt(str.Value, 10, 64) if err != nil { panic(fmt.Errorf("parsing %q as int: %w", str, err)) } return Element{Number: i} }, ) // Because the list/element definitions are recursive it requires using // SymbolPtrs, which is easier to do via a global initialization function // like this. list = func() grammar.Symbol[Element] { type listState []Element var ( listTail = new(grammar.SymbolPtr[listState]) list = new(grammar.SymbolPtr[Element]) element = new(grammar.SymbolPtr[Element]) // Right parenthesis indicates the end of a list, at which point we // can initialize the state which gets returned down the stack. We // pass rightParen as the Stringer because we want that to still be // the string identifier for this branch of the symbol, for error // purposes. rightParen = grammar.Rune(')') listTerm = grammar.Mapping( rightParen, rightParen, func(grammar.Located[rune]) listState { return listState{} }, ) listElTail = grammar.Reduction[Element, listState, listState]( element, // Stringer element, listTail, func(el Element, ls listState) listState { ls = append(ls, el) return ls }, ) listHead = grammar.FirstOf(listTerm, listElTail) ) listTail.Symbol = grammar.FirstOf( listTerm, grammar.Prefixed(grammar.Rune(','), listElTail), ) list.Symbol = grammar.Reduction[ grammar.Located[rune], listState, Element, ]( s("list"), grammar.Rune('('), listHead, func(_ grammar.Located[rune], ls listState) Element { slices.Reverse(ls) return Element{List: []Element(ls)} }, ) element.Symbol = grammar.FirstOf[Element](number, list) return list }() ) func Example() { r := grammar.NewReader(bytes.NewBufferString( `()` + `(1,(2,-3),4)` + `(ERROR`, )) l1, err := list.Decode(r) fmt.Println(l1, err) l2, err := list.Decode(r) fmt.Println(l2, err) _, err = list.Decode(r) fmt.Println(err) // Output: // () // (1,(2,-3),4) // 1:16: expected ')' or number or list }