parent
1a13c79ee4
commit
06cb2f44e3
@ -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