Add example of using grammar
This commit is contained in:
parent
1a13c79ee4
commit
9139d4830d
163
gg/grammar/example_test.go
Normal file
163
gg/grammar/example_test.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
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