ginger/gg/grammar/example_test.go

155 lines
3.5 KiB
Go
Raw Normal View History

2023-10-28 15:06:53 +00:00
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>
2023-10-29 09:20:37 +00:00
<list-tail> ::= ")" | "," <list-el-tail>
<list-head> ::= ")" | <list-el-tail>
<list> ::= "(" <list-head>
2023-10-28 15:06:53 +00:00
<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)
}
var (
digit = grammar.RuneFunc(
2023-10-29 09:20:37 +00:00
"digit", func(r rune) bool { return '0' <= r && r <= '9' },
2023-10-28 15:06:53 +00:00
)
positiveNumber = grammar.StringFromRunes(grammar.OneOrMore(digit))
negativeNumber = grammar.Reduction(
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)
},
)
2023-10-29 09:20:37 +00:00
number = grammar.Named(
"number",
grammar.Mapping(
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}
},
),
2023-10-28 15:06:53 +00:00
)
// 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
2023-10-29 09:20:37 +00:00
// can initialize the state which gets returned down the stack.
listTerm = grammar.Mapping(
grammar.Rune(')'),
2023-10-28 15:06:53 +00:00
func(grammar.Located[rune]) listState { return listState{} },
)
listElTail = grammar.Reduction[Element, listState, listState](
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),
)
2023-10-29 09:20:37 +00:00
list.Symbol = grammar.Named(
"list",
grammar.Reduction[grammar.Located[rune], listState, Element](
grammar.Rune('('),
listHead,
func(_ grammar.Located[rune], ls listState) Element {
slices.Reverse(ls)
return Element{List: []Element(ls)}
},
),
2023-10-28 15:06:53 +00:00
)
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
}