2017-07-15 21:49:08 +02:00
|
|
|
package treerack
|
2017-06-25 17:51:08 +02:00
|
|
|
|
|
|
|
import (
|
2025-08-20 00:45:32 +02:00
|
|
|
"code.squareroundforest.org/arpio/treerack/self"
|
2017-06-25 17:51:08 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
)
|
|
|
|
|
2017-11-04 22:49:42 +01:00
|
|
|
// if min=0&&max=0, it means min=1,max=1
|
|
|
|
// else if max<=0, it means no max
|
|
|
|
// else if min<=0, it means no min
|
2017-06-25 23:38:32 +02:00
|
|
|
type SequenceItem struct {
|
|
|
|
Name string
|
2017-11-04 22:49:42 +01:00
|
|
|
Min, Max int
|
2017-06-25 23:38:32 +02:00
|
|
|
}
|
|
|
|
|
2017-06-25 17:51:08 +02:00
|
|
|
type Syntax struct {
|
2025-08-20 00:45:32 +02:00
|
|
|
registry *registry
|
|
|
|
initialized bool
|
|
|
|
errInitFailed error
|
|
|
|
explicitRoot bool
|
|
|
|
keywords []definition
|
|
|
|
root definition
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
type GeneratorOptions struct {
|
|
|
|
PackageName string
|
2018-01-07 01:45:56 +01:00
|
|
|
Export bool
|
2018-01-05 19:06:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// applied in a non-type-checked way
|
|
|
|
type generator interface {
|
|
|
|
generate(io.Writer, map[string]bool) error
|
|
|
|
}
|
|
|
|
|
2017-11-01 03:54:53 +01:00
|
|
|
type definition interface {
|
|
|
|
nodeName() string
|
2017-11-05 03:28:36 +01:00
|
|
|
setName(string)
|
2017-11-01 03:54:53 +01:00
|
|
|
nodeID() int
|
2017-11-05 03:28:36 +01:00
|
|
|
setID(int)
|
2017-11-01 03:54:53 +01:00
|
|
|
commitType() CommitType
|
|
|
|
setCommitType(CommitType)
|
2017-11-04 22:49:42 +01:00
|
|
|
preinit()
|
2017-11-01 03:54:53 +01:00
|
|
|
validate(*registry) error
|
|
|
|
init(*registry)
|
2017-11-02 22:19:03 +01:00
|
|
|
addGeneralization(int)
|
2017-11-01 03:54:53 +01:00
|
|
|
parser() parser
|
|
|
|
builder() builder
|
2017-11-26 01:49:22 +01:00
|
|
|
format(*registry, formatFlags) string
|
2017-11-01 03:54:53 +01:00
|
|
|
}
|
|
|
|
|
2017-06-25 17:51:08 +02:00
|
|
|
var (
|
2018-01-05 19:06:10 +01:00
|
|
|
ErrSyntaxInitialized = errors.New("syntax initialized")
|
|
|
|
ErrNoParsersDefined = errors.New("no parsers defined")
|
|
|
|
ErrInvalidEscapeCharacter = errors.New("invalid escape character")
|
|
|
|
ErrMultipleRoots = errors.New("multiple roots")
|
|
|
|
ErrInvalidSymbolName = errors.New("invalid symbol name")
|
2017-06-25 17:51:08 +02:00
|
|
|
)
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
func (ct CommitType) String() string {
|
|
|
|
switch ct {
|
|
|
|
case None:
|
|
|
|
return "none"
|
|
|
|
case Alias:
|
|
|
|
return "alias"
|
|
|
|
case Whitespace:
|
|
|
|
return "whitespace"
|
|
|
|
case NoWhitespace:
|
|
|
|
return "no-whitespace"
|
|
|
|
case Keyword:
|
|
|
|
return "keyword"
|
|
|
|
case NoKeyword:
|
|
|
|
return "no-keyword"
|
|
|
|
case FailPass:
|
|
|
|
return "fail-pass"
|
|
|
|
case Root:
|
|
|
|
return "root"
|
|
|
|
default:
|
|
|
|
return "unknown"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 03:54:53 +01:00
|
|
|
func duplicateDefinition(name string) error {
|
|
|
|
return fmt.Errorf("duplicate definition: %s", name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func parserNotFound(name string) error {
|
|
|
|
return fmt.Errorf("parser not found: %s", name)
|
|
|
|
}
|
|
|
|
|
2018-01-06 21:30:07 +01:00
|
|
|
var symbolChars = []rune("\\ \n\t\b\f\r\v/.[]\"{}^+*?|():=;")
|
2017-10-31 21:53:09 +01:00
|
|
|
|
|
|
|
func isValidSymbol(n string) bool {
|
|
|
|
runes := []rune(n)
|
|
|
|
for _, r := range runes {
|
2018-01-06 21:30:07 +01:00
|
|
|
if !matchChar(symbolChars, nil, true, r) {
|
2017-10-31 21:53:09 +01:00
|
|
|
return false
|
|
|
|
}
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
2017-10-31 21:53:09 +01:00
|
|
|
|
|
|
|
return true
|
|
|
|
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
// func (pe *ParseError) Verbose() string {
|
|
|
|
// return ""
|
|
|
|
// }
|
|
|
|
|
2017-11-01 03:54:53 +01:00
|
|
|
func intsContain(is []int, i int) bool {
|
|
|
|
for _, ii := range is {
|
|
|
|
if ii == i {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
var incompatibleCommitTypes = map[CommitType][]CommitType{
|
|
|
|
Alias: {Root},
|
|
|
|
Whitespace: {Keyword, NoKeyword, FailPass, Root},
|
|
|
|
Keyword: {NoKeyword, Root},
|
|
|
|
FailPass: {Root},
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Syntax) checkCommitType(d definition) error {
|
|
|
|
for ct, ict := range incompatibleCommitTypes {
|
|
|
|
if d.commitType()&ct == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, cti := range ict {
|
|
|
|
if d.commitType()&cti == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Errorf(
|
|
|
|
"incompatible commit types in %s: %v and %v",
|
|
|
|
d.nodeName(),
|
|
|
|
ct,
|
|
|
|
cti,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-11-01 00:19:29 +01:00
|
|
|
func (s *Syntax) applyRoot(d definition) error {
|
|
|
|
explicitRoot := d.commitType()&Root != 0
|
|
|
|
if explicitRoot && s.explicitRoot {
|
|
|
|
return ErrMultipleRoots
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
2017-11-01 00:19:29 +01:00
|
|
|
if s.root != nil && (explicitRoot || !s.explicitRoot) {
|
|
|
|
s.root.setCommitType(s.root.commitType() &^ Root)
|
2017-10-31 21:53:09 +01:00
|
|
|
}
|
|
|
|
|
2017-11-01 00:19:29 +01:00
|
|
|
if explicitRoot || !s.explicitRoot {
|
2017-06-25 17:51:08 +02:00
|
|
|
s.root = d
|
2017-10-28 22:54:15 +02:00
|
|
|
s.root.setCommitType(s.root.commitType() | Root)
|
2017-11-01 00:19:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if explicitRoot {
|
2017-07-15 21:49:08 +02:00
|
|
|
s.explicitRoot = true
|
2017-11-01 00:19:29 +01:00
|
|
|
}
|
2017-10-28 22:54:15 +02:00
|
|
|
|
2017-11-01 00:19:29 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Syntax) register(d definition) error {
|
|
|
|
if s.initialized {
|
|
|
|
return ErrSyntaxInitialized
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.registry == nil {
|
|
|
|
s.registry = newRegistry()
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
if err := s.checkCommitType(d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-01 00:19:29 +01:00
|
|
|
if err := s.applyRoot(d); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-10-28 22:54:15 +02:00
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
if d.commitType()&Keyword != 0 {
|
|
|
|
s.keywords = append(s.keywords, d)
|
|
|
|
}
|
|
|
|
|
2017-06-25 17:51:08 +02:00
|
|
|
return s.registry.setDefinition(d)
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
func (s *Syntax) anyChar(name string, ct CommitType) error {
|
|
|
|
return s.class(name, ct, true, nil, nil)
|
|
|
|
}
|
|
|
|
|
2017-06-25 17:51:08 +02:00
|
|
|
func (s *Syntax) AnyChar(name string, ct CommitType) error {
|
2017-10-31 21:53:09 +01:00
|
|
|
if !isValidSymbol(name) {
|
|
|
|
return ErrInvalidSymbolName
|
|
|
|
}
|
|
|
|
|
2017-11-25 17:37:05 +01:00
|
|
|
return s.anyChar(name, ct|userDefined)
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func childName(name string, childIndex int) string {
|
|
|
|
return fmt.Sprintf("%s:%d", name, childIndex)
|
|
|
|
}
|
|
|
|
|
2017-10-29 16:46:17 +01:00
|
|
|
func namesToSequenceItems(n []string) []SequenceItem {
|
|
|
|
si := make([]SequenceItem, len(n))
|
|
|
|
for i := range n {
|
|
|
|
si[i] = SequenceItem{Name: n[i]}
|
|
|
|
}
|
|
|
|
|
|
|
|
return si
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
func (s *Syntax) class(name string, ct CommitType, not bool, chars []rune, ranges [][]rune) error {
|
2017-07-17 01:41:38 +02:00
|
|
|
cname := childName(name, 0)
|
2017-07-29 16:25:17 +02:00
|
|
|
if err := s.register(newChar(cname, not, chars, ranges)); err != nil {
|
2017-07-17 01:41:38 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
return s.sequence(name, ct, SequenceItem{Name: cname})
|
2017-07-17 01:41:38 +02:00
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
func (s *Syntax) Class(name string, ct CommitType, not bool, chars []rune, ranges [][]rune) error {
|
|
|
|
if !isValidSymbol(name) {
|
|
|
|
return ErrInvalidSymbolName
|
|
|
|
}
|
|
|
|
|
2017-11-25 17:37:05 +01:00
|
|
|
return s.class(name, ct|userDefined, not, chars, ranges)
|
2017-10-31 21:53:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Syntax) charSequence(name string, ct CommitType, chars []rune) error {
|
2017-06-25 17:51:08 +02:00
|
|
|
var refs []string
|
|
|
|
for i, ci := range chars {
|
|
|
|
ref := childName(name, i)
|
|
|
|
refs = append(refs, ref)
|
2017-07-29 16:25:17 +02:00
|
|
|
if err := s.register(newChar(ref, false, []rune{ci}, nil)); err != nil {
|
2017-06-25 17:51:08 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
return s.sequence(name, ct|NoWhitespace, namesToSequenceItems(refs)...)
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
func (s *Syntax) CharSequence(name string, ct CommitType, chars []rune) error {
|
|
|
|
if !isValidSymbol(name) {
|
|
|
|
return ErrInvalidSymbolName
|
|
|
|
}
|
|
|
|
|
2017-11-25 17:37:05 +01:00
|
|
|
return s.charSequence(name, ct|userDefined, chars)
|
2017-10-31 21:53:09 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Syntax) sequence(name string, ct CommitType, items ...SequenceItem) error {
|
2017-06-25 17:51:08 +02:00
|
|
|
return s.register(newSequence(name, ct, items))
|
|
|
|
}
|
|
|
|
|
2017-10-31 21:53:09 +01:00
|
|
|
func (s *Syntax) Sequence(name string, ct CommitType, items ...SequenceItem) error {
|
|
|
|
if !isValidSymbol(name) {
|
|
|
|
return ErrInvalidSymbolName
|
|
|
|
}
|
|
|
|
|
2017-11-25 17:37:05 +01:00
|
|
|
return s.sequence(name, ct|userDefined, items...)
|
2017-10-31 21:53:09 +01:00
|
|
|
}
|
|
|
|
|
2017-11-02 22:19:03 +01:00
|
|
|
func (s *Syntax) choice(name string, ct CommitType, options ...string) error {
|
|
|
|
return s.register(newChoice(name, ct, options))
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
2017-11-02 22:19:03 +01:00
|
|
|
func (s *Syntax) Choice(name string, ct CommitType, options ...string) error {
|
2017-10-31 21:53:09 +01:00
|
|
|
if !isValidSymbol(name) {
|
|
|
|
return ErrInvalidSymbolName
|
|
|
|
}
|
|
|
|
|
2017-11-25 17:37:05 +01:00
|
|
|
return s.choice(name, ct|userDefined, options...)
|
2017-10-31 21:53:09 +01:00
|
|
|
}
|
|
|
|
|
2018-01-04 18:36:59 +01:00
|
|
|
func (s *Syntax) ReadSyntax(r io.Reader) error {
|
2017-06-25 17:51:08 +02:00
|
|
|
if s.initialized {
|
|
|
|
return ErrSyntaxInitialized
|
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
sn, err := self.Parse(r)
|
2018-01-04 18:36:59 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
n := mapSelfNode(sn)
|
2018-01-04 18:36:59 +01:00
|
|
|
return define(s, n)
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Syntax) Init() error {
|
2025-08-20 00:45:32 +02:00
|
|
|
if s.errInitFailed != nil {
|
|
|
|
return s.errInitFailed
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if s.initialized {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.root == nil {
|
|
|
|
return ErrNoParsersDefined
|
|
|
|
}
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
if err := s.checkCommitType(s.root); err != nil {
|
|
|
|
return err
|
2017-12-31 16:14:56 +01:00
|
|
|
}
|
|
|
|
|
2018-01-09 03:53:20 +01:00
|
|
|
defs := s.registry.definitions
|
2019-02-02 18:07:10 +01:00
|
|
|
for i := range defs {
|
2017-11-04 22:49:42 +01:00
|
|
|
defs[i].preinit()
|
|
|
|
}
|
2017-10-28 22:54:15 +02:00
|
|
|
|
2017-11-01 02:43:46 +01:00
|
|
|
if hasWhitespace(defs) {
|
|
|
|
defs, s.root = applyWhitespace(defs)
|
|
|
|
s.registry = newRegistry(defs...)
|
2017-08-06 20:43:52 +02:00
|
|
|
}
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
for i := range s.keywords {
|
|
|
|
if err := s.keywords[i].validate(s.registry); err != nil {
|
2025-08-20 00:45:32 +02:00
|
|
|
s.errInitFailed = err
|
2019-02-02 18:07:10 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 02:43:46 +01:00
|
|
|
if err := s.root.validate(s.registry); err != nil {
|
2025-08-20 00:45:32 +02:00
|
|
|
s.errInitFailed = err
|
2017-06-25 17:51:08 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
for i := range s.keywords {
|
|
|
|
s.keywords[i].init(s.registry)
|
|
|
|
}
|
2017-11-01 02:43:46 +01:00
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
s.root.init(s.registry)
|
2017-06-25 17:51:08 +02:00
|
|
|
s.initialized = true
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-02-02 21:27:01 +01:00
|
|
|
func (s *Syntax) keywordParsers() []parser {
|
|
|
|
var p []parser
|
|
|
|
for _, kw := range s.keywords {
|
|
|
|
p = append(p, kw.parser())
|
|
|
|
}
|
|
|
|
|
|
|
|
return p
|
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
func (s *Syntax) Generate(o GeneratorOptions, w io.Writer) error {
|
2017-06-25 17:51:08 +02:00
|
|
|
if err := s.Init(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
if o.PackageName == "" {
|
|
|
|
o.PackageName = "main"
|
|
|
|
}
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
var err error
|
|
|
|
fprintf := func(f string, args ...interface{}) {
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
_, err = fmt.Fprintf(w, f, args...)
|
2018-01-04 18:36:59 +01:00
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
fprint := func(args ...interface{}) {
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = fmt.Fprint(w, args...)
|
2018-01-04 18:36:59 +01:00
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintln := func() {
|
|
|
|
fprint("\n")
|
2018-01-04 18:36:59 +01:00
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
fprint(gendoc)
|
|
|
|
fprintln()
|
|
|
|
fprintln()
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintf("package %s", o.PackageName)
|
|
|
|
fprintln()
|
|
|
|
fprintln()
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
// generate headCode with scripts/createhead.go
|
2025-08-20 03:22:39 +02:00
|
|
|
hc := headCode
|
|
|
|
if o.Export {
|
|
|
|
hc = headCodeExported
|
|
|
|
}
|
|
|
|
|
2025-08-20 03:44:05 +02:00
|
|
|
fprint("// head")
|
|
|
|
fprintln()
|
2025-08-20 03:22:39 +02:00
|
|
|
fprint(hc)
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintln()
|
2025-08-20 03:44:05 +02:00
|
|
|
fprint("// eo head")
|
|
|
|
fprintln()
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintln()
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-07 01:45:56 +01:00
|
|
|
if o.Export {
|
|
|
|
fprint(`func Parse(r io.Reader) (*Node, error) {`)
|
|
|
|
} else {
|
2025-08-20 03:30:46 +02:00
|
|
|
fprint(`func parse(r io.Reader) (*node, error) {`)
|
2018-01-07 01:45:56 +01:00
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintln()
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
done := make(map[string]bool)
|
2019-02-02 21:27:01 +01:00
|
|
|
for _, p := range s.keywordParsers() {
|
|
|
|
if err := p.(generator).generate(w, done); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fprintln()
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
if err := s.root.parser().(generator).generate(w, done); err != nil {
|
2018-01-05 19:06:10 +01:00
|
|
|
return err
|
|
|
|
}
|
2018-01-04 18:36:59 +01:00
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
done = make(map[string]bool)
|
2019-02-02 18:07:10 +01:00
|
|
|
if err := s.root.builder().(generator).generate(w, done); err != nil {
|
2018-01-04 18:36:59 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintln()
|
|
|
|
fprintln()
|
2019-02-02 18:07:10 +01:00
|
|
|
fprint(`var keywords = []parser{`)
|
|
|
|
for i := range s.keywords {
|
|
|
|
fprintf(`&p%d, `, s.keywords[i].nodeID())
|
|
|
|
}
|
|
|
|
fprint(`}`)
|
|
|
|
|
|
|
|
fprintln()
|
|
|
|
fprintln()
|
|
|
|
fprintf(`return parseInput(r, &p%d, &b%d, keywords)`, s.root.parser().nodeID(), s.root.builder().nodeID())
|
2018-01-05 19:06:10 +01:00
|
|
|
fprintln()
|
|
|
|
fprint(`}`)
|
|
|
|
fprintln()
|
|
|
|
|
2018-01-04 18:36:59 +01:00
|
|
|
return nil
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s *Syntax) Parse(r io.Reader) (*Node, error) {
|
|
|
|
if err := s.Init(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-02-02 18:07:10 +01:00
|
|
|
return parseInput(r, s.root.parser(), s.root.builder(), s.keywordParsers())
|
2017-06-25 17:51:08 +02:00
|
|
|
}
|