1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
package scfg
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
// This limits the max block nesting depth to prevent stack overflows.
const maxNestingDepth = 1000
// Load loads a configuration file.
func Load(path string) (Block, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return Read(f)
}
// Read parses a configuration file from an io.Reader.
func Read(r io.Reader) (Block, error) {
scanner := bufio.NewScanner(r)
dec := decoder{scanner: scanner}
block, closingBrace, err := dec.readBlock()
if err != nil {
return nil, err
} else if closingBrace {
return nil, fmt.Errorf("line %v: unexpected '}'", dec.lineno)
}
return block, scanner.Err()
}
type decoder struct {
scanner *bufio.Scanner
lineno int
blockDepth int
}
// readBlock reads a block. closingBrace is true if parsing stopped on '}'
// (otherwise, it stopped on Scanner.Scan).
func (dec *decoder) readBlock() (block Block, closingBrace bool, err error) {
dec.blockDepth++
defer func() {
dec.blockDepth--
}()
if dec.blockDepth >= maxNestingDepth {
return nil, false, fmt.Errorf("exceeded max block depth")
}
for dec.scanner.Scan() {
dec.lineno++
l := dec.scanner.Text()
words, err := splitWords(l)
if err != nil {
return nil, false, fmt.Errorf("line %v: %v", dec.lineno, err)
} else if len(words) == 0 {
continue
}
if len(words) == 1 && l[len(l)-1] == '}' {
closingBrace = true
break
}
var d *Directive
if words[len(words)-1] == "{" && l[len(l)-1] == '{' {
words = words[:len(words)-1]
var name string
params := words
if len(words) > 0 {
name, params = words[0], words[1:]
}
startLineno := dec.lineno
childBlock, childClosingBrace, err := dec.readBlock()
if err != nil {
return nil, false, err
} else if !childClosingBrace {
return nil, false, fmt.Errorf("line %v: unterminated block", startLineno)
}
// Allows callers to tell apart "no block" and "empty block"
if childBlock == nil {
childBlock = Block{}
}
d = &Directive{Name: name, Params: params, Children: childBlock, lineno: dec.lineno}
} else {
d = &Directive{Name: words[0], Params: words[1:], lineno: dec.lineno}
}
block = append(block, d)
}
return block, closingBrace, nil
}
func splitWords(l string) ([]string, error) {
var (
words []string
sb strings.Builder
escape bool
quote rune
wantWSP bool
)
for _, ch := range l {
switch {
case escape:
sb.WriteRune(ch)
escape = false
case wantWSP && (ch != ' ' && ch != '\t'):
return words, fmt.Errorf("atom not allowed after quoted string")
case ch == '\\':
escape = true
case quote != 0 && ch == quote:
quote = 0
wantWSP = true
if sb.Len() == 0 {
words = append(words, "")
}
case quote == 0 && len(words) == 0 && sb.Len() == 0 && ch == '#':
return nil, nil
case quote == 0 && (ch == '\'' || ch == '"'):
if sb.Len() > 0 {
return words, fmt.Errorf("quoted string not allowed after atom")
}
quote = ch
case quote == 0 && (ch == ' ' || ch == '\t'):
if sb.Len() > 0 {
words = append(words, sb.String())
}
sb.Reset()
wantWSP = false
default:
sb.WriteRune(ch)
}
}
if quote != 0 {
return words, fmt.Errorf("unterminated quoted string")
}
if sb.Len() > 0 {
words = append(words, sb.String())
}
return words, nil
}
|