155 lines
3.5 KiB
Go
155 lines
3.5 KiB
Go
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)
|
|
}
|
|
|
|
var (
|
|
digit = grammar.RuneFunc(
|
|
"digit", func(r rune) bool { return '0' <= r && r <= '9' },
|
|
)
|
|
|
|
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)
|
|
},
|
|
)
|
|
|
|
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}
|
|
},
|
|
),
|
|
)
|
|
|
|
// 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.
|
|
listTerm = grammar.Mapping(
|
|
grammar.Rune(')'),
|
|
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),
|
|
)
|
|
|
|
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)}
|
|
},
|
|
),
|
|
)
|
|
|
|
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
|
|
}
|