ref 4
This commit is contained in:
parent
a531dedc5c
commit
88c917210d
118
config.go
118
config.go
|
@ -189,91 +189,8 @@ func (c *Config) GetMap(key string) (map[string]any, error) {
|
||||||
|
|
||||||
// Load parses a config from a reader
|
// Load parses a config from a reader
|
||||||
func Load(r io.Reader) (*Config, error) {
|
func Load(r io.Reader) (*Config, error) {
|
||||||
scanner := NewScanner(r)
|
parser := NewParser(r)
|
||||||
config := NewConfig()
|
return parser.Parse()
|
||||||
|
|
||||||
for {
|
|
||||||
err := scanner.SkipWhitespace()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := scanner.PeekByte()
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle comments
|
|
||||||
if b == '-' {
|
|
||||||
peekBytes, err := scanner.PeekBytes(2)
|
|
||||||
if err == nil && len(peekBytes) == 2 && peekBytes[1] == '-' {
|
|
||||||
err = scanner.scanComment()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process key-value pair
|
|
||||||
if isLetter(b) {
|
|
||||||
// Read name
|
|
||||||
nameToken, err := scanner.scanName(scanner.line, scanner.col)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
name := string(nameToken.Value)
|
|
||||||
|
|
||||||
// Skip whitespace
|
|
||||||
err = scanner.SkipWhitespace()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be followed by = or {
|
|
||||||
b, err = scanner.PeekByte()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b != '=' && b != '{' {
|
|
||||||
return nil, scanner.Error("expected '=' or '{' after name")
|
|
||||||
}
|
|
||||||
|
|
||||||
var value any
|
|
||||||
if b == '=' {
|
|
||||||
_, _ = scanner.ReadByte() // consume =
|
|
||||||
err = scanner.SkipWhitespace()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err = scanner.ScanValue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else { // b == '{'
|
|
||||||
_, _ = scanner.ReadByte() // consume {
|
|
||||||
value, err = scanner.scanObjectOrArray()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store in config
|
|
||||||
config.data[name] = value
|
|
||||||
} else {
|
|
||||||
return nil, scanner.Error("expected name at top level")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
@ -286,11 +203,36 @@ func isDigit(b byte) bool {
|
||||||
return b >= '0' && b <= '9'
|
return b >= '0' && b <= '9'
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasDot(s string) bool {
|
// ParseNumber converts a string to a number (int64 or float64)
|
||||||
|
func ParseNumber(s string) (any, error) {
|
||||||
|
// Check if it has a decimal point
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
if s[i] == '.' {
|
if s[i] == '.' {
|
||||||
return true
|
// It's a float
|
||||||
|
return strconv.ParseFloat(s, 64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
// It's an integer
|
||||||
|
return strconv.ParseInt(s, 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDigitOrMinus checks if a string starts with a digit or minus sign
|
||||||
|
func isDigitOrMinus(s string) bool {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isDigit(s[0]) || (s[0] == '-' && len(s) > 1 && isDigit(s[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseStringAsNumber tries to parse a string as a number (float or int)
|
||||||
|
func parseStringAsNumber(s string) (any, error) {
|
||||||
|
// Check if it has a decimal point
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
if s[i] == '.' {
|
||||||
|
// It's a float
|
||||||
|
return strconv.ParseFloat(s, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// It's an integer
|
||||||
|
return strconv.ParseInt(s, 10, 64)
|
||||||
}
|
}
|
||||||
|
|
65
parser.go
65
parser.go
|
@ -41,21 +41,6 @@ func (p *Parser) Parse() (*Config, error) {
|
||||||
return p.config, nil
|
return p.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pushObject enters a new object scope
|
|
||||||
func (p *Parser) pushObject(obj map[string]any) {
|
|
||||||
p.stack = append(p.stack, p.currentObject)
|
|
||||||
p.currentObject = obj
|
|
||||||
}
|
|
||||||
|
|
||||||
// popObject exits the current object scope
|
|
||||||
func (p *Parser) popObject() {
|
|
||||||
n := len(p.stack)
|
|
||||||
if n > 0 {
|
|
||||||
p.currentObject = p.stack[n-1]
|
|
||||||
p.stack = p.stack[:n-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextToken gets the next meaningful token (skipping comments)
|
// nextToken gets the next meaningful token (skipping comments)
|
||||||
func (p *Parser) nextToken() (Token, error) {
|
func (p *Parser) nextToken() (Token, error) {
|
||||||
for {
|
for {
|
||||||
|
@ -124,14 +109,20 @@ func (p *Parser) parseContent() error {
|
||||||
p.currentObject[name] = newMap
|
p.currentObject[name] = newMap
|
||||||
|
|
||||||
// Process the map contents
|
// Process the map contents
|
||||||
p.pushObject(newMap)
|
p.stack = append(p.stack, p.currentObject)
|
||||||
|
p.currentObject = newMap
|
||||||
|
|
||||||
// Copy values from scanned map to our object
|
// Copy values from scanned map to our object
|
||||||
for k, v := range mapValue {
|
for k, v := range mapValue {
|
||||||
p.currentObject[k] = v
|
p.currentObject[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
p.popObject()
|
// Restore parent object
|
||||||
|
n := len(p.stack)
|
||||||
|
if n > 0 {
|
||||||
|
p.currentObject = p.stack[n-1]
|
||||||
|
p.stack = p.stack[:n-1]
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Direct storage for primitives and arrays
|
// Direct storage for primitives and arrays
|
||||||
p.currentObject[name] = value
|
p.currentObject[name] = value
|
||||||
|
@ -154,7 +145,6 @@ func (p *Parser) parseValue() (any, error) {
|
||||||
|
|
||||||
case TokenNumber:
|
case TokenNumber:
|
||||||
strValue := string(token.Value)
|
strValue := string(token.Value)
|
||||||
// Check if it's a float or int
|
|
||||||
for i := 0; i < len(strValue); i++ {
|
for i := 0; i < len(strValue); i++ {
|
||||||
if strValue[i] == '.' {
|
if strValue[i] == '.' {
|
||||||
// It's a float
|
// It's a float
|
||||||
|
@ -208,8 +198,9 @@ func (p *Parser) parseObject() (any, error) {
|
||||||
return contents, nil
|
return contents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we find a name, it could be a map entry or array element
|
// Handle based on token type
|
||||||
if token.Type == TokenName {
|
switch token.Type {
|
||||||
|
case TokenName:
|
||||||
// Get the name value
|
// Get the name value
|
||||||
name := string(token.Value)
|
name := string(token.Value)
|
||||||
|
|
||||||
|
@ -241,30 +232,26 @@ func (p *Parser) parseObject() (any, error) {
|
||||||
// Put the token back and treat the name as an array element
|
// Put the token back and treat the name as an array element
|
||||||
p.scanner.UnreadToken(nextToken)
|
p.scanner.UnreadToken(nextToken)
|
||||||
|
|
||||||
// Try to convert to appropriate type
|
// Convert to appropriate type if possible
|
||||||
var value any = name
|
var value any = name
|
||||||
|
|
||||||
// Do some type inference for common values
|
// Try to infer type
|
||||||
if name == "true" {
|
if name == "true" {
|
||||||
value = true
|
value = true
|
||||||
} else if name == "false" {
|
} else if name == "false" {
|
||||||
value = false
|
value = false
|
||||||
} else if isDigit(name[0]) || (len(name) > 1 && name[0] == '-' && isDigit(name[1])) {
|
} else if isDigitOrMinus(name) {
|
||||||
// Try to parse as number
|
// Try to parse as number
|
||||||
if hasDot(name) {
|
numValue, err := parseStringAsNumber(name)
|
||||||
if f, err := strconv.ParseFloat(name, 64); err == nil {
|
if err == nil {
|
||||||
value = f
|
value = numValue
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if i, err := strconv.ParseInt(name, 10, 64); err == nil {
|
|
||||||
value = i
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayElements = append(arrayElements, value)
|
arrayElements = append(arrayElements, value)
|
||||||
}
|
}
|
||||||
} else if token.Type == TokenString || token.Type == TokenNumber || token.Type == TokenBoolean {
|
|
||||||
|
case TokenString, TokenNumber, TokenBoolean:
|
||||||
// Direct array element
|
// Direct array element
|
||||||
var value any
|
var value any
|
||||||
|
|
||||||
|
@ -273,26 +260,22 @@ func (p *Parser) parseObject() (any, error) {
|
||||||
value = string(token.Value)
|
value = string(token.Value)
|
||||||
case TokenNumber:
|
case TokenNumber:
|
||||||
strVal := string(token.Value)
|
strVal := string(token.Value)
|
||||||
if hasDot(strVal) {
|
value, _ = parseStringAsNumber(strVal)
|
||||||
f, _ := strconv.ParseFloat(strVal, 64)
|
|
||||||
value = f
|
|
||||||
} else {
|
|
||||||
i, _ := strconv.ParseInt(strVal, 10, 64)
|
|
||||||
value = i
|
|
||||||
}
|
|
||||||
case TokenBoolean:
|
case TokenBoolean:
|
||||||
value = string(token.Value) == "true"
|
value = string(token.Value) == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayElements = append(arrayElements, value)
|
arrayElements = append(arrayElements, value)
|
||||||
} else if token.Type == TokenOpenBrace {
|
|
||||||
|
case TokenOpenBrace:
|
||||||
// Nested object in array
|
// Nested object in array
|
||||||
nestedObj, err := p.parseObject()
|
nestedObj, err := p.parseObject()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
arrayElements = append(arrayElements, nestedObj)
|
arrayElements = append(arrayElements, nestedObj)
|
||||||
} else {
|
|
||||||
|
default:
|
||||||
return nil, p.Error(fmt.Sprintf("unexpected token in object: %v", token.Type))
|
return nil, p.Error(fmt.Sprintf("unexpected token in object: %v", token.Type))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
267
scanner.go
267
scanner.go
|
@ -2,37 +2,11 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TokenType represents the type of token
|
|
||||||
type TokenType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TokenError TokenType = iota
|
|
||||||
TokenEOF
|
|
||||||
TokenName
|
|
||||||
TokenString
|
|
||||||
TokenNumber
|
|
||||||
TokenBoolean
|
|
||||||
TokenEquals
|
|
||||||
TokenOpenBrace
|
|
||||||
TokenCloseBrace
|
|
||||||
TokenComment
|
|
||||||
)
|
|
||||||
|
|
||||||
// Token represents a lexical token
|
|
||||||
type Token struct {
|
|
||||||
Type TokenType
|
|
||||||
Value []byte
|
|
||||||
Line int
|
|
||||||
Column int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scanner handles the low-level parsing of the configuration format
|
// Scanner handles the low-level parsing of the configuration format
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
|
@ -117,6 +91,11 @@ func (s *Scanner) SkipWhitespace() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnreadToken stores a token to be returned by the next call to NextToken
|
||||||
|
func (s *Scanner) UnreadToken(token Token) {
|
||||||
|
s.token = token
|
||||||
|
}
|
||||||
|
|
||||||
// NextToken scans and returns the next token
|
// NextToken scans and returns the next token
|
||||||
func (s *Scanner) NextToken() (Token, error) {
|
func (s *Scanner) NextToken() (Token, error) {
|
||||||
if s.token.Type != TokenError {
|
if s.token.Type != TokenError {
|
||||||
|
@ -126,7 +105,6 @@ func (s *Scanner) NextToken() (Token, error) {
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// No stored token, scan a new one
|
|
||||||
// Skip whitespace
|
// Skip whitespace
|
||||||
err := s.SkipWhitespace()
|
err := s.SkipWhitespace()
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
@ -198,10 +176,6 @@ func (s *Scanner) NextToken() (Token, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) UnreadToken(token Token) {
|
|
||||||
s.token = token // Store the token to be returned next
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanComment processes a comment
|
// scanComment processes a comment
|
||||||
func (s *Scanner) scanComment() error {
|
func (s *Scanner) scanComment() error {
|
||||||
// Consume the first dash
|
// Consume the first dash
|
||||||
|
@ -220,13 +194,23 @@ func (s *Scanner) scanComment() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for block comment [[
|
// Check for block comment [[
|
||||||
b, err = s.PeekByte()
|
if b1, err := s.PeekByte(); err == nil && b1 == '[' {
|
||||||
if err == nil && b == '[' {
|
|
||||||
_, _ = s.ReadByte() // consume first [
|
_, _ = s.ReadByte() // consume first [
|
||||||
b, err = s.PeekByte()
|
if b2, err := s.PeekByte(); err == nil && b2 == '[' {
|
||||||
if err == nil && b == '[' {
|
|
||||||
_, _ = s.ReadByte() // consume second [
|
_, _ = s.ReadByte() // consume second [
|
||||||
return s.scanBlockComment()
|
// Process block comment
|
||||||
|
for {
|
||||||
|
b, err := s.ReadByte()
|
||||||
|
if err != nil {
|
||||||
|
return s.Error("unclosed block comment")
|
||||||
|
}
|
||||||
|
if b == ']' {
|
||||||
|
if n, err := s.PeekByte(); err == nil && n == ']' {
|
||||||
|
_, _ = s.ReadByte() // consume second ]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,24 +229,6 @@ func (s *Scanner) scanComment() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanBlockComment processes a block comment
|
|
||||||
func (s *Scanner) scanBlockComment() error {
|
|
||||||
for {
|
|
||||||
b, err := s.ReadByte()
|
|
||||||
if err != nil {
|
|
||||||
return s.Error("unclosed block comment")
|
|
||||||
}
|
|
||||||
|
|
||||||
if b == ']' {
|
|
||||||
b, err = s.PeekByte()
|
|
||||||
if err == nil && b == ']' {
|
|
||||||
_, _ = s.ReadByte() // consume second ]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanString scans a quoted string
|
// scanString scans a quoted string
|
||||||
func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
|
func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
|
||||||
// Reset buffer
|
// Reset buffer
|
||||||
|
@ -300,17 +266,17 @@ func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
|
||||||
case 't':
|
case 't':
|
||||||
s.buffer = append(s.buffer, '\t')
|
s.buffer = append(s.buffer, '\t')
|
||||||
default:
|
default:
|
||||||
s.buffer = append(s.buffer, '\\')
|
s.buffer = append(s.buffer, '\\', escaped)
|
||||||
s.buffer = append(s.buffer, escaped)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.buffer = append(s.buffer, b)
|
s.buffer = append(s.buffer, b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return token using buffer directly - we'll copy in NextToken if needed
|
||||||
return Token{
|
return Token{
|
||||||
Type: TokenString,
|
Type: TokenString,
|
||||||
Value: append([]byte(nil), s.buffer...), // Make a copy of the buffer
|
Value: s.buffer,
|
||||||
Line: startLine,
|
Line: startLine,
|
||||||
Column: startColumn,
|
Column: startColumn,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -349,18 +315,15 @@ func (s *Scanner) scanName(startLine, startColumn int) (Token, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a boolean
|
// Check if it's a boolean
|
||||||
if bytes.Equal(s.buffer, []byte("true")) || bytes.Equal(s.buffer, []byte("false")) {
|
tokenType := TokenName
|
||||||
return Token{
|
if len(s.buffer) == 4 && (s.buffer[0] == 't' && s.buffer[1] == 'r' && s.buffer[2] == 'u' && s.buffer[3] == 'e' ||
|
||||||
Type: TokenBoolean,
|
s.buffer[0] == 'f' && s.buffer[1] == 'a' && s.buffer[2] == 'l' && s.buffer[3] == 's' && s.buffer[4] == 'e') {
|
||||||
Value: append([]byte(nil), s.buffer...), // Make a copy of the buffer
|
tokenType = TokenBoolean
|
||||||
Line: startLine,
|
|
||||||
Column: startColumn,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Token{
|
return Token{
|
||||||
Type: TokenName,
|
Type: tokenType,
|
||||||
Value: append([]byte(nil), s.buffer...), // Make a copy of the buffer
|
Value: s.buffer,
|
||||||
Line: startLine,
|
Line: startLine,
|
||||||
Column: startColumn,
|
Column: startColumn,
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -403,178 +366,8 @@ func (s *Scanner) scanNumber(startLine, startColumn int) (Token, error) {
|
||||||
|
|
||||||
return Token{
|
return Token{
|
||||||
Type: TokenNumber,
|
Type: TokenNumber,
|
||||||
Value: append([]byte(nil), s.buffer...), // Make a copy of the buffer
|
Value: s.buffer,
|
||||||
Line: startLine,
|
Line: startLine,
|
||||||
Column: startColumn,
|
Column: startColumn,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScanValue processes a value and returns its Go representation
|
|
||||||
func (s *Scanner) ScanValue() (any, error) {
|
|
||||||
token, err := s.NextToken()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch token.Type {
|
|
||||||
case TokenString:
|
|
||||||
return string(token.Value), nil
|
|
||||||
|
|
||||||
case TokenBoolean:
|
|
||||||
if bytes.Equal(token.Value, []byte("true")) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
|
|
||||||
case TokenNumber:
|
|
||||||
// Convert to number
|
|
||||||
value := string(token.Value)
|
|
||||||
if bytes.Contains(token.Value, []byte(".")) {
|
|
||||||
// Float
|
|
||||||
return strconv.ParseFloat(value, 64)
|
|
||||||
}
|
|
||||||
// Integer
|
|
||||||
return strconv.ParseInt(value, 10, 64)
|
|
||||||
|
|
||||||
case TokenOpenBrace:
|
|
||||||
// Object or array
|
|
||||||
return s.scanObjectOrArray()
|
|
||||||
|
|
||||||
case TokenName:
|
|
||||||
// Name identifier - could be a special value or just a string
|
|
||||||
return string(token.Value), nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unexpected token type %v at line %d, column %d", token.Type, token.Line, token.Column)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// scanObjectOrArray processes a map or array enclosed in braces
|
|
||||||
func (s *Scanner) scanObjectOrArray() (any, error) {
|
|
||||||
// Initialize collections
|
|
||||||
contents := make(map[string]any)
|
|
||||||
var arrayElements []any
|
|
||||||
isArray := true
|
|
||||||
|
|
||||||
for {
|
|
||||||
err := s.SkipWhitespace()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := s.PeekByte()
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil, errors.New("unclosed object/array")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for closing brace
|
|
||||||
if b == '}' {
|
|
||||||
_, _ = s.ReadByte() // consume closing brace
|
|
||||||
if isArray && len(contents) == 0 {
|
|
||||||
return arrayElements, nil
|
|
||||||
}
|
|
||||||
return contents, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle comments
|
|
||||||
if b == '-' {
|
|
||||||
peekBytes, err := s.PeekBytes(2)
|
|
||||||
if err == nil && len(peekBytes) == 2 && peekBytes[1] == '-' {
|
|
||||||
err = s.scanComment()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process key-value pair or array element
|
|
||||||
if isLetter(b) {
|
|
||||||
// Read name
|
|
||||||
nameToken, err := s.scanName(s.line, s.col)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
name := string(nameToken.Value)
|
|
||||||
|
|
||||||
// Skip whitespace
|
|
||||||
err = s.SkipWhitespace()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if it's followed by = or {
|
|
||||||
b, err = s.PeekByte()
|
|
||||||
if err != nil && err != io.EOF {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if b == '=' {
|
|
||||||
// It's a key-value pair
|
|
||||||
_, _ = s.ReadByte() // consume =
|
|
||||||
err = s.SkipWhitespace()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := s.ScanValue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
isArray = false
|
|
||||||
contents[name] = value
|
|
||||||
} else if b == '{' {
|
|
||||||
// It's a nested object/array
|
|
||||||
_, _ = s.ReadByte() // consume {
|
|
||||||
value, err := s.scanObjectOrArray()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
isArray = false
|
|
||||||
contents[name] = value
|
|
||||||
} else {
|
|
||||||
// It's a simple name as an array element
|
|
||||||
// Try to convert to appropriate type first
|
|
||||||
var value any = name
|
|
||||||
// Try common conversions
|
|
||||||
if name == "true" {
|
|
||||||
value = true
|
|
||||||
} else if name == "false" {
|
|
||||||
value = false
|
|
||||||
} else if isDigit(name[0]) || (len(name) > 1 && name[0] == '-' && isDigit(name[1])) {
|
|
||||||
// Looks like a number, try to convert
|
|
||||||
if hasDot(name) {
|
|
||||||
if f, err := strconv.ParseFloat(name, 64); err == nil {
|
|
||||||
value = f
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if i, err := strconv.ParseInt(name, 10, 64); err == nil {
|
|
||||||
value = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayElements = append(arrayElements, value)
|
|
||||||
}
|
|
||||||
} else if b == '"' {
|
|
||||||
// String value - must be an array element
|
|
||||||
value, err := s.ScanValue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
arrayElements = append(arrayElements, value)
|
|
||||||
} else {
|
|
||||||
// Other value type - must be an array element
|
|
||||||
value, err := s.ScanValue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
arrayElements = append(arrayElements, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
25
token.go
Normal file
25
token.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
// TokenType represents the type of token
|
||||||
|
type TokenType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
TokenError TokenType = iota
|
||||||
|
TokenEOF
|
||||||
|
TokenName
|
||||||
|
TokenString
|
||||||
|
TokenNumber
|
||||||
|
TokenBoolean
|
||||||
|
TokenEquals
|
||||||
|
TokenOpenBrace
|
||||||
|
TokenCloseBrace
|
||||||
|
TokenComment
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token represents a lexical token
|
||||||
|
type Token struct {
|
||||||
|
Type TokenType
|
||||||
|
Value []byte
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user