forked from emilsjolander/goson
/
parser.go
215 lines (187 loc) · 5.84 KB
/
parser.go
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package goson
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
)
type parser struct {
workingDir string //the directory of the template. Used as root for include statements
tokens []token
args Args
position int
result []byte
}
func (p *parser) parse() {
for p.position < len(p.tokens) {
tId := p.currentToken().id
switch tId {
case TOKEN_KEY:
p.parseKey()
tId = p.currentToken().id //parse key will have incremented p.position by one
switch tId {
case TOKEN_OPEN_BRACE:
p.parseObject()
case TOKEN_INT, TOKEN_BOOL, TOKEN_FLOAT, TOKEN_STRING:
p.parseValue()
case TOKEN_ALIAS:
p.parseAlias()
case TOKEN_LOOP:
p.parseLoop()
case TOKEN_ARGUMENT:
p.parseArgument()
default:
panic(fmt.Sprintf("Syntax error: Unexpected token %s", p.currentToken().match))
}
case TOKEN_INCLUDE:
p.parseInclude()
case TOKEN_COMMENT:
p.position++
default:
panic(fmt.Sprintf("Syntax error: Unexpected token %s", p.currentToken().match))
}
//add a comma if there are more tokens and the next token is not a comment
if p.position < len(p.tokens) && p.tokens[p.position].id != TOKEN_COMMENT {
p.appendComma()
}
}
}
func (p *parser) parseObject() {
//get the scope of this opening brace and make a recursive call
p.position++ //move past the opening brace
scopeParser := p.getScope()
p.position += len(scopeParser.tokens) + 1 //+1 for closing brace
scopeParser.parse()
p.appendObject(scopeParser.result)
}
func (p *parser) parseKey() {
//add the key to the result
p.appendKey(p.currentToken().match)
p.position++
}
func (p *parser) parseValue() {
//just add the value
p.appendValue(p.currentToken().match)
p.position++
}
func (p *parser) parseAlias() {
if p.tokens[p.position+1].id != TOKEN_OPEN_BRACE {
panic("Syntax error: Expected opening brace after alias declaration")
}
//get the arguments of the alias
valueAsKey := bytes.Split(p.currentToken().match, []byte(" as "))
key := string(bytes.Trim(valueAsKey[1], " "))
value := objectForKey(p.args, bytes.Trim(valueAsKey[0], " "))
//get the scope and add the value to the args map
p.position += 2 //move past the opening brace
scopeParser := p.getScope()
scopeParser.args[key] = value
p.position += len(scopeParser.tokens) + 1 //+1 for closing brace
scopeParser.parse()
p.appendObject(scopeParser.result)
//remove the alias from args
delete(scopeParser.args, key)
}
func (p *parser) parseLoop() {
if p.tokens[p.position+1].id != TOKEN_OPEN_BRACE {
panic("Syntax error: Expected opening brace after loop declaration")
}
//get the arguments for the loop
keyInCollection := bytes.Split(p.currentToken().match, []byte(" in "))
key := string(bytes.Trim(keyInCollection[0], " "))
collection := collectionForKey(p.args, bytes.Trim(keyInCollection[1], " "))
//get the scope
p.position += 2 //move past the opening brace
scopeParser := p.getScope()
p.position += len(scopeParser.tokens) + 1 //+1 for closing brace
//iterate through the collection and make a recusive call for each object in the collection keeping the same scope.
objects := make([][]byte, collection.Len())
for i := 0; i < collection.Len(); i++ {
//reset the fields of the scope parser and set the new loop variable
scopeParser.args[key] = collection.Get(i)
scopeParser.position = 0
scopeParser.result = []byte{}
scopeParser.parse()
objects[i] = scopeParser.result
}
//add the resulting array to the result
p.appendArray(objects)
//remove the loop variable from args
delete(scopeParser.args, key)
}
func (p *parser) parseArgument() {
//get the value for the key and add it to the result
value := valueForKey(p.args, p.currentToken().match)
p.appendValue(value)
p.position++
}
func (p *parser) parseInclude() {
statement := p.currentToken().match
params := bytes.Split(statement[8:len(statement)-1], []byte{','}) //strip away include() and split by comma
templateName := p.workingDir + string(bytes.Trim(params[0], " "))
template, err := ioutil.ReadFile(templateName + ".goson")
//probably cannot find the template file
if err != nil {
panic(err)
}
lastPathSegmentStart := strings.LastIndex(templateName, "/")
var workingDir string
if lastPathSegmentStart >= 0 {
workingDir = templateName[0 : lastPathSegmentStart+1]
}
tokens := Tokenize(template)
args := explodeIntoArgs(objectForKey(p.args, bytes.Trim(params[1], " ")))
includeParser := &parser{workingDir: workingDir, tokens: tokens, args: args}
includeParser.parse()
p.appendJson(includeParser.result)
p.position++
}
func (p *parser) currentToken() token {
return p.tokens[p.position]
}
func (p *parser) appendComma() {
p.result = append(p.result, ',')
}
func (p *parser) appendKey(key []byte) {
key = bytes.Trim(key[:len(key)-1], " ")
key = quote(key)
p.result = append(p.result, key...)
p.result = append(p.result, ':')
}
func (p *parser) appendValue(value []byte) {
p.result = append(p.result, value...)
}
func (p *parser) appendObject(object []byte) {
object = append(object, '}')
p.result = append(p.result, '{')
p.result = append(p.result, object...)
}
func (p *parser) appendJson(json []byte) {
p.result = append(p.result, json...)
}
func (p *parser) appendArray(objects [][]byte) {
p.result = append(p.result, '[')
for i, object := range objects {
p.appendObject(object)
if i < len(objects)-1 {
p.appendComma()
}
}
p.result = append(p.result, ']')
}
//get the tokens in the current scope. parser.position should be located at the first token of the scope (token after the opening brace)
func (p *parser) getScope() *parser {
braceCount := 1
for i, t := range p.tokens[p.position:] {
if t.id == TOKEN_OPEN_BRACE {
braceCount++
} else if t.id == TOKEN_CLOSE_BRACE {
braceCount--
}
if braceCount == 0 {
return &parser{workingDir: p.workingDir, tokens: p.tokens[p.position : p.position+i], args: p.args}
}
}
panic("Syntax error: End of scope could not be found")
return nil
}