Compare commits

...

4 Commits

Author SHA1 Message Date
80500963b4 update readme syntax 2025-03-04 19:16:36 -06:00
41b82c71cd ref 10 2025-03-04 19:15:05 -06:00
b334b09efa ref 9 2025-03-04 18:23:09 -06:00
456854246b ref 8 2025-03-04 17:29:39 -06:00
8 changed files with 1035 additions and 1038 deletions

View File

@ -17,9 +17,9 @@ A lightweight, intuitive configuration parser for Go applications with a clean,
This parser uses a clean, minimal syntax that's easy to read and write:
```
host = "localhost"
port = 8080
debug = true
host "localhost"
port 8080
debug true
allowed_ips {
"192.168.1.1"
@ -28,11 +28,11 @@ allowed_ips {
}
database {
host = "db.example.com"
port = 5432
host "db.example.com"
port 5432
credentials {
username = "admin"
password = "secure123"
username "admin"
password "secure123"
}
}

View File

@ -2,7 +2,6 @@ package config
import (
"encoding/json"
"strconv"
"strings"
"testing"
@ -11,618 +10,12 @@ import (
"gopkg.in/yaml.v3"
)
// Original benchmarks
func BenchmarkSmallConfig(b *testing.B) {
// Small config with just a few key-value pairs
smallConfig := `
host = "localhost"
port = 8080
debug = true
timeout = 30
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(smallConfig)
_, err := config.Load(reader)
if err != nil {
b.Fatalf("Failed to parse small config: %v", err)
}
}
}
func BenchmarkMediumConfig(b *testing.B) {
// Medium config with nested structures and arrays
mediumConfig := `
app {
name = "TestApp"
version = "1.0.0"
enableLogging = true
}
database {
host = "db.example.com"
port = 5432
credentials {
username = "admin"
password = "secure123"
}
}
features = {
"authentication"
"authorization"
"reporting"
"analytics"
}
timeouts {
connect = 5
read = 10
write = 10
idle = 60
}
-- Comments to add some parsing overhead
endpoints {
api = "/api/v1"
web = "/web"
admin = "/admin"
health = "/health"
}
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(mediumConfig)
_, err := config.Load(reader)
if err != nil {
b.Fatalf("Failed to parse medium config: %v", err)
}
}
}
func BenchmarkLargeConfig(b *testing.B) {
// Simpler large config with careful bracket matching
largeConfig := `
application {
name = "EnterpriseApp"
version = "2.5.1"
environment = "production"
debug = false
maxConnections = 1000
timeout = 30
retryCount = 3
logLevel = "info"
}
-- Database cluster configuration
databases {
primary {
host = "primary-db.example.com"
port = 5432
maxConnections = 100
credentials {
username = "app_user"
password = "super_secret"
ssl = true
timeout = 5
}
}
replica {
host = "replica-db.example.com"
port = 5432
maxConnections = 200
credentials {
username = "read_user"
password = "read_only_pw"
ssl = true
}
}
}
allowedIPs {
"192.168.1.1"
"192.168.1.2"
"192.168.1.3"
"192.168.1.4"
"192.168.1.5"
}
-- Add 50 numbered settings to make the config large
settings {`
// Add many numbered settings
var builder strings.Builder
builder.WriteString(largeConfig)
for i := 1; i <= 50; i++ {
builder.WriteString("\n\t\tsetting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(" = ")
builder.WriteString(strconv.Itoa(i * 10))
}
// Close the settings block and add one more block
builder.WriteString(`
}
roles {
admin {
permissions = {
"read"
"write"
"delete"
"admin"
}
}
user {
permissions = {
"read"
"write"
}
}
}
`)
largeConfig = builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(largeConfig)
_, err := config.Load(reader)
if err != nil {
b.Fatalf("Failed to parse large config: %v", err)
}
}
}
// JSON Benchmarks
func BenchmarkSmallConfigJSON(b *testing.B) {
smallConfigJSON := `{
"host": "localhost",
"port": 8080,
"debug": true,
"timeout": 30
}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(smallConfigJSON)
var result map[string]interface{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&result)
if err != nil {
b.Fatalf("Failed to parse small JSON config: %v", err)
}
}
}
func BenchmarkMediumConfigJSON(b *testing.B) {
mediumConfigJSON := `{
"app": {
"name": "TestApp",
"version": "1.0.0",
"enableLogging": true
},
"database": {
"host": "db.example.com",
"port": 5432,
"credentials": {
"username": "admin",
"password": "secure123"
}
},
"features": [
"authentication",
"authorization",
"reporting",
"analytics"
],
"timeouts": {
"connect": 5,
"read": 10,
"write": 10,
"idle": 60
},
"endpoints": {
"api": "/api/v1",
"web": "/web",
"admin": "/admin",
"health": "/health"
}
}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(mediumConfigJSON)
var result map[string]interface{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&result)
if err != nil {
b.Fatalf("Failed to parse medium JSON config: %v", err)
}
}
}
func BenchmarkLargeConfigJSON(b *testing.B) {
// Start building the large JSON config
largeConfigJSON := `{
"application": {
"name": "EnterpriseApp",
"version": "2.5.1",
"environment": "production",
"debug": false,
"maxConnections": 1000,
"timeout": 30,
"retryCount": 3,
"logLevel": "info"
},
"databases": {
"primary": {
"host": "primary-db.example.com",
"port": 5432,
"maxConnections": 100,
"credentials": {
"username": "app_user",
"password": "super_secret",
"ssl": true,
"timeout": 5
}
},
"replica": {
"host": "replica-db.example.com",
"port": 5432,
"maxConnections": 200,
"credentials": {
"username": "read_user",
"password": "read_only_pw",
"ssl": true
}
}
},
"allowedIPs": [
"192.168.1.1",
"192.168.1.2",
"192.168.1.3",
"192.168.1.4",
"192.168.1.5"
],
"settings": {`
var builder strings.Builder
builder.WriteString(largeConfigJSON)
// Add many numbered settings
for i := 1; i <= 50; i++ {
if i > 1 {
builder.WriteString(",")
}
builder.WriteString("\n\t\t\t\"setting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString("\": ")
builder.WriteString(strconv.Itoa(i * 10))
}
// Close the settings block and add roles
builder.WriteString(`
},
"roles": {
"admin": {
"permissions": [
"read",
"write",
"delete",
"admin"
]
},
"user": {
"permissions": [
"read",
"write"
]
}
}
}`)
largeConfigJSONString := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(largeConfigJSONString)
var result map[string]interface{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&result)
if err != nil {
b.Fatalf("Failed to parse large JSON config: %v", err)
}
}
}
// YAML Benchmarks
func BenchmarkSmallConfigYAML(b *testing.B) {
smallConfigYAML := `
host: localhost
port: 8080
debug: true
timeout: 30
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := yaml.Unmarshal([]byte(smallConfigYAML), &result)
if err != nil {
b.Fatalf("Failed to parse small YAML config: %v", err)
}
}
}
func BenchmarkMediumConfigYAML(b *testing.B) {
mediumConfigYAML := `
app:
name: TestApp
version: 1.0.0
enableLogging: true
database:
host: db.example.com
port: 5432
credentials:
username: admin
password: secure123
features:
- authentication
- authorization
- reporting
- analytics
timeouts:
connect: 5
read: 10
write: 10
idle: 60
# Comments to add some parsing overhead
endpoints:
api: /api/v1
web: /web
admin: /admin
health: /health
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := yaml.Unmarshal([]byte(mediumConfigYAML), &result)
if err != nil {
b.Fatalf("Failed to parse medium YAML config: %v", err)
}
}
}
func BenchmarkLargeConfigYAML(b *testing.B) {
// Start building the large YAML config
largeConfigYAML := `
application:
name: EnterpriseApp
version: 2.5.1
environment: production
debug: false
maxConnections: 1000
timeout: 30
retryCount: 3
logLevel: info
# Database cluster configuration
databases:
primary:
host: primary-db.example.com
port: 5432
maxConnections: 100
credentials:
username: app_user
password: super_secret
ssl: true
timeout: 5
replica:
host: replica-db.example.com
port: 5432
maxConnections: 200
credentials:
username: read_user
password: read_only_pw
ssl: true
allowedIPs:
- 192.168.1.1
- 192.168.1.2
- 192.168.1.3
- 192.168.1.4
- 192.168.1.5
# Add 50 numbered settings to make the config large
settings:
`
var builder strings.Builder
builder.WriteString(largeConfigYAML)
// Add many numbered settings
for i := 1; i <= 50; i++ {
builder.WriteString(" setting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(": ")
builder.WriteString(strconv.Itoa(i * 10))
builder.WriteString("\n")
}
// Add roles
builder.WriteString(`
roles:
admin:
permissions:
- read
- write
- delete
- admin
user:
permissions:
- read
- write
`)
largeConfigYAMLString := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := yaml.Unmarshal([]byte(largeConfigYAMLString), &result)
if err != nil {
b.Fatalf("Failed to parse large YAML config: %v", err)
}
}
}
// TOML Benchmarks
func BenchmarkSmallConfigTOML(b *testing.B) {
smallConfigTOML := `
host = "localhost"
port = 8080
debug = true
timeout = 30
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := toml.Unmarshal([]byte(smallConfigTOML), &result)
if err != nil {
b.Fatalf("Failed to parse small TOML config: %v", err)
}
}
}
func BenchmarkMediumConfigTOML(b *testing.B) {
mediumConfigTOML := `
[app]
name = "TestApp"
version = "1.0.0"
enableLogging = true
[database]
host = "db.example.com"
port = 5432
[database.credentials]
username = "admin"
password = "secure123"
features = ["authentication", "authorization", "reporting", "analytics"]
[timeouts]
connect = 5
read = 10
write = 10
idle = 60
# Comments to add some parsing overhead
[endpoints]
api = "/api/v1"
web = "/web"
admin = "/admin"
health = "/health"
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := toml.Unmarshal([]byte(mediumConfigTOML), &result)
if err != nil {
b.Fatalf("Failed to parse medium TOML config: %v", err)
}
}
}
func BenchmarkLargeConfigTOML(b *testing.B) {
// Start building the large TOML config
largeConfigTOML := `
[application]
name = "EnterpriseApp"
version = "2.5.1"
environment = "production"
debug = false
maxConnections = 1000
timeout = 30
retryCount = 3
logLevel = "info"
# Database cluster configuration
[databases.primary]
host = "primary-db.example.com"
port = 5432
maxConnections = 100
[databases.primary.credentials]
username = "app_user"
password = "super_secret"
ssl = true
timeout = 5
[databases.replica]
host = "replica-db.example.com"
port = 5432
maxConnections = 200
[databases.replica.credentials]
username = "read_user"
password = "read_only_pw"
ssl = true
allowedIPs = ["192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4", "192.168.1.5"]
# Add 50 numbered settings to make the config large
[settings]
`
var builder strings.Builder
builder.WriteString(largeConfigTOML)
// Add many numbered settings
for i := 1; i <= 50; i++ {
builder.WriteString("setting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(" = ")
builder.WriteString(strconv.Itoa(i * 10))
builder.WriteString("\n")
}
// Add roles
builder.WriteString(`
[roles.admin]
permissions = ["read", "write", "delete", "admin"]
[roles.user]
permissions = ["read", "write"]
`)
largeConfigTOMLString := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := toml.Unmarshal([]byte(largeConfigTOMLString), &result)
if err != nil {
b.Fatalf("Failed to parse large TOML config: %v", err)
}
}
}
// Value Retrieval Benchmarks for Custom Config Format
func BenchmarkRetrieveSimpleValues(b *testing.B) {
configData := `
host = "localhost"
port = 8080
debug = true
timeout = 30
host "localhost"
port 8080
debug true
timeout 30
`
// Parse once before benchmarking retrieval
@ -663,19 +56,19 @@ func BenchmarkRetrieveSimpleValues(b *testing.B) {
func BenchmarkRetrieveNestedValues(b *testing.B) {
configData := `
app {
name = "TestApp"
version = "1.0.0"
name "TestApp"
version "1.0.0"
settings {
enableLogging = true
maxConnections = 100
enableLogging true
maxConnections 100
}
}
database {
host = "db.example.com"
port = 5432
host "db.example.com"
port 5432
credentials {
username = "admin"
password = "secure123"
username "admin"
password "secure123"
}
}
`
@ -715,13 +108,13 @@ func BenchmarkRetrieveNestedValues(b *testing.B) {
func BenchmarkRetrieveArrayValues(b *testing.B) {
configData := `
features = {
features {
"authentication"
"authorization"
"reporting"
"analytics"
}
numbers = {
numbers {
1
2
3
@ -767,19 +160,19 @@ func BenchmarkRetrieveArrayValues(b *testing.B) {
func BenchmarkRetrieveMixedValues(b *testing.B) {
configData := `
app {
name = "TestApp"
version = "1.0.0"
environments = {
name "TestApp"
version "1.0.0"
environments {
"development"
"testing"
"production"
}
limits {
requests = 1000
connections = 100
requests 1000
connections 100
timeouts {
read = 5
write = 10
read 5
write 10
}
}
}
@ -1065,17 +458,17 @@ func BenchmarkComplexValueRetrieval(b *testing.B) {
// Setup complex config with deep nesting and arrays for all formats
customConfig := `
app {
name = "TestApp"
version = "1.0.0"
name "TestApp"
version "1.0.0"
settings {
enableLogging = true
maxConnections = 100
enableLogging true
maxConnections 100
timeouts {
read = 5
write = 10
read 5
write 10
}
}
environments = {
environments {
"development"
"testing"
"production"

617
bench/parse_test.go Normal file
View File

@ -0,0 +1,617 @@
package config
import (
"encoding/json"
"strconv"
"strings"
"testing"
config "git.sharkk.net/Go/Config"
"github.com/BurntSushi/toml"
"gopkg.in/yaml.v3"
)
// Original benchmarks
func BenchmarkSmallConfig(b *testing.B) {
// Small config with just a few key-value pairs
smallConfig := `
host "localhost"
port 8080
debug true
timeout 30
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(smallConfig)
_, err := config.Load(reader)
if err != nil {
b.Fatalf("Failed to parse small config: %v", err)
}
}
}
func BenchmarkMediumConfig(b *testing.B) {
// Medium config with nested structures and arrays
mediumConfig := `
app {
name "TestApp"
version "1.0.0"
enableLogging true
}
database {
host "db.example.com"
port 5432
credentials {
username "admin"
password "secure123"
}
}
features {
"authentication"
"authorization"
"reporting"
"analytics"
}
timeouts {
connect 5
read 10
write 10
idle 60
}
-- Comments to add some parsing overhead
endpoints {
api "/api/v1"
web "/web"
admin "/admin"
health "/health"
}
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(mediumConfig)
_, err := config.Load(reader)
if err != nil {
b.Fatalf("Failed to parse medium config: %v", err)
}
}
}
func BenchmarkLargeConfig(b *testing.B) {
// Simpler large config with careful bracket matching
largeConfig := `
application {
name "EnterpriseApp"
version "2.5.1"
environment "production"
debug false
maxConnections 1000
timeout 30
retryCount 3
logLevel "info"
}
-- Database cluster configuration
databases {
primary {
host "primary-db.example.com"
port 5432
maxConnections 100
credentials {
username "app_user"
password "super_secret"
ssl true
timeout 5
}
}
replica {
host "replica-db.example.com"
port 5432
maxConnections 200
credentials {
username "read_user"
password "read_only_pw"
ssl true
}
}
}
allowedIPs {
"192.168.1.1"
"192.168.1.2"
"192.168.1.3"
"192.168.1.4"
"192.168.1.5"
}
-- Add 50 numbered settings to make the config large
settings {`
// Add many numbered settings
var builder strings.Builder
builder.WriteString(largeConfig)
for i := 1; i <= 50; i++ {
builder.WriteString("\n\t\tsetting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(" ")
builder.WriteString(strconv.Itoa(i * 10))
}
// Close the settings block and add one more block
builder.WriteString(`
}
roles {
admin {
permissions {
"read"
"write"
"delete"
"admin"
}
}
user {
permissions {
"read"
"write"
}
}
}
`)
largeConfig = builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(largeConfig)
_, err := config.Load(reader)
if err != nil {
b.Fatalf("Failed to parse large config: %v", err)
}
}
}
// JSON Benchmarks
func BenchmarkSmallConfigJSON(b *testing.B) {
smallConfigJSON := `{
"host": "localhost",
"port": 8080,
"debug": true,
"timeout": 30
}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(smallConfigJSON)
var result map[string]interface{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&result)
if err != nil {
b.Fatalf("Failed to parse small JSON config: %v", err)
}
}
}
func BenchmarkMediumConfigJSON(b *testing.B) {
mediumConfigJSON := `{
"app": {
"name": "TestApp",
"version": "1.0.0",
"enableLogging": true
},
"database": {
"host": "db.example.com",
"port": 5432,
"credentials": {
"username": "admin",
"password": "secure123"
}
},
"features": [
"authentication",
"authorization",
"reporting",
"analytics"
],
"timeouts": {
"connect": 5,
"read": 10,
"write": 10,
"idle": 60
},
"endpoints": {
"api": "/api/v1",
"web": "/web",
"admin": "/admin",
"health": "/health"
}
}`
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(mediumConfigJSON)
var result map[string]interface{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&result)
if err != nil {
b.Fatalf("Failed to parse medium JSON config: %v", err)
}
}
}
func BenchmarkLargeConfigJSON(b *testing.B) {
// Start building the large JSON config
largeConfigJSON := `{
"application": {
"name": "EnterpriseApp",
"version": "2.5.1",
"environment": "production",
"debug": false,
"maxConnections": 1000,
"timeout": 30,
"retryCount": 3,
"logLevel": "info"
},
"databases": {
"primary": {
"host": "primary-db.example.com",
"port": 5432,
"maxConnections": 100,
"credentials": {
"username": "app_user",
"password": "super_secret",
"ssl": true,
"timeout": 5
}
},
"replica": {
"host": "replica-db.example.com",
"port": 5432,
"maxConnections": 200,
"credentials": {
"username": "read_user",
"password": "read_only_pw",
"ssl": true
}
}
},
"allowedIPs": [
"192.168.1.1",
"192.168.1.2",
"192.168.1.3",
"192.168.1.4",
"192.168.1.5"
],
"settings": {`
var builder strings.Builder
builder.WriteString(largeConfigJSON)
// Add many numbered settings
for i := 1; i <= 50; i++ {
if i > 1 {
builder.WriteString(",")
}
builder.WriteString("\n\t\t\t\"setting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString("\": ")
builder.WriteString(strconv.Itoa(i * 10))
}
// Close the settings block and add roles
builder.WriteString(`
},
"roles": {
"admin": {
"permissions": [
"read",
"write",
"delete",
"admin"
]
},
"user": {
"permissions": [
"read",
"write"
]
}
}
}`)
largeConfigJSONString := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
reader := strings.NewReader(largeConfigJSONString)
var result map[string]interface{}
decoder := json.NewDecoder(reader)
err := decoder.Decode(&result)
if err != nil {
b.Fatalf("Failed to parse large JSON config: %v", err)
}
}
}
// YAML Benchmarks
func BenchmarkSmallConfigYAML(b *testing.B) {
smallConfigYAML := `
host: localhost
port: 8080
debug: true
timeout: 30
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := yaml.Unmarshal([]byte(smallConfigYAML), &result)
if err != nil {
b.Fatalf("Failed to parse small YAML config: %v", err)
}
}
}
func BenchmarkMediumConfigYAML(b *testing.B) {
mediumConfigYAML := `
app:
name: TestApp
version: 1.0.0
enableLogging: true
database:
host: db.example.com
port: 5432
credentials:
username: admin
password: secure123
features:
- authentication
- authorization
- reporting
- analytics
timeouts:
connect: 5
read: 10
write: 10
idle: 60
# Comments to add some parsing overhead
endpoints:
api: /api/v1
web: /web
admin: /admin
health: /health
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := yaml.Unmarshal([]byte(mediumConfigYAML), &result)
if err != nil {
b.Fatalf("Failed to parse medium YAML config: %v", err)
}
}
}
func BenchmarkLargeConfigYAML(b *testing.B) {
// Start building the large YAML config
largeConfigYAML := `
application:
name: EnterpriseApp
version: 2.5.1
environment: production
debug: false
maxConnections: 1000
timeout: 30
retryCount: 3
logLevel: info
# Database cluster configuration
databases:
primary:
host: primary-db.example.com
port: 5432
maxConnections: 100
credentials:
username: app_user
password: super_secret
ssl: true
timeout: 5
replica:
host: replica-db.example.com
port: 5432
maxConnections: 200
credentials:
username: read_user
password: read_only_pw
ssl: true
allowedIPs:
- 192.168.1.1
- 192.168.1.2
- 192.168.1.3
- 192.168.1.4
- 192.168.1.5
# Add 50 numbered settings to make the config large
settings:
`
var builder strings.Builder
builder.WriteString(largeConfigYAML)
// Add many numbered settings
for i := 1; i <= 50; i++ {
builder.WriteString(" setting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(": ")
builder.WriteString(strconv.Itoa(i * 10))
builder.WriteString("\n")
}
// Add roles
builder.WriteString(`
roles:
admin:
permissions:
- read
- write
- delete
- admin
user:
permissions:
- read
- write
`)
largeConfigYAMLString := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := yaml.Unmarshal([]byte(largeConfigYAMLString), &result)
if err != nil {
b.Fatalf("Failed to parse large YAML config: %v", err)
}
}
}
// TOML Benchmarks
func BenchmarkSmallConfigTOML(b *testing.B) {
smallConfigTOML := `
host = "localhost"
port = 8080
debug = true
timeout = 30
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := toml.Unmarshal([]byte(smallConfigTOML), &result)
if err != nil {
b.Fatalf("Failed to parse small TOML config: %v", err)
}
}
}
func BenchmarkMediumConfigTOML(b *testing.B) {
mediumConfigTOML := `
[app]
name = "TestApp"
version = "1.0.0"
enableLogging = true
[database]
host = "db.example.com"
port = 5432
[database.credentials]
username = "admin"
password = "secure123"
features = ["authentication", "authorization", "reporting", "analytics"]
[timeouts]
connect = 5
read = 10
write = 10
idle = 60
# Comments to add some parsing overhead
[endpoints]
api = "/api/v1"
web = "/web"
admin = "/admin"
health = "/health"
`
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := toml.Unmarshal([]byte(mediumConfigTOML), &result)
if err != nil {
b.Fatalf("Failed to parse medium TOML config: %v", err)
}
}
}
func BenchmarkLargeConfigTOML(b *testing.B) {
// Start building the large TOML config
largeConfigTOML := `
[application]
name = "EnterpriseApp"
version = "2.5.1"
environment = "production"
debug = false
maxConnections = 1000
timeout = 30
retryCount = 3
logLevel = "info"
# Database cluster configuration
[databases.primary]
host = "primary-db.example.com"
port = 5432
maxConnections = 100
[databases.primary.credentials]
username = "app_user"
password = "super_secret"
ssl = true
timeout = 5
[databases.replica]
host = "replica-db.example.com"
port = 5432
maxConnections = 200
[databases.replica.credentials]
username = "read_user"
password = "read_only_pw"
ssl = true
allowedIPs = ["192.168.1.1", "192.168.1.2", "192.168.1.3", "192.168.1.4", "192.168.1.5"]
# Add 50 numbered settings to make the config large
[settings]
`
var builder strings.Builder
builder.WriteString(largeConfigTOML)
// Add many numbered settings
for i := 1; i <= 50; i++ {
builder.WriteString("setting")
builder.WriteString(strconv.Itoa(i))
builder.WriteString(" = ")
builder.WriteString(strconv.Itoa(i * 10))
builder.WriteString("\n")
}
// Add roles
builder.WriteString(`
[roles.admin]
permissions = ["read", "write", "delete", "admin"]
[roles.user]
permissions = ["read", "write"]
`)
largeConfigTOMLString := builder.String()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var result map[string]interface{}
err := toml.Unmarshal([]byte(largeConfigTOMLString), &result)
if err != nil {
b.Fatalf("Failed to parse large TOML config: %v", err)
}
}
}

575
config.go
View File

@ -4,20 +4,13 @@ import (
"fmt"
"io"
"strconv"
"strings"
)
// ParseState represents a parsing level state
type ParseState struct {
object map[string]any
arrayElements []any
isArray bool
currentKey string
expectValue bool
}
// Config holds a single hierarchical structure like JSON and handles parsing
type Config struct {
data map[string]any
dataRef *map[string]any // Reference to pooled map
scanner *Scanner
currentObject map[string]any
stack []map[string]any
@ -26,69 +19,70 @@ type Config struct {
// NewConfig creates a new empty config
func NewConfig() *Config {
dataRef := GetMap()
data := *dataRef
cfg := &Config{
data: make(map[string]any, 16), // Pre-allocate with expected capacity
stack: make([]map[string]any, 0, 8),
data: data,
dataRef: dataRef,
stack: make([]map[string]any, 0, 8),
}
cfg.currentObject = cfg.data
return cfg
}
// Release frees any resources and returns them to pools
func (c *Config) Release() {
if c.scanner != nil {
ReleaseScanner(c.scanner)
c.scanner = nil
}
if c.dataRef != nil {
PutMap(c.dataRef)
c.data = nil
c.dataRef = nil
}
c.currentObject = nil
c.stack = nil
}
// Get retrieves a value from the config using dot notation
func (c *Config) Get(key string) (any, error) {
if key == "" {
return c.data, nil
}
// Parse the dot-notation path manually
var start, i int
var current any = c.data
for i = 0; i < len(key); i++ {
if key[i] == '.' || i == len(key)-1 {
end := i
if i == len(key)-1 && key[i] != '.' {
end = i + 1
}
part := key[start:end]
// Handle current node based on its type
switch node := current.(type) {
case map[string]any:
// Simple map lookup
val, ok := node[part]
if !ok {
return nil, fmt.Errorf("key %s not found", part)
}
current = val
case []any:
// Must be numeric index
index, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("invalid array index: %s", part)
}
if index < 0 || index >= len(node) {
return nil, fmt.Errorf("array index out of bounds: %d", index)
}
current = node[index]
default:
return nil, fmt.Errorf("cannot access %s in non-container value", part)
}
// If we've processed the entire key, return the current value
if i == len(key)-1 || (i < len(key)-1 && key[i] == '.' && end == i) {
if i == len(key)-1 {
return current, nil
}
}
start = i + 1
if !strings.Contains(key, ".") {
if val, ok := c.data[key]; ok {
return val, nil
}
return nil, fmt.Errorf("key %s not found", key)
}
parts := strings.Split(key, ".")
current := any(c.data)
for _, part := range parts {
switch node := current.(type) {
case map[string]any:
var exists bool
current, exists = node[part]
if !exists {
return nil, fmt.Errorf("key %s not found", part)
}
case []any:
index, err := strconv.Atoi(part)
if err != nil {
return nil, fmt.Errorf("invalid array index: %s", part)
}
if index < 0 || index >= len(node) {
return nil, fmt.Errorf("array index out of bounds: %d", index)
}
current = node[index]
default:
return nil, fmt.Errorf("cannot access %s in non-container value", part)
}
}
return current, nil
}
@ -203,8 +197,6 @@ func (c *Config) GetMap(key string) (map[string]any, error) {
return nil, fmt.Errorf("value for key %s is not a map", key)
}
// --- Parser Methods (integrated into Config) ---
// Error creates an error with line information from the current token
func (c *Config) Error(msg string) error {
return fmt.Errorf("line %d, column %d: %s",
@ -257,244 +249,198 @@ func (c *Config) parseContent() error {
// We expect top level entries to be names
if token.Type != TokenName {
return c.Error("expected name at top level")
return c.Error(fmt.Sprintf("expected name at top level, got token type %v", token.Type))
}
// Get the property name - copy to create a stable key
nameBytes := token.Value
name := string(nameBytes)
nameBytes := GetByteSlice()
*nameBytes = append((*nameBytes)[:0], token.Value...)
name := string(*nameBytes)
PutByteSlice(nameBytes)
// Get the next token (should be = or {)
token, err = c.nextToken()
// Get the next token
nextToken, err := c.nextToken()
if err != nil {
if err == io.EOF {
// EOF after name - store as empty string
c.currentObject[name] = ""
break
}
return err
}
var value any
if token.Type == TokenEquals {
// It's a standard key=value assignment
value, err = c.parseValue()
if err != nil {
return err
}
} else if token.Type == TokenOpenBrace {
// It's a map/array without '='
if nextToken.Type == TokenOpenBrace {
// It's a nested object/array
value, err = c.parseObject()
if err != nil {
return err
}
} else {
return c.Error("expected '=' or '{' after name")
// It's a simple value
value = c.tokenToValue(nextToken)
// Check for potential nested object - look ahead
lookAhead, nextErr := c.nextToken()
if nextErr == nil && lookAhead.Type == TokenOpenBrace {
// It's a complex object that follows a value
nestedValue, err := c.parseObject()
if err != nil {
return err
}
// Store the previous simple value in a map to add to the object
if mapValue, ok := nestedValue.(map[string]any); ok {
// Create a new map value with both the simple value and the map
mapRef := GetMap()
newMap := *mapRef
for k, v := range mapValue {
newMap[k] = v
}
newMap["value"] = value // Store simple value under "value" key
value = newMap
}
} else if nextErr == nil && lookAhead.Type != TokenEOF {
// Put the token back if it's not EOF
c.scanner.UnreadToken(lookAhead)
}
}
// Store the value in the config
if mapValue, ok := value.(map[string]any); ok {
// Add an entry in current object
newMap := make(map[string]any, 8) // Pre-allocate with capacity
c.currentObject[name] = newMap
// Process the map contents
c.stack = append(c.stack, c.currentObject)
c.currentObject = newMap
// Copy values from scanned map to our object
for k, v := range mapValue {
c.currentObject[k] = v
}
// Restore parent object
n := len(c.stack)
if n > 0 {
c.currentObject = c.stack[n-1]
c.stack = c.stack[:n-1]
}
} else {
// Direct storage for primitives and arrays
c.currentObject[name] = value
}
c.currentObject[name] = value
}
return nil
}
// parseValue parses a value after an equals sign
func (c *Config) parseValue() (any, error) {
token, err := c.nextToken()
if err != nil {
return nil, err
}
switch token.Type {
case TokenString:
// Copy the value for string stability
return string(token.Value), nil
case TokenNumber:
strValue := string(token.Value)
for i := 0; i < len(strValue); i++ {
if strValue[i] == '.' {
// It's a float
val, err := strconv.ParseFloat(strValue, 64)
if err != nil {
return nil, c.Error(fmt.Sprintf("invalid float: %s", strValue))
}
return val, nil
}
}
// It's an integer
val, err := strconv.ParseInt(strValue, 10, 64)
if err != nil {
return nil, c.Error(fmt.Sprintf("invalid integer: %s", strValue))
}
return val, nil
case TokenBoolean:
return bytesEqual(token.Value, []byte("true")), nil
case TokenOpenBrace:
// It's a map or array
return c.parseObject()
case TokenName:
// Treat as a string value - copy to create a stable string
return string(token.Value), nil
default:
return nil, c.Error(fmt.Sprintf("unexpected token: %v", token.Type))
}
}
// parseObject parses a map or array
func (c *Config) parseObject() (any, error) {
// Initialize stack with first state
stack := []*ParseState{{
object: make(map[string]any, 8),
arrayElements: make([]any, 0, 8),
isArray: true,
}}
// Default to treating as an array until we see a name
isArray := true
arrayRef := GetArray()
arrayElements := *arrayRef
for len(stack) > 0 {
// Get current state from top of stack
current := stack[len(stack)-1]
mapRef := GetMap()
objectElements := *mapRef
defer func() {
if !isArray {
PutArray(arrayRef) // We didn't use the array
} else {
PutMap(mapRef) // We didn't use the map
}
}()
for {
token, err := c.nextToken()
if err != nil {
if err == io.EOF {
return nil, fmt.Errorf("unexpected EOF in object/array")
}
return nil, err
}
// Handle closing brace - finish current object/array
if token.Type == TokenCloseBrace {
// Determine result based on what we've collected
var result any
if current.isArray && len(current.object) == 0 {
result = current.arrayElements
} else {
result = current.object
}
// Pop the stack
stack = stack[:len(stack)-1]
// If stack is empty, we're done with the root object
if len(stack) == 0 {
if isArray && len(arrayElements) > 0 {
result := arrayElements
// Don't release the array, transfer ownership
*arrayRef = nil // Detach from pool reference
return result, nil
}
// Otherwise, add result to parent
parent := stack[len(stack)-1]
if parent.expectValue {
parent.object[parent.currentKey] = result
parent.expectValue = false
} else {
parent.arrayElements = append(parent.arrayElements, result)
}
continue
result := objectElements
// Don't release the map, transfer ownership
*mapRef = nil // Detach from pool reference
return result, nil
}
// Handle tokens based on type
switch token.Type {
case TokenName:
name := string(token.Value)
// Copy token value to create a stable key
keyBytes := GetByteSlice()
*keyBytes = append((*keyBytes)[:0], token.Value...)
key := string(*keyBytes)
PutByteSlice(keyBytes)
// Look ahead to determine context
// Look ahead to see what follows
nextToken, err := c.nextToken()
if err != nil {
if err == io.EOF {
// EOF after key - store as empty value
objectElements[key] = ""
isArray = false
return objectElements, nil
}
return nil, err
}
if nextToken.Type == TokenOpenBrace {
// Nested object
isArray = false // If we see a key, it's a map
nestedValue, err := c.parseObject()
if err != nil {
return nil, err
}
objectElements[key] = nestedValue
} else {
// Key-value pair
isArray = false // If we see a key, it's a map
value := c.tokenToValue(nextToken)
objectElements[key] = value
// Check if there's an object following
lookAhead, nextErr := c.nextToken()
if nextErr == nil && lookAhead.Type == TokenOpenBrace {
// Nested object after value
nestedValue, err := c.parseObject()
if err != nil {
return nil, err
}
// Check if we need to convert the value to a map
if mapValue, ok := nestedValue.(map[string]any); ok {
// Create a combined map
combinedMapRef := GetMap()
combinedMap := *combinedMapRef
for k, v := range mapValue {
combinedMap[k] = v
}
combinedMap["value"] = value
objectElements[key] = combinedMap
}
} else if nextErr == nil && lookAhead.Type != TokenEOF && lookAhead.Type != TokenCloseBrace {
c.scanner.UnreadToken(lookAhead)
} else if nextErr == nil && lookAhead.Type == TokenCloseBrace {
// We found the closing brace - unread it so it's handled by the main loop
c.scanner.UnreadToken(lookAhead)
}
}
case TokenString, TokenNumber, TokenBoolean:
// Array element
value := c.tokenToValue(token)
arrayElements = append(arrayElements, value)
case TokenOpenBrace:
// Nested object/array
nestedValue, err := c.parseObject()
if err != nil {
return nil, err
}
if nextToken.Type == TokenEquals {
// Key-value pair
current.isArray = false
current.currentKey = name
current.expectValue = true
// Parse the value
valueToken, err := c.nextToken()
if err != nil {
return nil, err
}
if valueToken.Type == TokenOpenBrace {
// Push new state for nested object/array
newState := &ParseState{
object: make(map[string]any, 8),
arrayElements: make([]any, 0, 8),
isArray: true,
}
stack = append(stack, newState)
} else {
// Handle primitive value
value := c.tokenToValue(valueToken)
current.object[name] = value
}
} else if nextToken.Type == TokenOpenBrace {
// Nested object with name
current.isArray = false
current.currentKey = name
current.expectValue = true
// Push new state for nested object
newState := &ParseState{
object: make(map[string]any, 8),
arrayElements: make([]any, 0, 8),
isArray: true,
}
stack = append(stack, newState)
if isArray {
arrayElements = append(arrayElements, nestedValue)
} else {
// Array element
c.scanner.UnreadToken(nextToken)
// Convert name to appropriate type
value := c.convertNameValue(name)
current.arrayElements = append(current.arrayElements, value)
// If we're in an object context, this is an error
return nil, c.Error("unexpected nested object without a key")
}
case TokenString, TokenNumber, TokenBoolean:
value := c.tokenToValue(token)
if current.expectValue {
current.object[current.currentKey] = value
current.expectValue = false
} else {
current.arrayElements = append(current.arrayElements, value)
}
case TokenOpenBrace:
// New nested object/array
newState := &ParseState{
object: make(map[string]any, 8),
arrayElements: make([]any, 0, 8),
isArray: true,
}
stack = append(stack, newState)
default:
return nil, c.Error(fmt.Sprintf("unexpected token: %v", token.Type))
return nil, c.Error(fmt.Sprintf("unexpected token type: %v", token.Type))
}
}
return nil, fmt.Errorf("unexpected end of parsing")
}
// Load parses a config from a reader
@ -503,13 +449,80 @@ func Load(r io.Reader) (*Config, error) {
err := config.Parse(r)
if err != nil {
config.Release()
return nil, err
}
return config, nil
}
// Helpers
// tokenToValue converts a token to a Go value, preserving byte slices until final conversion
func (c *Config) tokenToValue(token Token) any {
switch token.Type {
case TokenString:
// Convert to string using pooled buffer
valueBytes := GetByteSlice()
*valueBytes = append((*valueBytes)[:0], token.Value...)
result := string(*valueBytes)
PutByteSlice(valueBytes)
return result
case TokenNumber:
// Parse number
valueStr := string(token.Value)
if containsChar(token.Value, '.') {
// Float
val, _ := strconv.ParseFloat(valueStr, 64)
return val
}
// Integer
val, _ := strconv.ParseInt(valueStr, 10, 64)
return val
case TokenBoolean:
return bytesEqual(token.Value, []byte("true"))
case TokenName:
// Check if name is a special value
valueBytes := GetByteSlice()
*valueBytes = append((*valueBytes)[:0], token.Value...)
if bytesEqual(*valueBytes, []byte("true")) {
PutByteSlice(valueBytes)
return true
} else if bytesEqual(*valueBytes, []byte("false")) {
PutByteSlice(valueBytes)
return false
} else if isDigitOrMinus((*valueBytes)[0]) {
// Try to convert to number
valueStr := string(*valueBytes)
PutByteSlice(valueBytes)
if containsChar(token.Value, '.') {
val, err := strconv.ParseFloat(valueStr, 64)
if err == nil {
return val
}
} else {
val, err := strconv.ParseInt(valueStr, 10, 64)
if err == nil {
return val
}
}
return valueStr
}
// Default to string
result := string(*valueBytes)
PutByteSlice(valueBytes)
return result
default:
return nil
}
}
// Helper functions
func isLetter(b byte) bool {
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
@ -519,77 +532,27 @@ func isDigit(b byte) bool {
return b >= '0' && b <= '9'
}
// 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++ {
if s[i] == '.' {
// It's a float
return strconv.ParseFloat(s, 64)
}
}
// It's an integer
return strconv.ParseInt(s, 10, 64)
func isDigitOrMinus(b byte) bool {
return isDigit(b) || b == '-'
}
// bytesEqual compares a byte slice with either a string or byte slice
func bytesEqual(b []byte, s []byte) bool {
if len(b) != len(s) {
func bytesEqual(b1, b2 []byte) bool {
if len(b1) != len(b2) {
return false
}
for i := 0; i < len(b); i++ {
if b[i] != s[i] {
for i := 0; i < len(b1); i++ {
if b1[i] != b2[i] {
return false
}
}
return true
}
// 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)
func containsChar(b []byte, c byte) bool {
for _, v := range b {
if v == c {
return true
}
}
// It's an integer
return strconv.ParseInt(s, 10, 64)
}
func (c *Config) tokenToValue(token Token) any {
switch token.Type {
case TokenString:
return string(token.Value)
case TokenNumber:
val, _ := parseStringAsNumber(string(token.Value))
return val
case TokenBoolean:
return bytesEqual(token.Value, []byte("true"))
default:
return string(token.Value)
}
}
func (c *Config) convertNameValue(name string) any {
if name == "true" {
return true
} else if name == "false" {
return false
} else if isDigitOrMinus(name) {
val, err := parseStringAsNumber(name)
if err == nil {
return val
}
}
return name
return false
}

71
pool.go Normal file
View File

@ -0,0 +1,71 @@
package config
import (
"sync"
)
// byteSlicePool helps reuse byte slices
var byteSlicePool = sync.Pool{
New: func() any {
b := make([]byte, 0, 64)
return &b
},
}
// GetByteSlice gets a byte slice from the pool
func GetByteSlice() *[]byte {
return byteSlicePool.Get().(*[]byte)
}
// PutByteSlice returns a byte slice to the pool
func PutByteSlice(b *[]byte) {
if b != nil {
*b = (*b)[:0] // Clear but keep capacity
byteSlicePool.Put(b)
}
}
// mapPool helps reuse maps for config objects
var mapPool = sync.Pool{
New: func() any {
m := make(map[string]any, 8)
return &m
},
}
// GetMap gets a map from the pool
func GetMap() *map[string]any {
return mapPool.Get().(*map[string]any)
}
// PutMap returns a map to the pool after clearing it
func PutMap(m *map[string]any) {
if m != nil {
// Clear the map
for k := range *m {
delete(*m, k)
}
mapPool.Put(m)
}
}
// arrayPool helps reuse slices
var arrayPool = sync.Pool{
New: func() any {
a := make([]any, 0, 4)
return &a
},
}
// GetArray gets a slice from the pool
func GetArray() *[]any {
return arrayPool.Get().(*[]any)
}
// PutArray returns a slice to the pool
func PutArray(a *[]any) {
if a != nil {
*a = (*a)[:0] // Clear but keep capacity
arrayPool.Put(a)
}
}

View File

@ -19,20 +19,23 @@ var (
// Scanner handles the low-level parsing of the configuration format
type Scanner struct {
reader *bufio.Reader
line int
col int
buffer []byte
token Token // Current token for unread
reader *bufio.Reader
line int
col int
buffer []byte // Slice to the pooled buffer
bufferRef *[]byte // Reference to the pooled buffer
token Token // Current token for unread
}
// scannerPool helps reuse scanner objects
var scannerPool = sync.Pool{
New: func() interface{} {
New: func() any {
bufferRef := GetByteSlice()
return &Scanner{
line: 1,
col: 0,
buffer: make([]byte, 0, 128),
line: 1,
col: 0,
bufferRef: bufferRef,
buffer: (*bufferRef)[:0],
}
},
}
@ -40,10 +43,10 @@ var scannerPool = sync.Pool{
// NewScanner creates a new scanner from a pool
func NewScanner(r io.Reader) *Scanner {
s := scannerPool.Get().(*Scanner)
s.reader = bufio.NewReader(r)
s.reader = bufio.NewReaderSize(r, 1024)
s.line = 1
s.col = 0
s.buffer = s.buffer[:0]
s.buffer = (*s.bufferRef)[:0]
s.token = Token{Type: TokenError}
return s
}
@ -53,7 +56,7 @@ func ReleaseScanner(s *Scanner) {
if s != nil {
// Clear references but keep allocated memory
s.reader = nil
s.buffer = s.buffer[:0]
s.buffer = (*s.bufferRef)[:0]
scannerPool.Put(s)
}
}
@ -139,19 +142,19 @@ func (s *Scanner) NextToken() (Token, error) {
// Skip whitespace
err := s.SkipWhitespace()
if err == io.EOF {
return Token{Type: TokenEOF}, nil
}
if err != nil {
return Token{Type: TokenError, Value: []byte(err.Error())}, err
if err == io.EOF {
return Token{Type: TokenEOF, Line: s.line, Column: s.col}, nil
}
return Token{Type: TokenError, Value: []byte(err.Error()), Line: s.line, Column: s.col}, err
}
b, err := s.PeekByte()
if err != nil {
if err == io.EOF {
return Token{Type: TokenEOF}, nil
return Token{Type: TokenEOF, Line: s.line, Column: s.col}, nil
}
return Token{Type: TokenError, Value: []byte(err.Error())}, err
return Token{Type: TokenError, Value: []byte(err.Error()), Line: s.line, Column: s.col}, err
}
// Record start position for error reporting
@ -159,10 +162,6 @@ func (s *Scanner) NextToken() (Token, error) {
// Process based on first character
switch {
case b == '=':
_, _ = s.ReadByte() // consume equals
return Token{Type: TokenEquals, Line: startLine, Column: startColumn}, nil
case b == '{':
_, _ = s.ReadByte() // consume open brace
return Token{Type: TokenOpenBrace, Line: startLine, Column: startColumn}, nil
@ -264,7 +263,7 @@ func (s *Scanner) scanComment() error {
// scanString scans a quoted string
func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
// Reset buffer
s.buffer = s.buffer[:0]
s.buffer = (*s.bufferRef)[:0]
// Consume opening quote
_, err := s.ReadByte()
@ -317,7 +316,7 @@ func (s *Scanner) scanString(startLine, startColumn int) (Token, error) {
// scanName scans an identifier
func (s *Scanner) scanName(startLine, startColumn int) (Token, error) {
// Reset buffer
s.buffer = s.buffer[:0]
s.buffer = (*s.bufferRef)[:0]
// Read first character
b, err := s.ReadByte()
@ -363,7 +362,7 @@ func (s *Scanner) scanName(startLine, startColumn int) (Token, error) {
// scanNumber scans a numeric value
func (s *Scanner) scanNumber(startLine, startColumn int) (Token, error) {
// Reset buffer
s.buffer = s.buffer[:0]
s.buffer = (*s.bufferRef)[:0]
// Read first character (might be a minus sign or digit)
b, err := s.ReadByte()

View File

@ -10,13 +10,13 @@ import (
func TestBasicKeyValuePairs(t *testing.T) {
input := `
boolTrue = true
boolFalse = false
integer = 42
negativeInt = -10
floatValue = 3.14
negativeFloat = -2.5
stringValue = "hello world"
boolTrue true
boolFalse false
integer 42
negativeInt -10
floatValue 3.14
negativeFloat -2.5
stringValue "hello world"
`
config, err := config.Load(strings.NewReader(input))
if err != nil {
@ -83,18 +83,18 @@ func TestBasicKeyValuePairs(t *testing.T) {
func TestComments(t *testing.T) {
input := `
-- This is a line comment
key1 = "value1"
key1 "value1"
--[[ This is a
block comment spanning
multiple lines ]]
key2 = "value2"
key2 "value2"
settings {
-- Comment inside a map
timeout = 30
timeout 30
--[[ Another block comment ]]
retries = 3
retries 3
}
`
@ -135,15 +135,6 @@ func TestArrays(t *testing.T) {
"cherry"
}
-- Array with equals sign
numbers = {
1
2
3
4
5
}
-- Mixed types array
mixed {
"string"
@ -175,23 +166,6 @@ func TestArrays(t *testing.T) {
t.Errorf("expected fruits.0=\"apple\", got %v, err: %v", apple, err)
}
// Verify array with equals sign
numbers, err := config.GetArray("numbers")
if err != nil {
t.Fatalf("failed to get numbers array: %v", err)
}
// Check array length
if len(numbers) != 5 {
t.Errorf("expected 5 numbers, got %d", len(numbers))
}
// Verify first number
firstNumber, err := config.GetInt("numbers.0")
if err != nil || firstNumber != 1 {
t.Errorf("expected numbers.0=1, got %v, err: %v", firstNumber, err)
}
// Verify mixed types array
mixed, err := config.GetArray("mixed")
if err != nil {
@ -228,28 +202,20 @@ func TestMaps(t *testing.T) {
input := `
-- Simple map
server {
host = "localhost"
port = 8080
}
-- Map with equals sign
database = {
username = "admin"
password = "secret"
enabled = true
maxConnections = 100
host "localhost"
port 8080
}
-- Nested maps
application {
name = "MyApp"
version = "1.0.0"
name "MyApp"
version "1.0.0"
settings {
theme = "dark"
notifications = true
theme "dark"
notifications true
logging {
level = "info"
file = "app.log"
level "info"
file "app.log"
}
}
}
@ -281,17 +247,6 @@ func TestMaps(t *testing.T) {
t.Errorf("expected server.port=8080, got %v, err: %v", port, err)
}
// Verify map with equals sign
dbUser, err := config.GetString("database.username")
if err != nil || dbUser != "admin" {
t.Errorf("expected database.username=\"admin\", got %v, err: %v", dbUser, err)
}
dbEnabled, err := config.GetBool("database.enabled")
if err != nil || dbEnabled != true {
t.Errorf("expected database.enabled=true, got %v, err: %v", dbEnabled, err)
}
// Verify deeply nested maps
appName, err := config.GetString("application.name")
if err != nil || appName != "MyApp" {

View File

@ -10,7 +10,6 @@ const (
TokenString
TokenNumber
TokenBoolean
TokenEquals
TokenOpenBrace
TokenCloseBrace
TokenComment