diff options
| -rw-r--r-- | README.md | 431 | ||||
| -rw-r--r-- | config.ebnf | 30 | ||||
| -rw-r--r-- | example/file.conf | 33 | ||||
| -rw-r--r-- | example/file2.conf | 7 | ||||
| -rw-r--r-- | example/main.go | 27 | ||||
| -rw-r--r-- | gen.go | 2 | ||||
| -rw-r--r-- | go.mod | 12 | ||||
| -rw-r--r-- | go.sum | 8 | ||||
| -rw-r--r-- | internal/ast/processor.go | 119 | ||||
| -rw-r--r-- | internal/ast/tree.go | 43 | ||||
| -rw-r--r-- | internal/parser/parser.go | 1803 | ||||
| -rw-r--r-- | loader.go | 40 | ||||
| -rw-r--r-- | loader_test.go | 64 | ||||
| -rw-r--r-- | model/body.go | 28 | ||||
| -rw-r--r-- | model/directive.go | 9 | ||||
| -rw-r--r-- | model/lookup.go | 56 | ||||
| -rw-r--r-- | model/model.go | 44 | ||||
| -rw-r--r-- | model/setting.go | 6 | ||||
| -rw-r--r-- | model/value.go | 85 | ||||
| -rw-r--r-- | model/visitor.go | 6 | ||||
| -rw-r--r-- | parser/errors.go | 107 | ||||
| -rw-r--r-- | parser/grammar.peg | 126 | ||||
| -rw-r--r-- | parser/parser.go | 2767 | ||||
| -rw-r--r-- | visitor/default.go | 63 |
24 files changed, 3428 insertions, 2488 deletions
@@ -1,6 +1,9 @@ # conf -Go библиотека для парсинга конфигурационных файлов `.conf`. +Go библиотека для парсинга конфигурационных файлов `.conf` похожем на +классические UNIX конфиги, как у nginx или bind9. + +[English version](#english) ## Установка @@ -10,13 +13,12 @@ go get go.neonxp.ru/conf ## Особенности формата -- **Присваивания**: `key = value;` -- **Типы значений**: строки (двойные/одинарные кавычки, backticks), числа (целые/дробные), булевы значения -- **Директивы**: `directive arg1 arg2;` -- **Блочные директивы**: `directive { ... }` +- **Команды**: `directive arg1 arg2;` +- **Команды с телом**: `directive arg1 arg2 { ... }` +- **Типы аргументов**: строки (двойные/одинарные кавычки/backticks для многострочных строк), числа (целые/дробные), булевы значения +- **Вложенные блоки**: произвольная глубина вложенности - **Комментарии**: `#` до конца строки - **UTF-8**: включая кириллицу -- **Подстановка переменных окружения**: `$VAR` ## Быстрый старт @@ -26,68 +28,64 @@ package main import ( "fmt" "go.neonxp.ru/conf" - "go.neonxp.ru/conf/model" - "go.neonxp.ru/conf/visitor" ) func main() { - cfg := conf.New() - if err := cfg.LoadFile("config.conf"); err != nil { - panic(err) - } - - v := visitor.NewDefault() - if err := cfg.Process(v); err != nil { + // Загрузка из файла + cfg, err := conf.LoadFile("config.conf") + if err != nil { panic(err) } - // Доступ по пути с точечной нотацией - val, err := v.Get("server.host") - if err != nil { - panic(err) + // Получение команды и её значения + if hostCmd := cfg.Get("server"); hostCmd != nil { + fmt.Printf("Server: %v\n", hostCmd.Value()) } - fmt.Println(val.String()) // localhost - port, err := v.Get("server.port") - fmt.Println(port.Int()) // 8080 + // Навигация по вложенной структуре + sslEnabled := cfg.Get("server").Group.Get("ssl").Group.Get("enabled") + fmt.Printf("SSL enabled: %v\n", sslEnabled.Value()) } ``` ## Пример конфигурационного файла ```conf -# Переменная окружения: db_file = $HOME "/app/data.db"; - -# Простые присваивания -rss = "https://neonxp.ru/feed/"; -host = "localhost"; -port = 8080; -debug = true; - -# Директивы с аргументами -telegram "bot123" "-1003888840756" { - token = "token_value"; - admin_chat = "@admin"; -} +# Простые команды без тела +listen 8080; +host "127.0.0.1"; +debug false; -# Вложенные блоки -server { - host = "localhost"; - port = 8080; +# Команды с аргументами и телом +server "web" { + host "localhost"; + port 8080; ssl { - enabled = true; - cert = "/etc/ssl/cert.pem"; + enabled true; + cert "/etc/ssl/cert.pem"; + key "/etc/ssl/key.pem"; } middleware "auth" { - enabled = true; - secret = "$JWT_SECRET"; + enabled true; + secret "secret123"; } } +# Несколько команд с одинаковым именем +cache "redis" { + host "redis.local"; + port 6379; +} + +cache "memcached" { + host "memcached.local"; + port 11211; +} + # Многострочные строки -template = ` +template ` <!DOCTYPE html> <html> <body>Hello</body> @@ -100,93 +98,106 @@ template = ` ### Загрузка конфигурации ```go -cfg := conf.New() - // Из файла -cfg.LoadFile("config.conf") +cfg, err := conf.LoadFile("path/to/config.conf") // Из памяти -cfg.Load("inline", []byte("key = value;")) +cfg, err := conf.Load("inline", []byte("listen 8080;")) ``` -### Обработка через Visitor - -Библиотека использует паттерн Visitor для обхода конфигурации: +### Типы данных ```go -type Visitor interface { - VisitDirective(ident string, args Values, body Body) error - VisitSetting(key string, values Values) error +// model.Ident — идентификатор команды (псевдоним для string) +ident := model.Ident("server") + +// model.Command — команда с именем, аргументами и вложенной группой +type Command struct { + Name Ident // Имя команды + Args []any // Аргументы команды + Group Group // Вложенные команды (тело команды) } + +// model.Group — срез команд +type Group []Command ``` -### Get-методы на Values +### Методы Command -| Метод | Описание | -|-------|----------| -| `String()` | Строковое представление через пробел | -| `Int()` | Преобразование в int (одно значение) | -| `BuildString(lookups...)` | Сборка строки с подстановками | +| Метод | Описание | +| ------------- | -------------------------------------------- | +| `Value() any` | Возвращает первый аргумент команды или `nil` | -### Подстановка переменных окружения +### Методы Group -```go -vals, _ := v.Get("db_file") -path := vals.BuildString(model.LookupEnv) -// $HOME → "/home/user", результат: "/home/user/app/data.db" -``` +| Метод | Описание | +| ---------------------------------------------------------- | ---------------------------------------------------------- | +| `Get(name Ident) *Command` | Возвращает первую команду с указанным именем или `nil` | +| `Filter(predicate func(*Command) bool) iter.Seq[*Command]` | Возвращает итератор по командам, удовлетворяющим предикату | -### Кастомные подстановки +## Навигация по конфигурации ```go -substitutions := map[model.Word]string{ - "APP_DIR": "/opt/myapp", - "LOG_LEVEL": "debug", +// Простой доступ +listenCmd := cfg.Get("listen") +fmt.Println(listenCmd.Value()) // 8080 + +// Цепочка вложенной навигации +port := cfg.Get("server").Group.Get("http").Group.Get("port") +fmt.Println(port.Value()) // 8080 + +// Использование Filter +for cmd := range cfg.Filter(func(c *model.Command) bool { + return c.Name == "cache" +}) { + fmt.Printf("Cache: %v\n", cmd.Value()) } - -path := vals.BuildString(model.LookupSubst(substitutions), model.Origin) ``` -## Реализация собственного Visitor +## Работа с аргументами -```go -type MyVisitor struct{} +Аргументы сохраняются в срез `Args` типа `[]any`. Возможные типы: -func (m *MyVisitor) VisitDirective(ident string, args model.Values, body model.Body) error { - fmt.Printf("Directive: %s, args: %s\n", ident, args.String()) - return body.Execute(m) // Рекурсивный обход тела -} +```go +// Строка в двойных кавычках: "value" +// Строка в одинарных кавычках: 'value' +// Строка в backticks: `value` +// Число целое: 42 +// Число дробное: 3.14 +// Булево значение: true, false + +// Пример доступа к аргументам: +cmd := cfg.Get("test") +if cmd != nil && len(cmd.Args) > 0 { + val1 := cmd.Args[0] // Первый аргумент + val2 := cmd.Args[1] // Второй аргумент + + // Приведение типа для строк + if str, ok := val1.(string); ok { + fmt.Println("String:", str) + } -func (m *MyVisitor) VisitSetting(key string, values model.Values) error { - fmt.Printf("Setting: %s = %s\n", key, values.String()) - return nil + // Приведение типа для чисел + if num, ok := val2.(int); ok { + fmt.Println("Int:", num) + } } ``` -## Грамматика (EBNF) +## Грамматика (PEG) -``` -Config = Doc . -Doc = Stmt { Stmt } . -Stmt = Word ( Assignment | Command ) . +Формат использует PEG (Parsing Expression Grammar). Грамматика описана в файле `parser/grammar.peg`. -Assignment = "=" Values br . -Command = [Values] ( Body | br ) . +Основные правила: -Values = Value { Value } . -Value = Word | String | Number | Boolean . -Body = "{" [ Doc ] "}" . - -Word = word (alpha | "$" | "_") {alpha | number | "$" | "_"} . -String = `"[^"]*"` | `'[^']*'` | '`' { `[^`]' } '`' . -Number = `-?[0-9]+(\.[0-9]+)?` . -Boolean = `true` | `false` . -br = ";" . -``` +- Все команды без тела заканчиваются точкой с запятой `;` +- Тело команды заключается в фигурные скобки `{ }` +- Аргументы разделяются пробелами +- Комментарии начинаются с `#` ## Требования -- Go 1.25+ +- Go 1.23+ (для использования `iter.Seq`) ## Лицензия @@ -205,3 +216,223 @@ br = ";" . ## Автор - Александр Кирюхин <i@neonxp.ru> + +--- + +<a name="english"></a> + +# conf (English) + +Go library for parsing `.conf` configuration files (like many classic UNIX programms like nginx or bind9). + +## Installation + +```bash +go get go.neonxp.ru/conf +``` + +## Format Features + +- **Commands**: `directive arg1 arg2;` +- **Commands with body**: `directive arg1 arg2 { ... }` +- **Argument types**: strings (double/single quotes, backticks for multiline strings), numbers (integer/float), boolean values +- **Nested blocks**: arbitrary nesting depth +- **Comments**: `#` until end of line +- **UTF-8**: including Cyrillic + +## Quick Start + +```go +package main + +import ( + "fmt" + "go.neonxp.ru/conf" +) + +func main() { + // Load from file + cfg, err := conf.LoadFile("config.conf") + if err != nil { + panic(err) + } + + // Get command and its value + if hostCmd := cfg.Get("server"); hostCmd != nil { + fmt.Printf("Server: %v\n", hostCmd.Value()) + } + + // Navigate through nested structure + sslEnabled := cfg.Get("server").Group.Get("ssl").Group.Get("enabled") + fmt.Printf("SSL enabled: %v\n", sslEnabled.Value()) +} +``` + +## Example Configuration File + +```conf +# Simple commands without body +listen 8080; +host "127.0.0.1"; +debug false; + +# Commands with arguments and body +server "web" { + host "localhost"; + port 8080; + + ssl { + enabled true; + cert "/etc/ssl/cert.pem"; + key "/etc/ssl/key.pem"; + } + + middleware "auth" { + enabled true; + secret "secret123"; + } +} + +# Multiple commands with same name +cache "redis" { + host "redis.local"; + port 6379; +} + +cache "memcached" { + host "memcached.local"; + port 11211; +} + +# Multiline strings +template ` + <!DOCTYPE html> + <html> + <body>Hello</body> + </html> +`; +``` + +## API + +### Loading Configuration + +```go +// From file +cfg, err := conf.LoadFile("path/to/config.conf") + +// From memory +cfg, err := conf.Load("inline", []byte("listen 8080;")) +``` + +### Data Types + +```go +// model.Ident — command identifier (alias for string) +ident := model.Ident("server") + +// model.Command — command with name, arguments and nested group +type Command struct { + Name Ident // Command name + Args []any // Command arguments + Group Group // Nested commands (command body) +} + +// model.Group — slice of commands +type Group []Command +``` + +### Command Methods + +| Method | Description | +| ------------- | ------------------------------------------ | +| `Value() any` | Returns first argument of command or `nil` | + +### Group Methods + +| Method | Description | +| ---------------------------------------------------------- | -------------------------------------------------- | +| `Get(name Ident) *Command` | Returns first command with specified name or `nil` | +| `Filter(predicate func(*Command) bool) iter.Seq[*Command]` | Returns iterator over commands matching predicate | + +## Configuration Navigation + +```go +// Simple access +listenCmd := cfg.Get("listen") +fmt.Println(listenCmd.Value()) // 8080 + +// Nested navigation chain +port := cfg.Get("server").Group.Get("http").Group.Get("port") +fmt.Println(port.Value()) // 8080 + +// Using Filter +for cmd := range cfg.Filter(func(c *model.Command) bool { + return c.Name == "cache" +}) { + fmt.Printf("Cache: %v\n", cmd.Value()) +} +``` + +## Working with Arguments + +Arguments are stored in `Args` slice of type `[]any`. Possible types: + +```go +// Double-quoted string: "value" +// Single-quoted string: 'value' +// Backtick string: `value` +// Integer number: 42 +// Float number: 3.14 +// Boolean value: true, false + +// Example accessing arguments: +cmd := cfg.Get("test") +if cmd != nil && len(cmd.Args) > 0 { + val1 := cmd.Args[0] // First argument + val2 := cmd.Args[1] // Second argument + + // Type assertion for strings + if str, ok := val1.(string); ok { + fmt.Println("String:", str) + } + + // Type assertion for numbers + if num, ok := val2.(int); ok { + fmt.Println("Int:", num) + } +} +``` + +## Grammar (PEG) + +The format uses PEG (Parsing Expression Grammar). Grammar is described in file `parser/grammar.peg`. + +Basic rules: + +- All commands without body end with semicolon `;` +- Command body is enclosed in curly braces `{ }` +- Arguments are separated by spaces +- Comments start with `#` + +## Requirements + +- Go 1.23+ (for `iter.Seq`) + +## License + +This project is licensed under GNU General Public License version 3 (GPLv3). +See [LICENSE](LICENSE) file for details. + +``` + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + +Copyright (C) 2026 Alexander NeonXP Kiryukhin <i@neonxp.ru> +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. +``` + +## Author + +- Alexander Kiryukhin <i@neonxp.ru> diff --git a/config.ebnf b/config.ebnf deleted file mode 100644 index ffb588a..0000000 --- a/config.ebnf +++ /dev/null @@ -1,30 +0,0 @@ -Config = Doc . -Doc = Stmt { Stmt } . -Stmt = Word (Assignment | Command) . - -# Statements -Assignment = "=" Values br . -Command = [Values] ( Body | br ) . - -# Value types -Values = Value {Value} . -Value = Word | String | Number | Boolean . -Body = lbrace [ Doc ] rbrace . - -# Atoms -Word = word . -Number = number . -Boolean = boolean . -String = str . - -# Primitives -word = (alpha | spec) {alpha | number | spec} . -alpha = `[a-zA-Z]` . -spec = `\$|_|-` . -number = `-?[0-9]+(\.[0-9]+)?` . -boolean = `true|false` . -str = `"[^"]*"` | `'[^']*'` | '`' { `[^\x60]` } '`' . -lbrace = "{" . -rbrace = "}" . -br = ";" . -white_space = ` |\t|\r|\n|#.*` . diff --git a/example/file.conf b/example/file.conf deleted file mode 100644 index d23c7d7..0000000 --- a/example/file.conf +++ /dev/null @@ -1,33 +0,0 @@ -# one line comment - -simpe_key = "тест_кириллицы"; -simple_key_without_spaces = value; - -string_key = - "value" - 'string'; - -multiline_string = ` - multiline - string - 123 -`; - -int_key = -123.456; -bool_key = true; - -expression1 argument1 "argument2" 123; - -# comment can be everywhere -group_directive_without_arguments { - expression1 argument2 "string" 123 true; - expression2 argument3 "string111" 123321 false; - children_group "some argument" { - # child group. Can be empty. This is equivalent to directive `children_group "some argument"` - } -} - -group_directive_with_argument "some other argument" 'second argument' { - child_val = "children value"; -} - diff --git a/example/file2.conf b/example/file2.conf deleted file mode 100644 index b22f06b..0000000 --- a/example/file2.conf +++ /dev/null @@ -1,7 +0,0 @@ -rss = "https://neonxp.ru/feed/"; -db_file = $XDG_DATA_HOME "/pose/pose.db"; - -telegram { - token = "279146841:AAsome_secret_token_M"; - groups = "-1003888840756"; -} diff --git a/example/main.go b/example/main.go deleted file mode 100644 index c42ab3b..0000000 --- a/example/main.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - - "go.neonxp.ru/conf" - "go.neonxp.ru/conf/visitor" -) - -func main() { - cfg := conf.New() - if err := cfg.LoadFile("./example/file2.conf"); err != nil { - panic(err) - } - - pr := visitor.NewDefault() - if err := cfg.Process(pr); err != nil { - panic(err) - } - - tok, err := pr.Get("telegram.token") - if err != nil { - panic(err) - } - - fmt.Println(tok.String()) -} @@ -1,3 +1,3 @@ package conf -//go:generate egg -o ./internal/parser/parser.go -package parser -start Config config.ebnf +//go:generate go tool github.com/mna/pigeon -optimize-grammar -optimize-parser -nolint -o ./parser/parser.go ./parser/grammar.peg @@ -1,7 +1,13 @@ module go.neonxp.ru/conf -go 1.25.0 +go 1.23.0 -require modernc.org/scanner v1.3.0 -require modernc.org/token v1.1.0 // indirect +require ( + github.com/mna/pigeon v1.3.0 // indirect + golang.org/x/mod v0.21.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/tools v0.25.0 // indirect +) + +tool github.com/mna/pigeon @@ -1,3 +1,11 @@ +github.com/mna/pigeon v1.3.0 h1:/3fzVrl1C2RK3x04tyL+ribn+3S3VSEFFbCFLmRPAoc= +github.com/mna/pigeon v1.3.0/go.mod h1:SKQNHonx2q9U2QSSoPtMigExj+vQ1mOpL7UVFQF/IA0= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= modernc.org/scanner v1.3.0 h1:930qF5E11ZENhAV27sw601w5eVaKZIlC1JJdPCS1pS8= modernc.org/scanner v1.3.0/go.mod h1:dLuOdK1HO0HLmTSj29XOm62Oq3Y7Gbg92YmA16ZkKKQ= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/internal/ast/processor.go b/internal/ast/processor.go deleted file mode 100644 index 55dce59..0000000 --- a/internal/ast/processor.go +++ /dev/null @@ -1,119 +0,0 @@ -package ast - -import ( - "fmt" - "strconv" - - "go.neonxp.ru/conf/internal/parser" - "go.neonxp.ru/conf/model" -) - -func ToDoc(config *Node) (model.Body, error) { - if len(config.Children) < 1 { - return nil, fmt.Errorf("invalid ast tree") - } - - doc := config.Children[0] - - return processDoc(doc), nil -} - -func processDoc(docNode *Node) model.Body { - doc := make(model.Body, 0, len(docNode.Children)) - for _, stmt := range docNode.Children { - processStmt(&doc, stmt) - } - return doc -} - -func processStmt(doc *model.Body, stmt *Node) { - ident := extractIdent(stmt.Children[0]) - nodeBody := stmt.Children[1] - switch nodeBody.Symbol { - case parser.Command: - *doc = append(*doc, processDirective(ident, nodeBody)) - case parser.Assignment: - *doc = append(*doc, processSetting(ident, nodeBody)) - } -} - -func processDirective(ident string, directive *Node) *model.Directive { - result := &model.Directive{ - Name: ident, - } - - for _, child := range directive.Children { - // Can be arguments OR body OR both - switch child.Symbol { - case parser.Values: - result.Arguments = extractValues(child) - case parser.Body: - // Children[0] = '{', Children[1] = Body, Children[2] = '}' - result.Body = processDoc(child.Children[1]) - } - } - - return result -} - -func processSetting(ident string, setting *Node) *model.Setting { - result := &model.Setting{ - Key: ident, - Value: extractValues(setting.Children[1]), // Children[0] = '=', Children[1] = Values, Children[2] = ';' - } - - return result -} - -func extractIdent(word *Node) string { - return word.Children[0].Source -} - -func extractValues(args *Node) []model.Value { - result := make([]model.Value, len(args.Children)) - for i, child := range args.Children { - v, err := extractValue(child) - if err != nil { - result[i] = err - continue - } - result[i] = v - } - - return result -} - -func extractValue(value *Node) (any, error) { - v := value.Children[0] - s := v.Children[0].Source - switch v.Symbol { - case parser.Word: - return model.Word(s), nil - case parser.String: - return unquote(s), nil - case parser.Number: - d, err := strconv.Atoi(s) - if err == nil { - return d, nil - } - fl, err := strconv.ParseFloat(s, 32) - if err == nil { - return fl, nil - } - return nil, fmt.Errorf("invalid number: %s (%s)", v.Source, s) - case parser.Boolean: - return s == "true", nil - default: - return nil, fmt.Errorf("unknown value type: %s (%s)", v.Symbol, s) - } -} - -func unquote(str string) string { - if len(str) == 0 { - return "" - } - if str[0:1] == `"` || str[0:1] == "'" || str[0:1] == "`" { - return str[1 : len(str)-1] - } - return str -} diff --git a/internal/ast/tree.go b/internal/ast/tree.go deleted file mode 100644 index 72b7c78..0000000 --- a/internal/ast/tree.go +++ /dev/null @@ -1,43 +0,0 @@ -package ast - -import ( - "go.neonxp.ru/conf/internal/parser" - "modernc.org/scanner" -) - -func Parse(p *parser.Parser, data []int32) []*Node { - nodes := make([]*Node, 0, 2) - for len(data) != 0 { - next := int32(1) - var node *Node - switch n := data[0]; { - case n < 0: - next = 2 + data[1] - node = &Node{ - Symbol: parser.Symbol(-data[0]), - Children: Parse(p, data[2:next]), - } - default: - tok := p.Token(n) - node = &Node{ - Token: tok, - Symbol: parser.Symbol(tok.Ch), - Source: tok.Src(), - Col: tok.Position().Column, - Line: tok.Position().Line, - } - } - nodes = append(nodes, node) - data = data[next:] - } - return nodes -} - -type Node struct { - Symbol parser.Symbol - Token scanner.Token - Children []*Node - Source string - Col int - Line int -} diff --git a/internal/parser/parser.go b/internal/parser/parser.go deleted file mode 100644 index 171878d..0000000 --- a/internal/parser/parser.go +++ /dev/null @@ -1,1803 +0,0 @@ -// Code generated by 'egg -o ./internal/parser/parser.go -package parser -start Config config.ebnf', DO NOT EDIT. - -// Parser generated from config.ebnf. - -package parser - -import ( - "errors" - "fmt" - "go/token" - "strconv" - "unicode" - "unicode/utf8" - - "modernc.org/scanner" -) - -var _ = unicode.MaxRune - -// Symbols -const ( - TOK_EOF = Symbol(0) // EOF - TOK_003d = Symbol(1) // '=' - boolean = Symbol(2) // boolean - br = Symbol(3) // br - lbrace = Symbol(4) // lbrace - number = Symbol(5) // number - rbrace = Symbol(6) // rbrace - str = Symbol(7) // str - white_space = Symbol(8) // white_space - word = Symbol(9) // word - Config = Symbol(10) // Config - Doc = Symbol(11) // Doc - Stmt = Symbol(12) // Stmt - Assignment = Symbol(13) // Assignment - Command = Symbol(14) // Command - Values = Symbol(15) // Values - Value = Symbol(16) // Value - Body = Symbol(17) // Body - Word = Symbol(18) // Word - Number = Symbol(19) // Number - Boolean = Symbol(20) // Boolean - String = Symbol(21) // String -) - -const SymbolNames = "EOF'='booleanbrlbracenumberrbracestrwhite_spacewordConfigDocStmtAssignmentCommandValuesValueBodyWordNumberBooleanString" - -var SymbolIndex = [...]uint8{0, 3, 6, 13, 15, 21, 27, 33, 36, 47, 51, 57, 60, 64, 74, 81, 87, 92, 96, 100, 106, 113, 119} - -func (s Symbol) String() string { - idx := int(s) - 0 - if s < 0 || idx >= len(SymbolIndex)-1 { - return "Symbol(" + strconv.FormatInt(int64(s), 10) + ")" - } - return SymbolNames[SymbolIndex[idx]:SymbolIndex[idx+1]] -} - -var errorSets = [...][]Symbol{ - {String, Boolean, Number, Word, Body, Value, Values, word, str, number, lbrace, br, boolean, TOK_003d}, - {TOK_003d}, - {String, Boolean, Number, Word, Value, word, str, number, lbrace, br, boolean}, - {String, Boolean, Number, Word, Value, word, str, number, boolean}, - {String, Boolean, Number, Word, word, str, number, boolean}, - {word, str, number, boolean}, - {boolean}, - {lbrace, br}, - {br}, - {lbrace}, - {number}, - {Word, Stmt, word, rbrace}, - {rbrace}, - {str}, - {Word, Stmt, word}, - {Word, word}, - {word}, -} - -type Parser struct { - cache [][]int32 - eof bool - errBudget int - id rune // from scanSep, valid if .n != 0 - n int // from scanSep, valid if != 0 - off int - sc *scanner.RecScanner - src []byte - tok scanner.Token // current lookahead - tokIndex int32 // For scanner.Token(tokIndex) -} - -type Symbol int32 - -// scan recognizes longest UTF-8 lexemes. Lower IDs take precedence on same length. -// -// id 0: $ -// id 1: = -// id 2: (true|false) -// id 3: (;) -// id 4: (\{) -// id 5: (-?[0-9]+(\.[0-9]+)?) -// id 6: (\}) -// id 7: (("[^"]*")|('[^']*')|(`)(([^\x60]))*(`)) -// id 8: ( |\t|\r|\n|#.*) -// id 9: (((([a-zA-Z]))|((\$|_|-))))(((([a-zA-Z]))|((-?[0-9]+(\.[0-9]+)?))|((\$|_|-))))* -// -// ID == -1 is returned when no lexeme was recognized. -func (*Parser) scan(s []byte) (id, length int) { - const endOfText = 0x110000 - var pos, pos0, width, width1 int - id = -1 - var r, r1 rune - _ = pos0 - _ = r - _ = r1 - _ = width1 - step := func(pos int) (r rune, n int) { - if pos < len(s) { - c := s[pos] - if c < utf8.RuneSelf { - return rune(c), 1 - } - return utf8.DecodeRune(s[pos:]) - } - return endOfText, 0 - } - move := func() { - pos += width - if r, width = r1, width1; r != endOfText { - r1, width1 = step(pos + width) - } - } - accept := func(x rune) bool { - if r == x { - move() - return true - } - return false - } - _ = accept - accept2 := func(x rune) bool { - if r <= x { - move() - return true - } - return false - } - _ = accept2 - r, r1 = endOfText, endOfText - width, width1 = 0, 0 - r, width = step(pos) - if r != endOfText { - r1, width1 = step(pos + width) - } - if accept('\t') { - goto l41 - } - if accept('\n') { - goto l43 - } - if accept('\r') { - goto l45 - } - if accept(' ') { - goto l47 - } - if accept('"') { - goto l49 - } - if accept('#') { - goto l61 - } - if accept('$') { - goto l69 - } - if accept('\'') { - goto l172 - } - if accept('-') { - goto l184 - } - if accept(';') { - goto l252 - } - if accept('=') { - goto l254 - } - if accept('_') { - goto l268 - } - if accept('`') { - goto l280 - } - if accept('f') { - goto l292 - } - if accept('t') { - goto l360 - } - if accept('{') { - goto l414 - } - if accept('}') { - goto l416 - } - if r < '0' { - goto l34out - } - if accept2('9') { - goto l239 - } -l34out: - if r < 'A' { - goto l36out - } - if accept2('Z') { - goto l256 - } - if r < 'a' { - goto l36out - } - if accept2('e') { - goto l256 - } - if r < 'g' { - goto l36out - } - if accept2('s') { - goto l256 - } - if r < 'u' { - goto l36out - } - if accept2('z') { - goto l256 - } -l36out: - if r == endOfText { - goto l418 - } - return id, length -l41: - id, length = 8, pos - return id, length -l43: - id, length = 8, pos - return id, length -l45: - id, length = 8, pos - return id, length -l47: - id, length = 8, pos - return id, length -l49: - if accept('"') { - goto l54 - } - if accept2('!') { - goto l56 - } - if r < '#' { - goto l51out - } - if accept2('\U0010ffff') { - goto l56 - } -l51out: - return id, length -l54: - id, length = 7, pos - return id, length -l56: - if accept('"') { - goto l54 - } - if accept2('!') { - goto l56 - } - if r < '#' { - goto l58out - } - if accept2('\U0010ffff') { - goto l56 - } -l58out: - return id, length -l61: - id, length = 8, pos - if accept2('\t') { - goto l65 - } - if r < '\v' { - goto l62out - } - if accept2('\U0010ffff') { - goto l65 - } -l62out: - return id, length -l65: - id, length = 8, pos - if accept2('\t') { - goto l65 - } - if r < '\v' { - goto l66out - } - if accept2('\U0010ffff') { - goto l65 - } -l66out: - return id, length -l69: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l76out - } - if accept2('9') { - goto l105 - } -l76out: - if r < 'A' { - goto l78out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l78out - } - if accept2('z') { - goto l148 - } -l78out: - return id, length -l81: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l88out - } - if accept2('9') { - goto l105 - } -l88out: - if r < 'A' { - goto l90out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l90out - } - if accept2('z') { - goto l148 - } -l90out: - return id, length -l93: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l100out - } - if accept2('9') { - goto l105 - } -l100out: - if r < 'A' { - goto l102out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l102out - } - if accept2('z') { - goto l148 - } -l102out: - return id, length -l105: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('.') { - goto l119 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l114out - } - if accept2('9') { - goto l105 - } -l114out: - if r < 'A' { - goto l116out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l116out - } - if accept2('z') { - goto l148 - } -l116out: - return id, length -l119: - if r < '0' { - goto l119out - } - if accept2('9') { - goto l122 - } -l119out: - return id, length -l122: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l129out - } - if accept2('9') { - goto l134 - } -l129out: - if r < 'A' { - goto l131out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l131out - } - if accept2('z') { - goto l148 - } -l131out: - return id, length -l134: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('.') { - goto l119 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l143out - } - if accept2('9') { - goto l134 - } -l143out: - if r < 'A' { - goto l145out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l145out - } - if accept2('z') { - goto l148 - } -l145out: - return id, length -l148: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l155out - } - if accept2('9') { - goto l105 - } -l155out: - if r < 'A' { - goto l157out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l157out - } - if accept2('z') { - goto l148 - } -l157out: - return id, length -l160: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l167out - } - if accept2('9') { - goto l105 - } -l167out: - if r < 'A' { - goto l169out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l169out - } - if accept2('z') { - goto l148 - } -l169out: - return id, length -l172: - if accept('\'') { - goto l177 - } - if accept2('&') { - goto l179 - } - if r < '(' { - goto l174out - } - if accept2('\U0010ffff') { - goto l179 - } -l174out: - return id, length -l177: - id, length = 7, pos - return id, length -l179: - if accept('\'') { - goto l177 - } - if accept2('&') { - goto l179 - } - if r < '(' { - goto l181out - } - if accept2('\U0010ffff') { - goto l179 - } -l181out: - return id, length -l184: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l191out - } - if accept2('9') { - goto l196 - } -l191out: - if r < 'A' { - goto l193out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l193out - } - if accept2('z') { - goto l148 - } -l193out: - return id, length -l196: - id, length = 5, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('.') { - goto l210 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l205out - } - if accept2('9') { - goto l196 - } -l205out: - if r < 'A' { - goto l207out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l207out - } - if accept2('z') { - goto l148 - } -l207out: - return id, length -l210: - if r < '0' { - goto l210out - } - if accept2('9') { - goto l213 - } -l210out: - return id, length -l213: - id, length = 5, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l220out - } - if accept2('9') { - goto l225 - } -l220out: - if r < 'A' { - goto l222out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l222out - } - if accept2('z') { - goto l148 - } -l222out: - return id, length -l225: - id, length = 5, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('.') { - goto l119 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l234out - } - if accept2('9') { - goto l225 - } -l234out: - if r < 'A' { - goto l236out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l236out - } - if accept2('z') { - goto l148 - } -l236out: - return id, length -l239: - id, length = 5, pos - if accept('.') { - goto l245 - } - if r < '0' { - goto l242out - } - if accept2('9') { - goto l239 - } -l242out: - return id, length -l245: - if r < '0' { - goto l245out - } - if accept2('9') { - goto l248 - } -l245out: - return id, length -l248: - id, length = 5, pos - if r < '0' { - goto l249out - } - if accept2('9') { - goto l248 - } -l249out: - return id, length -l252: - id, length = 3, pos - return id, length -l254: - id, length = 1, pos - return id, length -l256: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l263out - } - if accept2('9') { - goto l105 - } -l263out: - if r < 'A' { - goto l265out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l265out - } - if accept2('z') { - goto l148 - } -l265out: - return id, length -l268: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l275out - } - if accept2('9') { - goto l105 - } -l275out: - if r < 'A' { - goto l277out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l277out - } - if accept2('z') { - goto l148 - } -l277out: - return id, length -l280: - if accept('`') { - goto l285 - } - if accept2('_') { - goto l287 - } - if r < 'a' { - goto l282out - } - if accept2('\U0010ffff') { - goto l287 - } -l282out: - return id, length -l285: - id, length = 7, pos - return id, length -l287: - if accept('`') { - goto l285 - } - if accept2('_') { - goto l287 - } - if r < 'a' { - goto l289out - } - if accept2('\U0010ffff') { - goto l287 - } -l289out: - return id, length -l292: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('a') { - goto l306 - } - if r < '0' { - goto l301out - } - if accept2('9') { - goto l105 - } -l301out: - if r < 'A' { - goto l303out - } - if accept2('Z') { - goto l148 - } - if r < 'b' { - goto l303out - } - if accept2('z') { - goto l148 - } -l303out: - return id, length -l306: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('l') { - goto l320 - } - if r < '0' { - goto l315out - } - if accept2('9') { - goto l105 - } -l315out: - if r < 'A' { - goto l317out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l317out - } - if accept2('k') { - goto l148 - } - if r < 'm' { - goto l317out - } - if accept2('z') { - goto l148 - } -l317out: - return id, length -l320: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('s') { - goto l334 - } - if r < '0' { - goto l329out - } - if accept2('9') { - goto l105 - } -l329out: - if r < 'A' { - goto l331out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l331out - } - if accept2('r') { - goto l148 - } - if r < 't' { - goto l331out - } - if accept2('z') { - goto l148 - } -l331out: - return id, length -l334: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('e') { - goto l348 - } - if r < '0' { - goto l343out - } - if accept2('9') { - goto l105 - } -l343out: - if r < 'A' { - goto l345out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l345out - } - if accept2('d') { - goto l148 - } - if r < 'f' { - goto l345out - } - if accept2('z') { - goto l148 - } -l345out: - return id, length -l348: - id, length = 2, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l355out - } - if accept2('9') { - goto l105 - } -l355out: - if r < 'A' { - goto l357out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l357out - } - if accept2('z') { - goto l148 - } -l357out: - return id, length -l360: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('r') { - goto l374 - } - if r < '0' { - goto l369out - } - if accept2('9') { - goto l105 - } -l369out: - if r < 'A' { - goto l371out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l371out - } - if accept2('q') { - goto l148 - } - if r < 's' { - goto l371out - } - if accept2('z') { - goto l148 - } -l371out: - return id, length -l374: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('u') { - goto l388 - } - if r < '0' { - goto l383out - } - if accept2('9') { - goto l105 - } -l383out: - if r < 'A' { - goto l385out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l385out - } - if accept2('t') { - goto l148 - } - if r < 'v' { - goto l385out - } - if accept2('z') { - goto l148 - } -l385out: - return id, length -l388: - id, length = 9, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if accept('e') { - goto l402 - } - if r < '0' { - goto l397out - } - if accept2('9') { - goto l105 - } -l397out: - if r < 'A' { - goto l399out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l399out - } - if accept2('d') { - goto l148 - } - if r < 'f' { - goto l399out - } - if accept2('z') { - goto l148 - } -l399out: - return id, length -l402: - id, length = 2, pos - if accept('$') { - goto l81 - } - if accept('-') { - goto l93 - } - if accept('_') { - goto l160 - } - if r < '0' { - goto l409out - } - if accept2('9') { - goto l105 - } -l409out: - if r < 'A' { - goto l411out - } - if accept2('Z') { - goto l148 - } - if r < 'a' { - goto l411out - } - if accept2('z') { - goto l148 - } -l411out: - return id, length -l414: - id, length = 4, pos - return id, length -l416: - id, length = 6, pos - return id, length -l418: - id, length = 0, pos - return id, length -} - -// Scan is used internally from Parse. -func (p *Parser) Scan() (r scanner.Token) { - return p.sc.Scan() -} - -// init initalizes 'p' with content in 'src', assuming it comes from 'name'. -// 'src' becomes "owned" by 'p'. init invalidates any pre-existing ASTs produced by 'p'. Mutating -// 'src' invalidates the current AST returned from any parsing function called after init. -func (p *Parser) init(name string, src []byte) (err error) { - p.eof = false - p.errBudget = 10 - p.n = 0 - p.off = 0 - p.src = src - p.tok = scanner.Token{} - p.tokIndex = 0 - p.sc = scanner.NewRecScanner(name, p.src, p.scan, int(white_space)) - return nil -} - -// Assignment grammar: -// -// # Statements -// Assignment = "=" Values br . -// -// State 0 -// on '=' -// shift and goto state 1 -// State 1 -// on boolean, number, str, word, Value, Word, Number, Boolean, String -// call Values and goto state 2 -// State 2 -// on br -// shift and goto state 3 -// State 3 -// Accept -// -// Assignment is used internally from Parse. -func (p *Parser) Assignment() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Assignment), 0) - // state0: - accept, errorSet = false, 1 - switch Symbol(p.tok.Ch) { - case TOK_003d: - r = append(r, p.shift()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = false, 3 - switch Symbol(p.tok.Ch) { - case boolean, number, str, word, Value, Word, Number, Boolean, String: - r = p.add(r, p.Values()) - goto state2 - } - return p.stop(r, accept, errorSet) -state2: - accept, errorSet = false, 8 - switch Symbol(p.tok.Ch) { - case br: - r = append(r, p.shift()) - goto state3 - } - return p.stop(r, accept, errorSet) -state3: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -// Body grammar: -// -// Body = lbrace [ Doc ] rbrace . -// -// State 0 -// on lbrace -// shift and goto state 1 -// State 1 -// on rbrace -// shift and goto state 2 -// on word, Stmt, Word -// call Doc and goto state 3 -// State 2 -// Accept -// State 3 -// on rbrace -// shift and goto state 2 -// -// Body is used internally from Parse. -func (p *Parser) Body() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Body), 0) - // state0: - accept, errorSet = false, 9 - switch Symbol(p.tok.Ch) { - case lbrace: - r = append(r, p.shift()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = false, 11 - switch Symbol(p.tok.Ch) { - case rbrace: - r = append(r, p.shift()) - goto state2 - case word, Stmt, Word: - r = p.add(r, p.Doc()) - goto state3 - } - return p.stop(r, accept, errorSet) -state2: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -state3: - accept, errorSet = false, 12 - switch Symbol(p.tok.Ch) { - case rbrace: - r = append(r, p.shift()) - goto state2 - } - return p.stop(r, accept, errorSet) -} - -// Boolean grammar: -// -// Boolean = boolean . -// -// State 0 -// on boolean -// shift and goto state 1 -// State 1 -// Accept -// -// Boolean is used internally from Parse. -func (p *Parser) Boolean() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Boolean), 0) - // state0: - accept, errorSet = false, 6 - switch Symbol(p.tok.Ch) { - case boolean: - r = append(r, p.shift()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -// Command grammar: -// -// Command = [Values] ( Body | br ) . -// -// State 0 -// on br -// shift and goto state 1 -// on boolean, number, str, word, Value, Word, Number, Boolean, String -// call Values and goto state 2 -// on lbrace -// call Body and goto state 1 -// State 1 -// Accept -// State 2 -// on br -// shift and goto state 1 -// on lbrace -// call Body and goto state 1 -// -// Command is used internally from Parse. -func (p *Parser) Command() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Command), 0) - // state0: - accept, errorSet = false, 2 - switch Symbol(p.tok.Ch) { - case br: - r = append(r, p.shift()) - goto state1 - case boolean, number, str, word, Value, Word, Number, Boolean, String: - r = p.add(r, p.Values()) - goto state2 - case lbrace: - r = p.add(r, p.Body()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -state2: - accept, errorSet = false, 7 - switch Symbol(p.tok.Ch) { - case br: - r = append(r, p.shift()) - goto state1 - case lbrace: - r = p.add(r, p.Body()) - goto state1 - } - return p.stop(r, accept, errorSet) -} - -// Config grammar: -// -// Config = Doc . -// -// State 0 -// on word, Stmt, Word -// call Doc and goto state 1 -// State 1 -// Accept -// -// Config is used internally from Parse. -func (p *Parser) Config() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Config), 0) - // state0: - accept, errorSet = false, 14 - switch Symbol(p.tok.Ch) { - case word, Stmt, Word: - r = p.add(r, p.Doc()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - if accept = accept && p.eof; accept { - r = append(r, p.shift()) - } - r[1] = int32(len(r) - 2) - if !accept { - p.err(p.tok.Position(), "%q [%s]: expected %v", p.tok.Src(), Symbol(p.tok.Ch), errorSets[errorSet]) - } - return r -} - -// Doc grammar: -// -// Doc = Stmt { Stmt } . -// -// State 0 -// on word, Word -// call Stmt and goto state 1 -// State 1 -// Accept -// on word, Word -// call Stmt and goto state 1 -// -// Doc is used internally from Parse. -func (p *Parser) Doc() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Doc), 0) - // state0: - accept, errorSet = false, 15 - switch Symbol(p.tok.Ch) { - case word, Word: - r = p.add(r, p.Stmt()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 15 - switch Symbol(p.tok.Ch) { - case word, Word: - r = p.add(r, p.Stmt()) - goto state1 - } - return p.stop(r, accept, errorSet) -} - -// Number grammar: -// -// Number = number . -// -// State 0 -// on number -// shift and goto state 1 -// State 1 -// Accept -// -// Number is used internally from Parse. -func (p *Parser) Number() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Number), 0) - // state0: - accept, errorSet = false, 10 - switch Symbol(p.tok.Ch) { - case number: - r = append(r, p.shift()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -// Stmt grammar: -// -// Stmt = Word (Assignment | Command) . -// -// State 0 -// on word -// call Word and goto state 1 -// State 1 -// on '=' -// call Assignment and goto state 2 -// on boolean, br, lbrace, number, str, word, Values, Value, Body, Word, Number, Boolean, String -// call Command and goto state 2 -// State 2 -// Accept -// -// Stmt is used internally from Parse. -func (p *Parser) Stmt() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Stmt), 0) - // state0: - accept, errorSet = false, 16 - switch Symbol(p.tok.Ch) { - case word: - r = p.add(r, p.Word()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = false, 0 - switch Symbol(p.tok.Ch) { - case TOK_003d: - r = p.add(r, p.Assignment()) - goto state2 - case boolean, br, lbrace, number, str, word, Values, Value, Body, Word, Number, Boolean, String: - r = p.add(r, p.Command()) - goto state2 - } - return p.stop(r, accept, errorSet) -state2: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -// String grammar: -// -// String = str . -// -// State 0 -// on str -// shift and goto state 1 -// State 1 -// Accept -// -// String is used internally from Parse. -func (p *Parser) String() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(String), 0) - // state0: - accept, errorSet = false, 13 - switch Symbol(p.tok.Ch) { - case str: - r = append(r, p.shift()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -// Value grammar: -// -// Value = Word | String | Number | Boolean . -// -// State 0 -// on word -// call Word and goto state 1 -// on number -// call Number and goto state 1 -// on boolean -// call Boolean and goto state 1 -// on str -// call String and goto state 1 -// State 1 -// Accept -// -// Value is used internally from Parse. -func (p *Parser) Value() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Value), 0) - // state0: - accept, errorSet = false, 5 - switch Symbol(p.tok.Ch) { - case word: - r = p.add(r, p.Word()) - goto state1 - case number: - r = p.add(r, p.Number()) - goto state1 - case boolean: - r = p.add(r, p.Boolean()) - goto state1 - case str: - r = p.add(r, p.String()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -// Values grammar: -// -// # Value types -// Values = Value {Value} . -// -// State 0 -// on boolean, number, str, word, Word, Number, Boolean, String -// call Value and goto state 1 -// State 1 -// Accept -// on boolean, number, str, word, Word, Number, Boolean, String -// call Value and goto state 1 -// -// Values is used internally from Parse. -func (p *Parser) Values() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Values), 0) - // state0: - accept, errorSet = false, 4 - switch Symbol(p.tok.Ch) { - case boolean, number, str, word, Word, Number, Boolean, String: - r = p.add(r, p.Value()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 4 - switch Symbol(p.tok.Ch) { - case boolean, number, str, word, Word, Number, Boolean, String: - r = p.add(r, p.Value()) - goto state1 - } - return p.stop(r, accept, errorSet) -} - -// Word grammar: -// -// # Atoms -// Word = word . -// -// State 0 -// on word -// shift and goto state 1 -// State 1 -// Accept -// -// Word is used internally from Parse. -func (p *Parser) Word() (r []int32) { - accept, errorSet := false, 0 - r = append(p.get(), -int32(Word), 0) - // state0: - accept, errorSet = false, 16 - switch Symbol(p.tok.Ch) { - case word: - r = append(r, p.shift()) - goto state1 - } - return p.stop(r, accept, errorSet) -state1: - accept, errorSet = true, 0 - return p.stop(r, accept, errorSet) -} - -func (p *Parser) shift() (r int32) { - r = p.tokIndex - if !p.eof { - p.tok = p.Scan() - p.tokIndex++ - p.eof = p.tok.Ch == rune(TOK_EOF) - } - return r -} - -func (p *Parser) get() (r []int32) { - if n := len(p.cache); n != 0 { - r = p.cache[n-1][:0] - p.cache = p.cache[:n-1] - } - return r -} - -func (p *Parser) add(r, s []int32) (t []int32) { - p.cache = append(p.cache, s) - return append(r, s...) -} - -func (p *Parser) stop(r []int32, accept bool, errorSet int) []int32 { - r[1] = int32(len(r) - 2) - if !accept { - p.err(p.tok.Position(), "%q [%s]: expected %v", p.tok.Src(), Symbol(p.tok.Ch), errorSets[errorSet]) - } - return r -} - -// EOF reports whether the parser lookahead token is EOF. -func (p *Parser) EOF() bool { - return p.eof -} - -// Token returns the n-th token in 'p'. Token panics if 'n' is out of range. -func (p *Parser) Token(n int32) (r scanner.Token) { - return p.sc.Token(int(n)) -} - -// Parse parses 'src'. 'src' becomes "owned" by the parser and must not be -// mutated afterwards. -func (p *Parser) Parse(name string, src []byte) (ast []int32, err error) { - if err = p.init(name, src); err != nil { - return nil, err - } - - defer func() { - switch e := recover(); x := e.(type) { - case nil: - // ok - case scanner.ErrList: - err = x - case error: - err = errors.Join(err, x) - default: - err = errors.Join(err, fmt.Errorf("%v", x)) - } - }() - - p.tok = p.Scan() - ast = p.Config() - return ast, p.sc.Err() -} - -func (p *Parser) err0(pos token.Position, s string, args ...any) { - p.sc.AddErr(pos, s, args...) - if p.errBudget--; p.errBudget == 0 { - p.sc.AddErr(pos, "too many errors") - } -} - -func (p *Parser) err(pos token.Position, s string, args ...any) { - p.err0(pos, s, args...) - p.shift() - if p.eof { - panic(p.sc.Err()) - } -} @@ -4,49 +4,25 @@ import ( "fmt" "os" - "go.neonxp.ru/conf/internal/ast" - "go.neonxp.ru/conf/internal/parser" "go.neonxp.ru/conf/model" + "go.neonxp.ru/conf/parser" ) -func New() *Conf { - return &Conf{ - root: model.Body{}, - } -} - -type Conf struct { - root model.Body -} - -func (c *Conf) LoadFile(filename string) error { +func LoadFile(filename string) (model.Group, error) { content, err := os.ReadFile(filename) if err != nil { - return fmt.Errorf("failed load file: %w", err) + return nil, fmt.Errorf("failed load file: %w", err) } - return c.Load(filename, content) + return Load(filename, content) } -func (c *Conf) Load(name string, input []byte) error { - p := &parser.Parser{} - astSlice, err := p.Parse(name, input) - if err != nil { - return fmt.Errorf("failed parse conf content: %w", err) - } - - astTree := ast.Parse(p, astSlice) +func Load(name string, input []byte) (model.Group, error) { - doc, err := ast.ToDoc(astTree[0]) + res, err := parser.Parse(name, input) if err != nil { - return fmt.Errorf("failed build Doc: %w", err) + return nil, parser.CaretErrors(err, string(input)) } - c.root = doc - - return nil -} - -func (c *Conf) Process(visitor model.Visitor) error { - return c.root.Execute(visitor) + return res.(model.Group), nil } diff --git a/loader_test.go b/loader_test.go index 3e16922..3c9a84a 100644 --- a/loader_test.go +++ b/loader_test.go @@ -2,50 +2,40 @@ package conf_test import ( "fmt" + "log" "go.neonxp.ru/conf" - "go.neonxp.ru/conf/visitor" ) -func ExampleNew() { - config := ` - key = "value"; - group "test" { - key = 123; +func ExampleLoad() { + test := ` + some directive; + group1 param1 { + group2 param2 { + group3 param3 { + key value; + } + } } ` - - cfg := conf.New() - - if err := cfg.Load("example", []byte(config)); err != nil { - panic(err) - } - - pr := visitor.NewDefault() - if err := cfg.Process(pr); err != nil { - panic(err) - } - - val1, err := pr.Get("key") + result, err := conf.Load("test", []byte(test)) if err != nil { - panic(err) + log.Fatal(err) } - val2, err := pr.Get("group.key") - if err != nil { - panic(err) - } - - val3, err := pr.Get("group") - if err != nil { - panic(err) - } - - fmt.Println("key =", val1.String()) - fmt.Println("group.key =", val2.String()) - fmt.Println("group args =", val3.String()) - // Output: - // key = value - // group.key = 123 - // group args = test + fmt.Println( + result.Get("group1"). + Group.Get("group2"). + Group.Get("group3"). + Group.Get("key").Value(), + ) // → value + + fmt.Println( + result.Get("group1"). + Group.Get("group2"). + Group.Get("group3").Value(), + ) // → param3 + + // Output: value + // param3 } diff --git a/model/body.go b/model/body.go deleted file mode 100644 index b7b4a0c..0000000 --- a/model/body.go +++ /dev/null @@ -1,28 +0,0 @@ -package model - -import ( - "errors" - "fmt" -) - -type Body []any - -var ErrInvalidType = errors.New("invalid type") - -func (d Body) Execute(v Visitor) error { - for _, it := range d { - switch it := it.(type) { - case *Setting: - if err := v.VisitSetting(it.Key, it.Value); err != nil { - return err - } - case *Directive: - if err := v.VisitDirective(it.Name, it.Arguments, it.Body); err != nil { - return err - } - default: - return fmt.Errorf("%w: %t", ErrInvalidType, it) - } - } - return nil -} diff --git a/model/directive.go b/model/directive.go deleted file mode 100644 index 3852e72..0000000 --- a/model/directive.go +++ /dev/null @@ -1,9 +0,0 @@ -package model - -type Directive struct { - Name string - Arguments Values - Body Body -} - -type Directives []*Directive diff --git a/model/lookup.go b/model/lookup.go deleted file mode 100644 index 880a1f8..0000000 --- a/model/lookup.go +++ /dev/null @@ -1,56 +0,0 @@ -package model - -import ( - "os" - "strings" -) - -// WordLookup тип определяющий функцию поиска замены слов при -// стрингификации Values. -type WordLookup func(word Word) string - -// chainLookup утилитарная функция для последовательных применений функций -// поиска до первого нахождения подстановки. Если returnOrigin == true, -// то в случае неудачи вернёт имя слова. -func chainLookup(lookups ...WordLookup) WordLookup { - return func(word Word) string { - for _, lookup := range lookups { - if v := lookup(word); v != "" { - return v - } - } - return "" - } -} - -// LookupEnv функция типа WordLookup которая пытается подставить вместо word -// соответствующую ему переменную окружения ОС. При этом он срабатывает только -// если слово начинается со знака `$`. -func LookupEnv(word Word) string { - if !strings.HasPrefix(string(word), "$") { - return "" - } - varName := strings.TrimPrefix(string(word), "$") - if result, ok := os.LookupEnv(varName); ok { - return result - } - return "" -} - -// LookupSubst функция типа WordLookup которая пытается подставить вместо word -// значение из словаря подстановок по соответствующему ключу. -func LookupSubst(subst map[Word]string) WordLookup { - return func(word Word) string { - if result, ok := subst[word]; ok { - return result - } - return "" - } -} - -// Origin возвращает просто строковое представления слова. Если поставить в -// конце цепочки - то вместо пустоты (если предыдущие фильтры не сработали) -// вернётся оригинальное имя слова. -func Origin(word Word) string { - return string(word) -} diff --git a/model/model.go b/model/model.go new file mode 100644 index 0000000..2bc6dfb --- /dev/null +++ b/model/model.go @@ -0,0 +1,44 @@ +package model + +import ( + "iter" +) + +type Ident string + +type Command struct { + Name Ident + Args []any + Group Group +} + +// Value returns first argument of command. +func (c *Command) Value() any { + if len(c.Args) > 0 { + return c.Args[0] + } + return nil +} + +type Group []Command + +// Get returns first command with given name. +func (g Group) Get(name Ident) *Command { + for _, c := range g { + if c.Name == name { + return &c + } + } + return nil +} + +// Filter commands by predicate and returns iterator over filtered commands. +func (g Group) Filter(predicate func(c *Command) bool) iter.Seq[*Command] { + return func(yield func(*Command) bool) { + for _, c := range g { + if predicate(&c) && !yield(&c) { + return + } + } + } +} diff --git a/model/setting.go b/model/setting.go deleted file mode 100644 index 363a8a9..0000000 --- a/model/setting.go +++ /dev/null @@ -1,6 +0,0 @@ -package model - -type Setting struct { - Key string - Value Values -} diff --git a/model/value.go b/model/value.go deleted file mode 100644 index 9fa8e9b..0000000 --- a/model/value.go +++ /dev/null @@ -1,85 +0,0 @@ -package model - -import ( - "fmt" - "strconv" - "strings" -) - -type Value any - -type Values []Value - -// BuildString собирает из значений Value цельную строку, при этом приводя все -// значения к типу string. Так же принимает функции типа WordLookup, которые -// последовательно будут пытаться привести значения типа Word к -// контекстозависимым значениям. Например, пытаться находить по имени переменную -// окружения ОС. -func (v Values) BuildString(lookups ...WordLookup) string { - sw := strings.Builder{} - - for _, v := range v { - switch v := v.(type) { - case string: - sw.WriteString(v) - case float64: - sw.WriteString(strconv.FormatFloat(v, 'f', 5, 64)) - case int: - sw.WriteString(strconv.Itoa(v)) - case bool: - if v { - sw.WriteString("true") - continue - } - sw.WriteString("false") - case Word: - sw.WriteString(chainLookup(lookups...)(v)) - } - } - - return sw.String() -} - -func (v Values) String() string { - result := make([]string, 0, len(v)) - - for _, v := range v { - switch v := v.(type) { - case string: - result = append(result, v) - case float64: - result = append(result, strconv.FormatFloat(v, 'f', 5, 64)) - case int: - result = append(result, strconv.Itoa(v)) - case bool: - if v { - result = append(result, "true") - continue - } - result = append(result, "false") - case Word: - result = append(result, string(v)) - } - } - - return strings.Join(result, " ") -} - -func (v Values) Int() (int, error) { - if len(v) != 1 { - return 0, fmt.Errorf("AsInt can return only single value (there is %d values)", len(v)) - } - val := v[0] - switch val := val.(type) { - case int: - return val, nil - case string: - return strconv.Atoi(val) - case float64: - return int(val), nil - default: - return 0, fmt.Errorf("invalid type for convert to int: %t", val) - } -} - -type Word string diff --git a/model/visitor.go b/model/visitor.go deleted file mode 100644 index 3f290d3..0000000 --- a/model/visitor.go +++ /dev/null @@ -1,6 +0,0 @@ -package model - -type Visitor interface { - VisitDirective(ident string, args Values, body Body) error - VisitSetting(key string, values Values) error -} diff --git a/parser/errors.go b/parser/errors.go new file mode 100644 index 0000000..c2ed049 --- /dev/null +++ b/parser/errors.go @@ -0,0 +1,107 @@ +package parser + +import ( + "bytes" + "errors" + "fmt" + "strings" +) + +type ErrorLister interface { + Errors() []error +} + +func (e errList) Errors() []error { + return e +} + +// ParserError is the public interface to errors of type parserError +type ParserError interface { + Error() string + InnerError() error + Pos() (int, int, int) + Expected() []string +} + +func (p *parserError) InnerError() error { + return p.Inner +} + +func (p *parserError) Pos() (line, col, offset int) { + return p.pos.line, p.pos.col, p.pos.offset +} + +func (p *parserError) Expected() []string { + return p.expected +} + +func CaretErrors(err error, input string) error { + if el, ok := err.(ErrorLister); ok { + var buffer bytes.Buffer + for _, e := range el.Errors() { + err1, shouldReturn := caretError(e, input, buffer, err) + if shouldReturn { + return err1 + } + } + return errors.New(buffer.String()) + } + return err +} + +func caretError(e error, input string, buffer bytes.Buffer, err error) (error, bool) { + if parserErr, ok := e.(ParserError); ok { + _, col, off := parserErr.Pos() + line := extractLine(input, off) + if col >= len(line) { + col = len(line) - 1 + } else { + if col > 0 { + col-- + } + } + if col < 0 { + col = 0 + } + pos := col + for _, chr := range line[:col] { + if chr == '\t' { + pos += 7 + } + } + fmt.Fprintf(&buffer, "%s\n%s\n%s\n", line, strings.Repeat(" ", pos)+"^", err.Error()) + + return err, true + } else { + return err, true + } + return nil, false +} + +func extractLine(input string, initPos int) string { + if initPos < 0 { + initPos = 0 + } + if initPos >= len(input) && len(input) > 0 { + initPos = len(input) - 1 + } + startPos := initPos + endPos := initPos + for ; startPos > 0; startPos-- { + if input[startPos] == '\n' { + if startPos != initPos { + startPos++ + break + } + } + } + for ; endPos < len(input); endPos++ { + if input[endPos] == '\n' { + if endPos == initPos { + endPos++ + } + break + } + } + return input[startPos:endPos] +} diff --git a/parser/grammar.peg b/parser/grammar.peg new file mode 100644 index 0000000..38b6dc8 --- /dev/null +++ b/parser/grammar.peg @@ -0,0 +1,126 @@ +{ + // Package parser parses conf language. + package parser + + import ( + "strconv" + "errors" + + "go.neonxp.ru/conf/model" + ) + + func toAnySlice(v any) []any { + if v == nil { + return nil + } + if v := v.([]any); len(v) == 0 { + return nil + } + + return v.([]any) + } +} + +Config ← _ stmts:( Command* ) EOF { + if stmts == nil { + return model.Group{}, nil + } + groupAny := toAnySlice(stmts) + groupSl := make(model.Group, len(groupAny)) + for i, e := range groupAny { + groupSl[i] = e.(model.Command) + } + return groupSl, nil +} + +Group ← '{' _ stmts:( Command* ) _ '}' { + if stmts == nil { + return model.Group{}, nil + } + groupAny := toAnySlice(stmts) + groupSl := make(model.Group, len(groupAny)) + for i, e := range groupAny { + groupSl[i] = e.(model.Command) + } + return groupSl, nil +} + +Command ← name:Ident _ args:Args _ { + var group model.Group + rawArgs := args.([]any) + argsSlice := make([]any, 0, len(rawArgs)) + if len(rawArgs) > 0 { + for _, s := range rawArgs { + if s == nil { + continue + } + if g, ok := s.(model.Group); ok { + group = g + continue + } + if l, ok := s.([]any); ok { + l = slices.DeleteFunc(l, func(x any) bool { return x == nil }) + argsSlice = append(argsSlice, l...) + } + } + } + return model.Command{Name: name.(model.Ident), Args: argsSlice, Group: group}, nil +} + +Args ← Values? (Group / EOS) + +Values ← vals:Value* { return toAnySlice(vals), nil } + +Value ← val:( Ident / String / Number / Boolean ) __ { return val, nil } +String ← ( '"' DoubleStringChar* '"' / "'" SingleStringChar "'" / '`' RawStringChar* '`' ) { + return string(c.text), nil +} + +// Сервисные литералы + +// {{{ Строки +DoubleStringChar ← !( '"' / "\\" / EOL ) SourceChar / "\\" DoubleStringEscape +SingleStringChar ← !( "'" / "\\" / EOL ) SourceChar / "\\" SingleStringEscape +RawStringChar ← !'`' SourceChar + +DoubleStringEscape ← '"' / CommonEscapeSequence +SingleStringEscape ← "'" / CommonEscapeSequence + +CommonEscapeSequence ← SingleCharEscape / OctalEscape / HexEscape / LongUnicodeEscape / ShortUnicodeEscape +SingleCharEscape ← 'a' / 'b' / 'n' / 'f' / 'r' / 't' / 'v' / '\\' +OctalEscape ← OctalDigit OctalDigit OctalDigit +HexEscape ← 'x' HexDigit HexDigit +LongUnicodeEscape ← 'U' HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit +ShortUnicodeEscape ← 'u' HexDigit HexDigit HexDigit HexDigit + +OctalDigit ← [0-7] +DecimalDigit ← [0-9] +HexDigit ← [0-9a-f]i +SourceChar ← . +// }}} + +// {{{ Числа +Number ← '-'? Integer ( '.' DecimalDigit+ )? Exponent? { return strconv.ParseFloat(string(c.text), 64) } +Integer ← '0' / NonZeroDecimalDigit DecimalDigit* +Exponent ← 'e'i [+-]? DecimalDigit+ +DecimalDigit ← [0-9] +NonZeroDecimalDigit ← [1-9] +// }}} + +// {{{ Идентификатор +Ident ← (Alpha / AllowedSpec) (Alpha / AllowedSpec / Number)* { return model.Ident(c.text), nil } +Alpha ← [a-zA-Z] +AllowedSpec ← '$' / '@' / '%' / '_' / '-' / '+' +// }}} + +Boolean ← "true" { return true, nil } / "false" { return false, nil } + +Comment ← '#' ( ![\r\n] . )* +__ ← ( Whitespace / EOL / Comment )* +_ ← ( [ \t\r\n] / Comment )* + +Whitespace ← [ \t\r] +EOL ← '\n' +EOS ← __ ';' { return nil, nil } / _ Comment? EOL { return nil, nil } / __ EOF { return nil, nil } + +EOF ← !.
\ No newline at end of file diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..e6abdaf --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,2767 @@ +// Code generated by pigeon; DO NOT EDIT. + +// Package parser parses conf language. +package parser + +import ( + "bytes" + "errors" + "fmt" + "io" + "math" + "os" + "slices" + "sort" + "strconv" + "strings" + "unicode" + "unicode/utf8" + + "go.neonxp.ru/conf/model" +) + +func toAnySlice(v any) []any { + if v == nil { + return nil + } + if v := v.([]any); len(v) == 0 { + return nil + } + + return v.([]any) +} + +var g = &grammar{ + rules: []*rule{ + { + name: "Config", + pos: position{line: 24, col: 1, offset: 355}, + expr: &actionExpr{ + pos: position{line: 24, col: 10, offset: 366}, + run: (*parser).callonConfig1, + expr: &seqExpr{ + pos: position{line: 24, col: 10, offset: 366}, + exprs: []any{ + &zeroOrMoreExpr{ + pos: position{line: 120, col: 5, offset: 3403}, + expr: &choiceExpr{ + pos: position{line: 120, col: 7, offset: 3405}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 120, col: 7, offset: 3405}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &labeledExpr{ + pos: position{line: 24, col: 12, offset: 368}, + label: "stmts", + expr: &zeroOrMoreExpr{ + pos: position{line: 24, col: 20, offset: 376}, + expr: &ruleRefExpr{ + pos: position{line: 24, col: 20, offset: 376}, + name: "Command", + }, + }, + }, + ¬Expr{ + pos: position{line: 126, col: 7, offset: 3575}, + expr: &anyMatcher{ + line: 126, col: 8, offset: 3576, + }, + }, + }, + }, + }, + }, + { + name: "Group", + pos: position{line: 36, col: 1, offset: 642}, + expr: &actionExpr{ + pos: position{line: 36, col: 9, offset: 652}, + run: (*parser).callonGroup1, + expr: &seqExpr{ + pos: position{line: 36, col: 9, offset: 652}, + exprs: []any{ + &litMatcher{ + pos: position{line: 36, col: 9, offset: 652}, + val: "{", + ignoreCase: false, + want: "\"{\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 120, col: 5, offset: 3403}, + expr: &choiceExpr{ + pos: position{line: 120, col: 7, offset: 3405}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 120, col: 7, offset: 3405}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &labeledExpr{ + pos: position{line: 36, col: 15, offset: 658}, + label: "stmts", + expr: &zeroOrMoreExpr{ + pos: position{line: 36, col: 23, offset: 666}, + expr: &ruleRefExpr{ + pos: position{line: 36, col: 23, offset: 666}, + name: "Command", + }, + }, + }, + &zeroOrMoreExpr{ + pos: position{line: 120, col: 5, offset: 3403}, + expr: &choiceExpr{ + pos: position{line: 120, col: 7, offset: 3405}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 120, col: 7, offset: 3405}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 36, col: 36, offset: 679}, + val: "}", + ignoreCase: false, + want: "\"}\"", + }, + }, + }, + }, + }, + { + name: "Command", + pos: position{line: 48, col: 1, offset: 934}, + expr: &actionExpr{ + pos: position{line: 48, col: 11, offset: 946}, + run: (*parser).callonCommand1, + expr: &seqExpr{ + pos: position{line: 48, col: 11, offset: 946}, + exprs: []any{ + &labeledExpr{ + pos: position{line: 48, col: 11, offset: 946}, + label: "name", + expr: &actionExpr{ + pos: position{line: 111, col: 9, offset: 3087}, + run: (*parser).callonCommand4, + expr: &seqExpr{ + pos: position{line: 111, col: 9, offset: 3087}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 112, col: 9, offset: 3187}, + val: "[$@%_-+a-zA-Z]", + chars: []rune{'$', '@', '%', '_', '-', '+'}, + ranges: []rune{'a', 'z', 'A', 'Z'}, + ignoreCase: false, + inverted: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 111, col: 31, offset: 3109}, + expr: &choiceExpr{ + pos: position{line: 111, col: 32, offset: 3110}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 112, col: 9, offset: 3187}, + val: "[$@%_-+a-zA-Z]", + chars: []rune{'$', '@', '%', '_', '-', '+'}, + ranges: []rune{'a', 'z', 'A', 'Z'}, + ignoreCase: false, + inverted: false, + }, + &actionExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + run: (*parser).callonCommand10, + expr: &seqExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + exprs: []any{ + &zeroOrOneExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + expr: &litMatcher{ + pos: position{line: 103, col: 10, offset: 2796}, + val: "-", + ignoreCase: false, + want: "\"-\"", + }, + }, + &choiceExpr{ + pos: position{line: 104, col: 11, offset: 2904}, + alternatives: []any{ + &litMatcher{ + pos: position{line: 104, col: 11, offset: 2904}, + val: "0", + ignoreCase: false, + want: "\"0\"", + }, + &seqExpr{ + pos: position{line: 104, col: 17, offset: 2910}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 107, col: 23, offset: 3029}, + val: "[1-9]", + ranges: []rune{'1', '9'}, + ignoreCase: false, + inverted: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 104, col: 37, offset: 2930}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 103, col: 23, offset: 2809}, + expr: &seqExpr{ + pos: position{line: 103, col: 25, offset: 2811}, + exprs: []any{ + &litMatcher{ + pos: position{line: 103, col: 25, offset: 2811}, + val: ".", + ignoreCase: false, + want: "\".\"", + }, + &oneOrMoreExpr{ + pos: position{line: 103, col: 29, offset: 2815}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 103, col: 46, offset: 2832}, + expr: &seqExpr{ + pos: position{line: 105, col: 12, offset: 2957}, + exprs: []any{ + &litMatcher{ + pos: position{line: 105, col: 12, offset: 2957}, + val: "e", + ignoreCase: true, + want: "\"e\"i", + }, + &zeroOrOneExpr{ + pos: position{line: 105, col: 17, offset: 2962}, + expr: &charClassMatcher{ + pos: position{line: 105, col: 17, offset: 2962}, + val: "[+-]", + chars: []rune{'+', '-'}, + ignoreCase: false, + inverted: false, + }, + }, + &oneOrMoreExpr{ + pos: position{line: 105, col: 23, offset: 2968}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &zeroOrMoreExpr{ + pos: position{line: 120, col: 5, offset: 3403}, + expr: &choiceExpr{ + pos: position{line: 120, col: 7, offset: 3405}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 120, col: 7, offset: 3405}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &labeledExpr{ + pos: position{line: 48, col: 24, offset: 959}, + label: "args", + expr: &ruleRefExpr{ + pos: position{line: 48, col: 29, offset: 964}, + name: "Args", + }, + }, + &zeroOrMoreExpr{ + pos: position{line: 120, col: 5, offset: 3403}, + expr: &choiceExpr{ + pos: position{line: 120, col: 7, offset: 3405}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 120, col: 7, offset: 3405}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "Args", + pos: position{line: 70, col: 1, offset: 1602}, + expr: &seqExpr{ + pos: position{line: 70, col: 8, offset: 1611}, + exprs: []any{ + &zeroOrOneExpr{ + pos: position{line: 70, col: 8, offset: 1611}, + expr: &actionExpr{ + pos: position{line: 72, col: 10, offset: 1645}, + run: (*parser).callonArgs3, + expr: &labeledExpr{ + pos: position{line: 72, col: 10, offset: 1645}, + label: "vals", + expr: &zeroOrMoreExpr{ + pos: position{line: 72, col: 15, offset: 1650}, + expr: &actionExpr{ + pos: position{line: 74, col: 9, offset: 1701}, + run: (*parser).callonArgs6, + expr: &seqExpr{ + pos: position{line: 74, col: 9, offset: 1701}, + exprs: []any{ + &labeledExpr{ + pos: position{line: 74, col: 9, offset: 1701}, + label: "val", + expr: &choiceExpr{ + pos: position{line: 74, col: 15, offset: 1707}, + alternatives: []any{ + &actionExpr{ + pos: position{line: 111, col: 9, offset: 3087}, + run: (*parser).callonArgs10, + expr: &seqExpr{ + pos: position{line: 111, col: 9, offset: 3087}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 112, col: 9, offset: 3187}, + val: "[$@%_-+a-zA-Z]", + chars: []rune{'$', '@', '%', '_', '-', '+'}, + ranges: []rune{'a', 'z', 'A', 'Z'}, + ignoreCase: false, + inverted: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 111, col: 31, offset: 3109}, + expr: &choiceExpr{ + pos: position{line: 111, col: 32, offset: 3110}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 112, col: 9, offset: 3187}, + val: "[$@%_-+a-zA-Z]", + chars: []rune{'$', '@', '%', '_', '-', '+'}, + ranges: []rune{'a', 'z', 'A', 'Z'}, + ignoreCase: false, + inverted: false, + }, + &actionExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + run: (*parser).callonArgs16, + expr: &seqExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + exprs: []any{ + &zeroOrOneExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + expr: &litMatcher{ + pos: position{line: 103, col: 10, offset: 2796}, + val: "-", + ignoreCase: false, + want: "\"-\"", + }, + }, + &choiceExpr{ + pos: position{line: 104, col: 11, offset: 2904}, + alternatives: []any{ + &litMatcher{ + pos: position{line: 104, col: 11, offset: 2904}, + val: "0", + ignoreCase: false, + want: "\"0\"", + }, + &seqExpr{ + pos: position{line: 104, col: 17, offset: 2910}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 107, col: 23, offset: 3029}, + val: "[1-9]", + ranges: []rune{'1', '9'}, + ignoreCase: false, + inverted: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 104, col: 37, offset: 2930}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 103, col: 23, offset: 2809}, + expr: &seqExpr{ + pos: position{line: 103, col: 25, offset: 2811}, + exprs: []any{ + &litMatcher{ + pos: position{line: 103, col: 25, offset: 2811}, + val: ".", + ignoreCase: false, + want: "\".\"", + }, + &oneOrMoreExpr{ + pos: position{line: 103, col: 29, offset: 2815}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 103, col: 46, offset: 2832}, + expr: &seqExpr{ + pos: position{line: 105, col: 12, offset: 2957}, + exprs: []any{ + &litMatcher{ + pos: position{line: 105, col: 12, offset: 2957}, + val: "e", + ignoreCase: true, + want: "\"e\"i", + }, + &zeroOrOneExpr{ + pos: position{line: 105, col: 17, offset: 2962}, + expr: &charClassMatcher{ + pos: position{line: 105, col: 17, offset: 2962}, + val: "[+-]", + chars: []rune{'+', '-'}, + ignoreCase: false, + inverted: false, + }, + }, + &oneOrMoreExpr{ + pos: position{line: 105, col: 23, offset: 2968}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &actionExpr{ + pos: position{line: 75, col: 10, offset: 1777}, + run: (*parser).callonArgs38, + expr: &choiceExpr{ + pos: position{line: 75, col: 12, offset: 1779}, + alternatives: []any{ + &seqExpr{ + pos: position{line: 75, col: 12, offset: 1779}, + exprs: []any{ + &litMatcher{ + pos: position{line: 75, col: 12, offset: 1779}, + val: "\"", + ignoreCase: false, + want: "\"\\\"\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 75, col: 16, offset: 1783}, + expr: &choiceExpr{ + pos: position{line: 82, col: 20, offset: 1976}, + alternatives: []any{ + &seqExpr{ + pos: position{line: 82, col: 20, offset: 1976}, + exprs: []any{ + ¬Expr{ + pos: position{line: 82, col: 20, offset: 1976}, + expr: &charClassMatcher{ + pos: position{line: 82, col: 23, offset: 1979}, + val: "[\"\\\\\\n]", + chars: []rune{'"', '\\', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 99, col: 14, offset: 2757, + }, + }, + }, + &seqExpr{ + pos: position{line: 82, col: 55, offset: 2011}, + exprs: []any{ + &litMatcher{ + pos: position{line: 82, col: 55, offset: 2011}, + val: "\\", + ignoreCase: false, + want: "\"\\\\\"", + }, + &choiceExpr{ + pos: position{line: 86, col: 22, offset: 2173}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 90, col: 20, offset: 2381}, + val: "[abnf\"rtv\\\\]", + chars: []rune{'a', 'b', 'n', 'f', '"', 'r', 't', 'v', '\\'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 91, col: 15, offset: 2444}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 96, col: 14, offset: 2690}, + val: "[0-7]", + ranges: []rune{'0', '7'}, + ignoreCase: false, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 96, col: 14, offset: 2690}, + val: "[0-7]", + ranges: []rune{'0', '7'}, + ignoreCase: false, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 96, col: 14, offset: 2690}, + val: "[0-7]", + ranges: []rune{'0', '7'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + &seqExpr{ + pos: position{line: 92, col: 13, offset: 2491}, + exprs: []any{ + &litMatcher{ + pos: position{line: 92, col: 13, offset: 2491}, + val: "x", + ignoreCase: false, + want: "\"x\"", + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + }, + }, + &seqExpr{ + pos: position{line: 93, col: 21, offset: 2535}, + exprs: []any{ + &litMatcher{ + pos: position{line: 93, col: 21, offset: 2535}, + val: "U", + ignoreCase: false, + want: "\"U\"", + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + }, + }, + &seqExpr{ + pos: position{line: 94, col: 22, offset: 2634}, + exprs: []any{ + &litMatcher{ + pos: position{line: 94, col: 22, offset: 2634}, + val: "u", + ignoreCase: false, + want: "\"u\"", + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 75, col: 34, offset: 1801}, + val: "\"", + ignoreCase: false, + want: "\"\\\"\"", + }, + }, + }, + &seqExpr{ + pos: position{line: 75, col: 40, offset: 1807}, + exprs: []any{ + &litMatcher{ + pos: position{line: 75, col: 40, offset: 1807}, + val: "'", + ignoreCase: false, + want: "\"'\"", + }, + &choiceExpr{ + pos: position{line: 83, col: 20, offset: 2056}, + alternatives: []any{ + &seqExpr{ + pos: position{line: 83, col: 20, offset: 2056}, + exprs: []any{ + ¬Expr{ + pos: position{line: 83, col: 20, offset: 2056}, + expr: &charClassMatcher{ + pos: position{line: 83, col: 23, offset: 2059}, + val: "[\\\\\\\\n]", + chars: []rune{'\'', '\\', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 99, col: 14, offset: 2757, + }, + }, + }, + &seqExpr{ + pos: position{line: 83, col: 55, offset: 2091}, + exprs: []any{ + &litMatcher{ + pos: position{line: 83, col: 55, offset: 2091}, + val: "\\", + ignoreCase: false, + want: "\"\\\\\"", + }, + &choiceExpr{ + pos: position{line: 87, col: 22, offset: 2223}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 90, col: 20, offset: 2381}, + val: "[abnf\\rtv\\\\]", + chars: []rune{'a', 'b', 'n', 'f', '\'', 'r', 't', 'v', '\\'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 91, col: 15, offset: 2444}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 96, col: 14, offset: 2690}, + val: "[0-7]", + ranges: []rune{'0', '7'}, + ignoreCase: false, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 96, col: 14, offset: 2690}, + val: "[0-7]", + ranges: []rune{'0', '7'}, + ignoreCase: false, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 96, col: 14, offset: 2690}, + val: "[0-7]", + ranges: []rune{'0', '7'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + &seqExpr{ + pos: position{line: 92, col: 13, offset: 2491}, + exprs: []any{ + &litMatcher{ + pos: position{line: 92, col: 13, offset: 2491}, + val: "x", + ignoreCase: false, + want: "\"x\"", + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + }, + }, + &seqExpr{ + pos: position{line: 93, col: 21, offset: 2535}, + exprs: []any{ + &litMatcher{ + pos: position{line: 93, col: 21, offset: 2535}, + val: "U", + ignoreCase: false, + want: "\"U\"", + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + }, + }, + &seqExpr{ + pos: position{line: 94, col: 22, offset: 2634}, + exprs: []any{ + &litMatcher{ + pos: position{line: 94, col: 22, offset: 2634}, + val: "u", + ignoreCase: false, + want: "\"u\"", + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + &charClassMatcher{ + pos: position{line: 98, col: 12, offset: 2732}, + val: "[0-9a-f]i", + ranges: []rune{'0', '9', 'a', 'f'}, + ignoreCase: true, + inverted: false, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 75, col: 61, offset: 1828}, + val: "'", + ignoreCase: false, + want: "\"'\"", + }, + }, + }, + &seqExpr{ + pos: position{line: 75, col: 67, offset: 1834}, + exprs: []any{ + &litMatcher{ + pos: position{line: 75, col: 67, offset: 1834}, + val: "`", + ignoreCase: false, + want: "\"`\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 75, col: 71, offset: 1838}, + expr: &seqExpr{ + pos: position{line: 84, col: 17, offset: 2133}, + exprs: []any{ + ¬Expr{ + pos: position{line: 84, col: 17, offset: 2133}, + expr: &litMatcher{ + pos: position{line: 84, col: 18, offset: 2134}, + val: "`", + ignoreCase: false, + want: "\"`\"", + }, + }, + &anyMatcher{ + line: 99, col: 14, offset: 2757, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 75, col: 86, offset: 1853}, + val: "`", + ignoreCase: false, + want: "\"`\"", + }, + }, + }, + }, + }, + }, + &actionExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + run: (*parser).callonArgs121, + expr: &seqExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + exprs: []any{ + &zeroOrOneExpr{ + pos: position{line: 103, col: 10, offset: 2796}, + expr: &litMatcher{ + pos: position{line: 103, col: 10, offset: 2796}, + val: "-", + ignoreCase: false, + want: "\"-\"", + }, + }, + &choiceExpr{ + pos: position{line: 104, col: 11, offset: 2904}, + alternatives: []any{ + &litMatcher{ + pos: position{line: 104, col: 11, offset: 2904}, + val: "0", + ignoreCase: false, + want: "\"0\"", + }, + &seqExpr{ + pos: position{line: 104, col: 17, offset: 2910}, + exprs: []any{ + &charClassMatcher{ + pos: position{line: 107, col: 23, offset: 3029}, + val: "[1-9]", + ranges: []rune{'1', '9'}, + ignoreCase: false, + inverted: false, + }, + &zeroOrMoreExpr{ + pos: position{line: 104, col: 37, offset: 2930}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 103, col: 23, offset: 2809}, + expr: &seqExpr{ + pos: position{line: 103, col: 25, offset: 2811}, + exprs: []any{ + &litMatcher{ + pos: position{line: 103, col: 25, offset: 2811}, + val: ".", + ignoreCase: false, + want: "\".\"", + }, + &oneOrMoreExpr{ + pos: position{line: 103, col: 29, offset: 2815}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 103, col: 46, offset: 2832}, + expr: &seqExpr{ + pos: position{line: 105, col: 12, offset: 2957}, + exprs: []any{ + &litMatcher{ + pos: position{line: 105, col: 12, offset: 2957}, + val: "e", + ignoreCase: true, + want: "\"e\"i", + }, + &zeroOrOneExpr{ + pos: position{line: 105, col: 17, offset: 2962}, + expr: &charClassMatcher{ + pos: position{line: 105, col: 17, offset: 2962}, + val: "[+-]", + chars: []rune{'+', '-'}, + ignoreCase: false, + inverted: false, + }, + }, + &oneOrMoreExpr{ + pos: position{line: 105, col: 23, offset: 2968}, + expr: &charClassMatcher{ + pos: position{line: 106, col: 16, offset: 2999}, + val: "[0-9]", + ranges: []rune{'0', '9'}, + ignoreCase: false, + inverted: false, + }, + }, + }, + }, + }, + }, + }, + }, + &actionExpr{ + pos: position{line: 116, col: 11, offset: 3266}, + run: (*parser).callonArgs143, + expr: &litMatcher{ + pos: position{line: 116, col: 11, offset: 3266}, + val: "true", + ignoreCase: false, + want: "\"true\"", + }, + }, + &actionExpr{ + pos: position{line: 116, col: 41, offset: 3296}, + run: (*parser).callonArgs145, + expr: &litMatcher{ + pos: position{line: 116, col: 41, offset: 3296}, + val: "false", + ignoreCase: false, + want: "\"false\"", + }, + }, + }, + }, + }, + &zeroOrMoreExpr{ + pos: position{line: 119, col: 6, offset: 3365}, + expr: &choiceExpr{ + pos: position{line: 119, col: 8, offset: 3367}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 122, col: 14, offset: 3444}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &choiceExpr{ + pos: position{line: 70, col: 17, offset: 1620}, + alternatives: []any{ + &ruleRefExpr{ + pos: position{line: 70, col: 17, offset: 1620}, + name: "Group", + }, + &actionExpr{ + pos: position{line: 124, col: 7, offset: 3473}, + run: (*parser).callonArgs159, + expr: &seqExpr{ + pos: position{line: 124, col: 7, offset: 3473}, + exprs: []any{ + &zeroOrMoreExpr{ + pos: position{line: 119, col: 6, offset: 3365}, + expr: &choiceExpr{ + pos: position{line: 119, col: 8, offset: 3367}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 122, col: 14, offset: 3444}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 124, col: 10, offset: 3476}, + val: ";", + ignoreCase: false, + want: "\";\"", + }, + }, + }, + }, + &actionExpr{ + pos: position{line: 124, col: 36, offset: 3502}, + run: (*parser).callonArgs172, + expr: &seqExpr{ + pos: position{line: 124, col: 36, offset: 3502}, + exprs: []any{ + &zeroOrMoreExpr{ + pos: position{line: 120, col: 5, offset: 3403}, + expr: &choiceExpr{ + pos: position{line: 120, col: 7, offset: 3405}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 120, col: 7, offset: 3405}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + &zeroOrOneExpr{ + pos: position{line: 124, col: 38, offset: 3504}, + expr: &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + &litMatcher{ + pos: position{line: 123, col: 7, offset: 3460}, + val: "\n", + ignoreCase: false, + want: "\"\\n\"", + }, + }, + }, + }, + &actionExpr{ + pos: position{line: 124, col: 73, offset: 3539}, + run: (*parser).callonArgs193, + expr: &seqExpr{ + pos: position{line: 124, col: 73, offset: 3539}, + exprs: []any{ + &zeroOrMoreExpr{ + pos: position{line: 119, col: 6, offset: 3365}, + expr: &choiceExpr{ + pos: position{line: 119, col: 8, offset: 3367}, + alternatives: []any{ + &charClassMatcher{ + pos: position{line: 122, col: 14, offset: 3444}, + val: "[ \\t\\r\\n]", + chars: []rune{' ', '\t', '\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + &seqExpr{ + pos: position{line: 118, col: 11, offset: 3339}, + exprs: []any{ + &litMatcher{ + pos: position{line: 118, col: 11, offset: 3339}, + val: "#", + ignoreCase: false, + want: "\"#\"", + }, + &zeroOrMoreExpr{ + pos: position{line: 118, col: 15, offset: 3343}, + expr: &seqExpr{ + pos: position{line: 118, col: 17, offset: 3345}, + exprs: []any{ + ¬Expr{ + pos: position{line: 118, col: 17, offset: 3345}, + expr: &charClassMatcher{ + pos: position{line: 118, col: 18, offset: 3346}, + val: "[\\r\\n]", + chars: []rune{'\r', '\n'}, + ignoreCase: false, + inverted: false, + }, + }, + &anyMatcher{ + line: 118, col: 25, offset: 3353, + }, + }, + }, + }, + }, + }, + }, + }, + }, + ¬Expr{ + pos: position{line: 126, col: 7, offset: 3575}, + expr: &anyMatcher{ + line: 126, col: 8, offset: 3576, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +func (c *current) onConfig1(stmts any) (any, error) { + if stmts == nil { + return model.Group{}, nil + } + groupAny := toAnySlice(stmts) + groupSl := make(model.Group, len(groupAny)) + for i, e := range groupAny { + groupSl[i] = e.(model.Command) + } + return groupSl, nil +} + +func (p *parser) callonConfig1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onConfig1(stack["stmts"]) +} + +func (c *current) onGroup1(stmts any) (any, error) { + if stmts == nil { + return model.Group{}, nil + } + groupAny := toAnySlice(stmts) + groupSl := make(model.Group, len(groupAny)) + for i, e := range groupAny { + groupSl[i] = e.(model.Command) + } + return groupSl, nil +} + +func (p *parser) callonGroup1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onGroup1(stack["stmts"]) +} + +func (c *current) onCommand10() (any, error) { + return strconv.ParseFloat(string(c.text), 64) +} + +func (p *parser) callonCommand10() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onCommand10() +} + +func (c *current) onCommand4() (any, error) { + return model.Ident(c.text), nil +} + +func (p *parser) callonCommand4() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onCommand4() +} + +func (c *current) onCommand1(name, args any) (any, error) { + var group model.Group + rawArgs := args.([]any) + argsSlice := make([]any, 0, len(rawArgs)) + if len(rawArgs) > 0 { + for _, s := range rawArgs { + if s == nil { + continue + } + if g, ok := s.(model.Group); ok { + group = g + continue + } + if l, ok := s.([]any); ok { + l = slices.DeleteFunc(l, func(x any) bool { return x == nil }) + argsSlice = append(argsSlice, l...) + } + } + } + return model.Command{Name: name.(model.Ident), Args: argsSlice, Group: group}, nil +} + +func (p *parser) callonCommand1() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onCommand1(stack["name"], stack["args"]) +} + +func (c *current) onArgs16() (any, error) { + return strconv.ParseFloat(string(c.text), 64) +} + +func (p *parser) callonArgs16() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs16() +} + +func (c *current) onArgs10() (any, error) { + return model.Ident(c.text), nil +} + +func (p *parser) callonArgs10() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs10() +} + +func (c *current) onArgs38() (any, error) { + return string(c.text), nil +} + +func (p *parser) callonArgs38() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs38() +} + +func (c *current) onArgs121() (any, error) { + return strconv.ParseFloat(string(c.text), 64) +} + +func (p *parser) callonArgs121() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs121() +} + +func (c *current) onArgs143() (any, error) { + return true, nil +} + +func (p *parser) callonArgs143() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs143() +} + +func (c *current) onArgs145() (any, error) { + return false, nil +} + +func (p *parser) callonArgs145() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs145() +} + +func (c *current) onArgs6(val any) (any, error) { + return val, nil +} + +func (p *parser) callonArgs6() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs6(stack["val"]) +} + +func (c *current) onArgs3(vals any) (any, error) { + return toAnySlice(vals), nil +} + +func (p *parser) callonArgs3() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs3(stack["vals"]) +} + +func (c *current) onArgs159() (any, error) { + return nil, nil +} + +func (p *parser) callonArgs159() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs159() +} + +func (c *current) onArgs172() (any, error) { + return nil, nil +} + +func (p *parser) callonArgs172() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs172() +} + +func (c *current) onArgs193() (any, error) { + return nil, nil +} + +func (p *parser) callonArgs193() (any, error) { + stack := p.vstack[len(p.vstack)-1] + _ = stack + return p.cur.onArgs193() +} + +var ( + // errNoRule is returned when the grammar to parse has no rule. + errNoRule = errors.New("grammar has no rule") + + // errInvalidEntrypoint is returned when the specified entrypoint rule + // does not exit. + errInvalidEntrypoint = errors.New("invalid entrypoint") + + // errInvalidEncoding is returned when the source is not properly + // utf8-encoded. + errInvalidEncoding = errors.New("invalid encoding") + + // errMaxExprCnt is used to signal that the maximum number of + // expressions have been parsed. + errMaxExprCnt = errors.New("max number of expressions parsed") +) + +// Option is a function that can set an option on the parser. It returns +// the previous setting as an Option. +type Option func(*parser) Option + +// MaxExpressions creates an Option to stop parsing after the provided +// number of expressions have been parsed, if the value is 0 then the parser will +// parse for as many steps as needed (possibly an infinite number). +// +// The default for maxExprCnt is 0. +func MaxExpressions(maxExprCnt uint64) Option { + return func(p *parser) Option { + oldMaxExprCnt := p.maxExprCnt + p.maxExprCnt = maxExprCnt + return MaxExpressions(oldMaxExprCnt) + } +} + +// Entrypoint creates an Option to set the rule name to use as entrypoint. +// The rule name must have been specified in the -alternate-entrypoints +// if generating the parser with the -optimize-grammar flag, otherwise +// it may have been optimized out. Passing an empty string sets the +// entrypoint to the first rule in the grammar. +// +// The default is to start parsing at the first rule in the grammar. +func Entrypoint(ruleName string) Option { + return func(p *parser) Option { + oldEntrypoint := p.entrypoint + p.entrypoint = ruleName + if ruleName == "" { + p.entrypoint = g.rules[0].name + } + return Entrypoint(oldEntrypoint) + } +} + +// AllowInvalidUTF8 creates an Option to allow invalid UTF-8 bytes. +// Every invalid UTF-8 byte is treated as a utf8.RuneError (U+FFFD) +// by character class matchers and is matched by the any matcher. +// The returned matched value, c.text and c.offset are NOT affected. +// +// The default is false. +func AllowInvalidUTF8(b bool) Option { + return func(p *parser) Option { + old := p.allowInvalidUTF8 + p.allowInvalidUTF8 = b + return AllowInvalidUTF8(old) + } +} + +// Recover creates an Option to set the recover flag to b. When set to +// true, this causes the parser to recover from panics and convert it +// to an error. Setting it to false can be useful while debugging to +// access the full stack trace. +// +// The default is true. +func Recover(b bool) Option { + return func(p *parser) Option { + old := p.recover + p.recover = b + return Recover(old) + } +} + +// GlobalStore creates an Option to set a key to a certain value in +// the globalStore. +func GlobalStore(key string, value any) Option { + return func(p *parser) Option { + old := p.cur.globalStore[key] + p.cur.globalStore[key] = value + return GlobalStore(key, old) + } +} + +// ParseFile parses the file identified by filename. +func ParseFile(filename string, opts ...Option) (i any, err error) { // nolint: deadcode + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer func() { + if closeErr := f.Close(); closeErr != nil { + err = closeErr + } + }() + return ParseReader(filename, f, opts...) +} + +// ParseReader parses the data from r using filename as information in the +// error messages. +func ParseReader(filename string, r io.Reader, opts ...Option) (any, error) { // nolint: deadcode + b, err := io.ReadAll(r) + if err != nil { + return nil, err + } + + return Parse(filename, b, opts...) +} + +// Parse parses the data from b using filename as information in the +// error messages. +func Parse(filename string, b []byte, opts ...Option) (any, error) { + return newParser(filename, b, opts...).parse(g) +} + +// position records a position in the text. +type position struct { + line, col, offset int +} + +func (p position) String() string { + return strconv.Itoa(p.line) + ":" + strconv.Itoa(p.col) + " [" + strconv.Itoa(p.offset) + "]" +} + +// savepoint stores all state required to go back to this point in the +// parser. +type savepoint struct { + position + rn rune + w int +} + +type current struct { + pos position // start position of the match + text []byte // raw text of the match + + // globalStore is a general store for the user to store arbitrary key-value + // pairs that they need to manage and that they do not want tied to the + // backtracking of the parser. This is only modified by the user and never + // rolled back by the parser. It is always up to the user to keep this in a + // consistent state. + globalStore storeDict +} + +type storeDict map[string]any + +// the AST types... + +// nolint: structcheck +type grammar struct { + pos position + rules []*rule +} + +// nolint: structcheck +type rule struct { + pos position + name string + displayName string + expr any +} + +// nolint: structcheck +type choiceExpr struct { + pos position + alternatives []any +} + +// nolint: structcheck +type actionExpr struct { + pos position + expr any + run func(*parser) (any, error) +} + +// nolint: structcheck +type recoveryExpr struct { + pos position + expr any + recoverExpr any + failureLabel []string +} + +// nolint: structcheck +type seqExpr struct { + pos position + exprs []any +} + +// nolint: structcheck +type throwExpr struct { + pos position + label string +} + +// nolint: structcheck +type labeledExpr struct { + pos position + label string + expr any +} + +// nolint: structcheck +type expr struct { + pos position + expr any +} + +type ( + andExpr expr // nolint: structcheck + notExpr expr // nolint: structcheck + zeroOrOneExpr expr // nolint: structcheck + zeroOrMoreExpr expr // nolint: structcheck + oneOrMoreExpr expr // nolint: structcheck +) + +// nolint: structcheck +type ruleRefExpr struct { + pos position + name string +} + +// nolint: structcheck +type andCodeExpr struct { + pos position + run func(*parser) (bool, error) +} + +// nolint: structcheck +type notCodeExpr struct { + pos position + run func(*parser) (bool, error) +} + +// nolint: structcheck +type litMatcher struct { + pos position + val string + ignoreCase bool + want string +} + +// nolint: structcheck +type charClassMatcher struct { + pos position + val string + basicLatinChars [128]bool + chars []rune + ranges []rune + classes []*unicode.RangeTable + ignoreCase bool + inverted bool +} + +type anyMatcher position // nolint: structcheck + +// errList cumulates the errors found by the parser. +type errList []error + +func (e *errList) add(err error) { + *e = append(*e, err) +} + +func (e errList) err() error { + if len(e) == 0 { + return nil + } + e.dedupe() + return e +} + +func (e *errList) dedupe() { + var cleaned []error + set := make(map[string]bool) + for _, err := range *e { + if msg := err.Error(); !set[msg] { + set[msg] = true + cleaned = append(cleaned, err) + } + } + *e = cleaned +} + +func (e errList) Error() string { + switch len(e) { + case 0: + return "" + case 1: + return e[0].Error() + default: + var buf bytes.Buffer + + for i, err := range e { + if i > 0 { + buf.WriteRune('\n') + } + buf.WriteString(err.Error()) + } + return buf.String() + } +} + +// parserError wraps an error with a prefix indicating the rule in which +// the error occurred. The original error is stored in the Inner field. +type parserError struct { + Inner error + pos position + prefix string + expected []string +} + +// Error returns the error message. +func (p *parserError) Error() string { + return p.prefix + ": " + p.Inner.Error() +} + +// newParser creates a parser with the specified input source and options. +func newParser(filename string, b []byte, opts ...Option) *parser { + stats := Stats{ + ChoiceAltCnt: make(map[string]map[string]int), + } + + p := &parser{ + filename: filename, + errs: new(errList), + data: b, + pt: savepoint{position: position{line: 1}}, + recover: true, + cur: current{ + globalStore: make(storeDict), + }, + maxFailPos: position{col: 1, line: 1}, + maxFailExpected: make([]string, 0, 20), + Stats: &stats, + // start rule is rule [0] unless an alternate entrypoint is specified + entrypoint: g.rules[0].name, + } + p.setOptions(opts) + + if p.maxExprCnt == 0 { + p.maxExprCnt = math.MaxUint64 + } + + return p +} + +// setOptions applies the options to the parser. +func (p *parser) setOptions(opts []Option) { + for _, opt := range opts { + opt(p) + } +} + +// nolint: structcheck,deadcode +type resultTuple struct { + v any + b bool + end savepoint +} + +// nolint: varcheck +const choiceNoMatch = -1 + +// Stats stores some statistics, gathered during parsing +type Stats struct { + // ExprCnt counts the number of expressions processed during parsing + // This value is compared to the maximum number of expressions allowed + // (set by the MaxExpressions option). + ExprCnt uint64 + + // ChoiceAltCnt is used to count for each ordered choice expression, + // which alternative is used how may times. + // These numbers allow to optimize the order of the ordered choice expression + // to increase the performance of the parser + // + // The outer key of ChoiceAltCnt is composed of the name of the rule as well + // as the line and the column of the ordered choice. + // The inner key of ChoiceAltCnt is the number (one-based) of the matching alternative. + // For each alternative the number of matches are counted. If an ordered choice does not + // match, a special counter is incremented. The name of this counter is set with + // the parser option Statistics. + // For an alternative to be included in ChoiceAltCnt, it has to match at least once. + ChoiceAltCnt map[string]map[string]int +} + +// nolint: structcheck,maligned +type parser struct { + filename string + pt savepoint + cur current + + data []byte + errs *errList + + depth int + recover bool + + // rules table, maps the rule identifier to the rule node + rules map[string]*rule + // variables stack, map of label to value + vstack []map[string]any + // rule stack, allows identification of the current rule in errors + rstack []*rule + + // parse fail + maxFailPos position + maxFailExpected []string + maxFailInvertExpected bool + + // max number of expressions to be parsed + maxExprCnt uint64 + // entrypoint for the parser + entrypoint string + + allowInvalidUTF8 bool + + *Stats + + choiceNoMatch string + // recovery expression stack, keeps track of the currently available recovery expression, these are traversed in reverse + recoveryStack []map[string]any +} + +// push a variable set on the vstack. +func (p *parser) pushV() { + if cap(p.vstack) == len(p.vstack) { + // create new empty slot in the stack + p.vstack = append(p.vstack, nil) + } else { + // slice to 1 more + p.vstack = p.vstack[:len(p.vstack)+1] + } + + // get the last args set + m := p.vstack[len(p.vstack)-1] + if m != nil && len(m) == 0 { + // empty map, all good + return + } + + m = make(map[string]any) + p.vstack[len(p.vstack)-1] = m +} + +// pop a variable set from the vstack. +func (p *parser) popV() { + // if the map is not empty, clear it + m := p.vstack[len(p.vstack)-1] + if len(m) > 0 { + // GC that map + p.vstack[len(p.vstack)-1] = nil + } + p.vstack = p.vstack[:len(p.vstack)-1] +} + +// push a recovery expression with its labels to the recoveryStack +func (p *parser) pushRecovery(labels []string, expr any) { + if cap(p.recoveryStack) == len(p.recoveryStack) { + // create new empty slot in the stack + p.recoveryStack = append(p.recoveryStack, nil) + } else { + // slice to 1 more + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)+1] + } + + m := make(map[string]any, len(labels)) + for _, fl := range labels { + m[fl] = expr + } + p.recoveryStack[len(p.recoveryStack)-1] = m +} + +// pop a recovery expression from the recoveryStack +func (p *parser) popRecovery() { + // GC that map + p.recoveryStack[len(p.recoveryStack)-1] = nil + + p.recoveryStack = p.recoveryStack[:len(p.recoveryStack)-1] +} + +func (p *parser) addErr(err error) { + p.addErrAt(err, p.pt.position, []string{}) +} + +func (p *parser) addErrAt(err error, pos position, expected []string) { + var buf bytes.Buffer + if p.filename != "" { + buf.WriteString(p.filename) + } + if buf.Len() > 0 { + buf.WriteString(":") + } + buf.WriteString(fmt.Sprintf("%d:%d (%d)", pos.line, pos.col, pos.offset)) + if len(p.rstack) > 0 { + if buf.Len() > 0 { + buf.WriteString(": ") + } + rule := p.rstack[len(p.rstack)-1] + if rule.displayName != "" { + buf.WriteString("rule " + rule.displayName) + } else { + buf.WriteString("rule " + rule.name) + } + } + pe := &parserError{Inner: err, pos: pos, prefix: buf.String(), expected: expected} + p.errs.add(pe) +} + +func (p *parser) failAt(fail bool, pos position, want string) { + // process fail if parsing fails and not inverted or parsing succeeds and invert is set + if fail == p.maxFailInvertExpected { + if pos.offset < p.maxFailPos.offset { + return + } + + if pos.offset > p.maxFailPos.offset { + p.maxFailPos = pos + p.maxFailExpected = p.maxFailExpected[:0] + } + + if p.maxFailInvertExpected { + want = "!" + want + } + p.maxFailExpected = append(p.maxFailExpected, want) + } +} + +// read advances the parser to the next rune. +func (p *parser) read() { + p.pt.offset += p.pt.w + rn, n := utf8.DecodeRune(p.data[p.pt.offset:]) + p.pt.rn = rn + p.pt.w = n + p.pt.col++ + if rn == '\n' { + p.pt.line++ + p.pt.col = 0 + } + + if rn == utf8.RuneError && n == 1 { // see utf8.DecodeRune + if !p.allowInvalidUTF8 { + p.addErr(errInvalidEncoding) + } + } +} + +// restore parser position to the savepoint pt. +func (p *parser) restore(pt savepoint) { + if pt.offset == p.pt.offset { + return + } + p.pt = pt +} + +// get the slice of bytes from the savepoint start to the current position. +func (p *parser) sliceFrom(start savepoint) []byte { + return p.data[start.position.offset:p.pt.position.offset] +} + +func (p *parser) buildRulesTable(g *grammar) { + p.rules = make(map[string]*rule, len(g.rules)) + for _, r := range g.rules { + p.rules[r.name] = r + } +} + +// nolint: gocyclo +func (p *parser) parse(g *grammar) (val any, err error) { + if len(g.rules) == 0 { + p.addErr(errNoRule) + return nil, p.errs.err() + } + + // TODO : not super critical but this could be generated + p.buildRulesTable(g) + + if p.recover { + // panic can be used in action code to stop parsing immediately + // and return the panic as an error. + defer func() { + if e := recover(); e != nil { + val = nil + switch e := e.(type) { + case error: + p.addErr(e) + default: + p.addErr(fmt.Errorf("%v", e)) + } + err = p.errs.err() + } + }() + } + + startRule, ok := p.rules[p.entrypoint] + if !ok { + p.addErr(errInvalidEntrypoint) + return nil, p.errs.err() + } + + p.read() // advance to first rune + val, ok = p.parseRuleWrap(startRule) + if !ok { + if len(*p.errs) == 0 { + // If parsing fails, but no errors have been recorded, the expected values + // for the farthest parser position are returned as error. + maxFailExpectedMap := make(map[string]struct{}, len(p.maxFailExpected)) + for _, v := range p.maxFailExpected { + maxFailExpectedMap[v] = struct{}{} + } + expected := make([]string, 0, len(maxFailExpectedMap)) + eof := false + if _, ok := maxFailExpectedMap["!."]; ok { + delete(maxFailExpectedMap, "!.") + eof = true + } + for k := range maxFailExpectedMap { + expected = append(expected, k) + } + sort.Strings(expected) + if eof { + expected = append(expected, "EOF") + } + p.addErrAt(errors.New("no match found, expected: "+listJoin(expected, ", ", "or")), p.maxFailPos, expected) + } + + return nil, p.errs.err() + } + return val, p.errs.err() +} + +func listJoin(list []string, sep string, lastSep string) string { + switch len(list) { + case 0: + return "" + case 1: + return list[0] + default: + return strings.Join(list[:len(list)-1], sep) + " " + lastSep + " " + list[len(list)-1] + } +} + +func (p *parser) parseRuleWrap(rule *rule) (any, bool) { + var ( + val any + ok bool + ) + + val, ok = p.parseRule(rule) + + return val, ok +} + +func (p *parser) parseRule(rule *rule) (any, bool) { + p.rstack = append(p.rstack, rule) + p.pushV() + val, ok := p.parseExprWrap(rule.expr) + p.popV() + p.rstack = p.rstack[:len(p.rstack)-1] + return val, ok +} + +func (p *parser) parseExprWrap(expr any) (any, bool) { + val, ok := p.parseExpr(expr) + + return val, ok +} + +// nolint: gocyclo +func (p *parser) parseExpr(expr any) (any, bool) { + p.ExprCnt++ + if p.ExprCnt > p.maxExprCnt { + panic(errMaxExprCnt) + } + + var val any + var ok bool + switch expr := expr.(type) { + case *actionExpr: + val, ok = p.parseActionExpr(expr) + case *andCodeExpr: + val, ok = p.parseAndCodeExpr(expr) + case *andExpr: + val, ok = p.parseAndExpr(expr) + case *anyMatcher: + val, ok = p.parseAnyMatcher(expr) + case *charClassMatcher: + val, ok = p.parseCharClassMatcher(expr) + case *choiceExpr: + val, ok = p.parseChoiceExpr(expr) + case *labeledExpr: + val, ok = p.parseLabeledExpr(expr) + case *litMatcher: + val, ok = p.parseLitMatcher(expr) + case *notCodeExpr: + val, ok = p.parseNotCodeExpr(expr) + case *notExpr: + val, ok = p.parseNotExpr(expr) + case *oneOrMoreExpr: + val, ok = p.parseOneOrMoreExpr(expr) + case *recoveryExpr: + val, ok = p.parseRecoveryExpr(expr) + case *ruleRefExpr: + val, ok = p.parseRuleRefExpr(expr) + case *seqExpr: + val, ok = p.parseSeqExpr(expr) + case *throwExpr: + val, ok = p.parseThrowExpr(expr) + case *zeroOrMoreExpr: + val, ok = p.parseZeroOrMoreExpr(expr) + case *zeroOrOneExpr: + val, ok = p.parseZeroOrOneExpr(expr) + default: + panic(fmt.Sprintf("unknown expression type %T", expr)) + } + return val, ok +} + +func (p *parser) parseActionExpr(act *actionExpr) (any, bool) { + start := p.pt + val, ok := p.parseExprWrap(act.expr) + if ok { + p.cur.pos = start.position + p.cur.text = p.sliceFrom(start) + actVal, err := act.run(p) + if err != nil { + p.addErrAt(err, start.position, []string{}) + } + + val = actVal + } + return val, ok +} + +func (p *parser) parseAndCodeExpr(and *andCodeExpr) (any, bool) { + + ok, err := and.run(p) + if err != nil { + p.addErr(err) + } + + return nil, ok +} + +func (p *parser) parseAndExpr(and *andExpr) (any, bool) { + pt := p.pt + p.pushV() + _, ok := p.parseExprWrap(and.expr) + p.popV() + p.restore(pt) + + return nil, ok +} + +func (p *parser) parseAnyMatcher(any *anyMatcher) (any, bool) { + if p.pt.rn == utf8.RuneError && p.pt.w == 0 { + // EOF - see utf8.DecodeRune + p.failAt(false, p.pt.position, ".") + return nil, false + } + start := p.pt + p.read() + p.failAt(true, start.position, ".") + return p.sliceFrom(start), true +} + +// nolint: gocyclo +func (p *parser) parseCharClassMatcher(chr *charClassMatcher) (any, bool) { + cur := p.pt.rn + start := p.pt + + // can't match EOF + if cur == utf8.RuneError && p.pt.w == 0 { // see utf8.DecodeRune + p.failAt(false, start.position, chr.val) + return nil, false + } + + if chr.ignoreCase { + cur = unicode.ToLower(cur) + } + + // try to match in the list of available chars + for _, rn := range chr.chars { + if rn == cur { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + // try to match in the list of ranges + for i := 0; i < len(chr.ranges); i += 2 { + if cur >= chr.ranges[i] && cur <= chr.ranges[i+1] { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + // try to match in the list of Unicode classes + for _, cl := range chr.classes { + if unicode.Is(cl, cur) { + if chr.inverted { + p.failAt(false, start.position, chr.val) + return nil, false + } + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + } + + if chr.inverted { + p.read() + p.failAt(true, start.position, chr.val) + return p.sliceFrom(start), true + } + p.failAt(false, start.position, chr.val) + return nil, false +} + +func (p *parser) parseChoiceExpr(ch *choiceExpr) (any, bool) { + + for altI, alt := range ch.alternatives { + // dummy assignment to prevent compile error if optimized + _ = altI + + p.pushV() + val, ok := p.parseExprWrap(alt) + p.popV() + if ok { + return val, ok + } + } + return nil, false +} + +func (p *parser) parseLabeledExpr(lab *labeledExpr) (any, bool) { + p.pushV() + val, ok := p.parseExprWrap(lab.expr) + p.popV() + if ok && lab.label != "" { + m := p.vstack[len(p.vstack)-1] + m[lab.label] = val + } + return val, ok +} + +func (p *parser) parseLitMatcher(lit *litMatcher) (any, bool) { + start := p.pt + for _, want := range lit.val { + cur := p.pt.rn + if lit.ignoreCase { + cur = unicode.ToLower(cur) + } + if cur != want { + p.failAt(false, start.position, lit.want) + p.restore(start) + return nil, false + } + p.read() + } + p.failAt(true, start.position, lit.want) + return p.sliceFrom(start), true +} + +func (p *parser) parseNotCodeExpr(not *notCodeExpr) (any, bool) { + ok, err := not.run(p) + if err != nil { + p.addErr(err) + } + + return nil, !ok +} + +func (p *parser) parseNotExpr(not *notExpr) (any, bool) { + pt := p.pt + p.pushV() + p.maxFailInvertExpected = !p.maxFailInvertExpected + _, ok := p.parseExprWrap(not.expr) + p.maxFailInvertExpected = !p.maxFailInvertExpected + p.popV() + p.restore(pt) + + return nil, !ok +} + +func (p *parser) parseOneOrMoreExpr(expr *oneOrMoreExpr) (any, bool) { + var vals []any + + for { + p.pushV() + val, ok := p.parseExprWrap(expr.expr) + p.popV() + if !ok { + if len(vals) == 0 { + // did not match once, no match + return nil, false + } + return vals, true + } + vals = append(vals, val) + } +} + +func (p *parser) parseRecoveryExpr(recover *recoveryExpr) (any, bool) { + + p.pushRecovery(recover.failureLabel, recover.recoverExpr) + val, ok := p.parseExprWrap(recover.expr) + p.popRecovery() + + return val, ok +} + +func (p *parser) parseRuleRefExpr(ref *ruleRefExpr) (any, bool) { + if ref.name == "" { + panic(fmt.Sprintf("%s: invalid rule: missing name", ref.pos)) + } + + rule := p.rules[ref.name] + if rule == nil { + p.addErr(fmt.Errorf("undefined rule: %s", ref.name)) + return nil, false + } + return p.parseRuleWrap(rule) +} + +func (p *parser) parseSeqExpr(seq *seqExpr) (any, bool) { + vals := make([]any, 0, len(seq.exprs)) + + pt := p.pt + for _, expr := range seq.exprs { + val, ok := p.parseExprWrap(expr) + if !ok { + p.restore(pt) + return nil, false + } + vals = append(vals, val) + } + return vals, true +} + +func (p *parser) parseThrowExpr(expr *throwExpr) (any, bool) { + + for i := len(p.recoveryStack) - 1; i >= 0; i-- { + if recoverExpr, ok := p.recoveryStack[i][expr.label]; ok { + if val, ok := p.parseExprWrap(recoverExpr); ok { + return val, ok + } + } + } + + return nil, false +} + +func (p *parser) parseZeroOrMoreExpr(expr *zeroOrMoreExpr) (any, bool) { + var vals []any + + for { + p.pushV() + val, ok := p.parseExprWrap(expr.expr) + p.popV() + if !ok { + return vals, true + } + vals = append(vals, val) + } +} + +func (p *parser) parseZeroOrOneExpr(expr *zeroOrOneExpr) (any, bool) { + p.pushV() + val, _ := p.parseExprWrap(expr.expr) + p.popV() + // whether it matched or not, consider it a match + return val, true +} diff --git a/visitor/default.go b/visitor/default.go deleted file mode 100644 index d8c8172..0000000 --- a/visitor/default.go +++ /dev/null @@ -1,63 +0,0 @@ -package visitor - -import ( - "errors" - "fmt" - "strings" - - "go.neonxp.ru/conf/model" -) - -var ( - ErrEmptyQuery = errors.New("empty query") - ErrNoChildKey = errors.New("no child key") -) - -func NewDefault() *Default { - return &Default{ - vars: map[string]model.Values{}, - children: map[string]*Default{}, - args: model.Values{}, - } -} - -// Default просто собирает рекурсивно все переменные в дерево. -// На самом деле, для большинства сценариев конфигов его должно хватить. -type Default struct { - vars map[string]model.Values - children map[string]*Default - args model.Values -} - -func (p *Default) VisitDirective(ident string, args model.Values, body model.Body) error { - p.children[ident] = NewDefault() - p.children[ident].args = args - return body.Execute(p.children[ident]) -} - -func (p *Default) VisitSetting(key string, values model.Values) error { - p.vars[key] = values - - return nil -} - -func (p *Default) Get(path string) (model.Values, error) { - splitPath := strings.SplitN(path, ".", 2) - switch len(splitPath) { - case 1: - if v, ok := p.vars[splitPath[0]]; ok { - return v, nil - } - if child, ok := p.children[splitPath[0]]; ok { - return child.args, nil - } - return nil, fmt.Errorf("%w: %s", ErrNoChildKey, splitPath[0]) - case 2: - if child, ok := p.children[splitPath[0]]; ok { - return child.Get(splitPath[1]) - } - return nil, fmt.Errorf("%w: %s", ErrNoChildKey, splitPath[0]) - default: - return nil, ErrEmptyQuery - } -} |
