aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2026-03-09 23:05:42 +0300
committer2026-03-09 23:05:42 +0300
commit00394a80501960ad26787b5c44435ed5ed67ad84 (patch)
tree672eb918c552c858f32e9533dc3799af6b75769f
parent'-' sign in words accepted (diff)
downloadconf-0.1.0.tar.gz
conf-0.1.0.tar.bz2
conf-0.1.0.tar.xz
conf-0.1.0.zip
Полностью переписал библиотеку. Перевёл с EBNF на PEG.v0.1.0
-rw-r--r--README.md431
-rw-r--r--config.ebnf30
-rw-r--r--example/file.conf33
-rw-r--r--example/file2.conf7
-rw-r--r--example/main.go27
-rw-r--r--gen.go2
-rw-r--r--go.mod12
-rw-r--r--go.sum8
-rw-r--r--internal/ast/processor.go119
-rw-r--r--internal/ast/tree.go43
-rw-r--r--internal/parser/parser.go1803
-rw-r--r--loader.go40
-rw-r--r--loader_test.go64
-rw-r--r--model/body.go28
-rw-r--r--model/directive.go9
-rw-r--r--model/lookup.go56
-rw-r--r--model/model.go44
-rw-r--r--model/setting.go6
-rw-r--r--model/value.go85
-rw-r--r--model/visitor.go6
-rw-r--r--parser/errors.go107
-rw-r--r--parser/grammar.peg126
-rw-r--r--parser/parser.go2767
-rw-r--r--visitor/default.go63
24 files changed, 3428 insertions, 2488 deletions
diff --git a/README.md b/README.md
index 267b226..773c77a 100644
--- a/README.md
+++ b/README.md
@@ -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())
-}
diff --git a/gen.go b/gen.go
index 04c9e93..fcbf8c8 100644
--- a/gen.go
+++ b/gen.go
@@ -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
diff --git a/go.mod b/go.mod
index c1e462d..69c05e7 100644
--- a/go.mod
+++ b/go.mod
@@ -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
diff --git a/go.sum b/go.sum
index 08246e5..328c5c1 100644
--- a/go.sum
+++ b/go.sum
@@ -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())
- }
-}
diff --git a/loader.go b/loader.go
index 7f58237..99a8276 100644
--- a/loader.go
+++ b/loader.go
@@ -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{
+ &notExpr{
+ 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",
+ },
+ },
+ },
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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{
+ &notExpr{
+ 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,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ &notExpr{
+ 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
- }
-}