Add example of using grammar
This commit is contained in:
parent
1a13c79ee4
commit
06cb2f44e3
164
gg/grammar/example_test.go
Normal file
164
gg/grammar/example_test.go
Normal file
@ -0,0 +1,164 @@
|
||||
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:
|
||||
|
||||
```
|
||||
<digit> ::= "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
|
||||
<positive-number> ::= <digit>+
|
||||
<negative-number> ::= "-" <positive-number>
|
||||
<number> ::= <negative-number> | <positive-number>
|
||||
|
||||
<list-el-tail> ::= <element> <list-tail>
|
||||
<list-tail> ::= ")" | "," <list-el-tail>
|
||||
<list-head> ::= ")" | <list-el-tail>
|
||||
<list> ::= "(" <list-head>
|
||||
|
||||
<element> ::= <number> | <list>
|
||||
```
|
||||
|
||||
*/
|
||||
|
||||
// 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:
|
||||
// () <nil>
|
||||
// (1,(2,-3),4) <nil>
|
||||
// 1:16: expected ')' or number or list
|
||||
}
|
Loading…
Reference in New Issue
Block a user