{ // Package parser parses conf language. // // This file is part of conf library. // Copyright (C) 2026 Alexander NeonXP Kiryukhin // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . package parser import ( "strconv" "errors" "go.neonxp.ru/conf/model" ) func toAnySlice(v any) []any { if v == nil { return nil } if v, ok := v.([]any); ok && len(v) == 0 { return nil } return v.([]any) } } Config ← _ directives:( Directive* ) EOF { if directives == nil { return model.Group{}, nil } groupAny := toAnySlice(directives) groupSl := make(model.Group, len(groupAny)) for i, e := range groupAny { groupSl[i] = e.(model.Directive) } return groupSl, nil } Group ← '{' _ directives:( Directive* ) _ '}' { if directives == nil { return model.Group{}, nil } groupAny := toAnySlice(directives) groupSl := make(model.Group, len(groupAny)) for i, e := range groupAny { groupSl[i] = e.(model.Directive) } return groupSl, nil } Directive ← name:Ident _ args:Args _ { rawArgs := args.([]any) argsSlice := make([]any, 0, len(rawArgs)) if len(rawArgs) > 0 { for _, s := range rawArgs { if s == nil { continue } if l, ok := s.([]any); ok { l = slices.DeleteFunc(l, func(x any) bool { return x == nil }) argsSlice = append(argsSlice, l...) } } } return model.Directive{Name: name.(model.Ident), RawValues: argsSlice}, nil } Args ← Values? (Group / EOS) Values ← vals:Value* { return toAnySlice(vals), nil } Value ← val:( Ident / String / Number ) __ { return val, nil } // {{{ Строки String ← ( '"' DoubleStringChar* '"' / "'" SingleStringChar "'" / '`' RawStringChar* '`' ) { return strconv.Unquote(string(c.text)) } 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] HexDigit ← [0-9a-f]i SourceChar ← . // }}} // {{{ Числа Number ← '-'? UInteger ('.' DecimalDigit+)? Exponent? { if i, err := strconv.Atoi(string(c.text)); err == nil { return i, nil } return strconv.ParseFloat(string(c.text), 64) } UInteger ← '0' / NonZeroDecimalDigit DecimalDigit* Exponent ← 'e'i [+-]? DecimalDigit+ DecimalDigit ← [0-9] NonZeroDecimalDigit ← [1-9] // }}} // {{{ Идентификатор Ident ← (Alpha / AllowedSpec) (Alpha / AllowedSpec / Number)* { switch string(c.text) { case "true": return true, nil case "false": return false, nil default: return model.Ident(c.text), nil } } Alpha ← [a-zA-Z] AllowedSpec ← '$' / '@' / '%' / '_' / '-' / '+' // }}} // {{{ Оставшиеся спец моменты 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 ← !. // }}}