8 // State enums for the parse table
11 // stmt -> value stmt'
13 // stmt' -> MarkComplete | op stmt
15 // value -> number | string | boolean | quoted_string
17 // section -> [ section'
19 // section' -> value section_close
23 // SkipState will skip (NL WS)+
25 // SkipTokenState will skip any token and push the previous
26 // state onto the stack.
28 // comment -> # comment' | ; comment'
29 // comment' -> MarkComplete | value
31 // MarkComplete state will complete statements and move that
32 // to the completed AST list
34 // TerminalState signifies that the tokens have been fully parsed
38 // parseTable is a state machine to dictate the grammar above.
39 var parseTable = map[ASTKind]map[TokenType]int{
40 ASTKindStart: map[TokenType]int{
41 TokenLit: StatementState,
42 TokenSep: OpenScopeState,
43 TokenWS: SkipTokenState,
44 TokenNL: SkipTokenState,
45 TokenComment: CommentState,
46 TokenNone: TerminalState,
48 ASTKindCommentStatement: map[TokenType]int{
49 TokenLit: StatementState,
50 TokenSep: OpenScopeState,
51 TokenWS: SkipTokenState,
52 TokenNL: SkipTokenState,
53 TokenComment: CommentState,
54 TokenNone: MarkCompleteState,
56 ASTKindExpr: map[TokenType]int{
57 TokenOp: StatementPrimeState,
59 TokenSep: OpenScopeState,
62 TokenComment: CommentState,
63 TokenNone: MarkCompleteState,
65 ASTKindEqualExpr: map[TokenType]int{
67 TokenWS: SkipTokenState,
70 ASTKindStatement: map[TokenType]int{
71 TokenLit: SectionState,
72 TokenSep: CloseScopeState,
73 TokenWS: SkipTokenState,
74 TokenNL: SkipTokenState,
75 TokenComment: CommentState,
76 TokenNone: MarkCompleteState,
78 ASTKindExprStatement: map[TokenType]int{
80 TokenSep: OpenScopeState,
83 TokenNL: MarkCompleteState,
84 TokenComment: CommentState,
85 TokenNone: TerminalState,
86 TokenComma: SkipState,
88 ASTKindSectionStatement: map[TokenType]int{
89 TokenLit: SectionState,
90 TokenOp: SectionState,
91 TokenSep: CloseScopeState,
92 TokenWS: SectionState,
93 TokenNL: SkipTokenState,
95 ASTKindCompletedSectionStatement: map[TokenType]int{
96 TokenWS: SkipTokenState,
97 TokenNL: SkipTokenState,
98 TokenLit: StatementState,
99 TokenSep: OpenScopeState,
100 TokenComment: CommentState,
101 TokenNone: MarkCompleteState,
103 ASTKindSkipStatement: map[TokenType]int{
104 TokenLit: StatementState,
105 TokenSep: OpenScopeState,
106 TokenWS: SkipTokenState,
107 TokenNL: SkipTokenState,
108 TokenComment: CommentState,
109 TokenNone: TerminalState,
113 // ParseAST will parse input from an io.Reader using
115 func ParseAST(r io.Reader) ([]AST, error) {
117 tokens, err := lexer.Tokenize(r)
125 // ParseASTBytes will parse input from a byte slice using
127 func ParseASTBytes(b []byte) ([]AST, error) {
129 tokens, err := lexer.tokenize(b)
137 func parse(tokens []Token) ([]AST, error) {
139 stack := newParseStack(3, len(tokens))
145 for stack.Len() > 0 {
149 if len(tokens) == 0 {
150 // this occurs when all the tokens have been processed
151 // but reduction of what's left on the stack needs to
158 step := parseTable[k.Kind][tok.Type()]
159 if s.ShouldSkip(tok) {
160 // being in a skip state with no tokens will break out of
161 // the parse loop since there is nothing left to process.
162 if len(tokens) == 0 {
166 step = SkipTokenState
171 // Finished parsing. Push what should be the last
172 // statement to the stack. If there is anything left
173 // on the stack, an error in parsing has occurred.
174 if k.Kind != ASTKindStart {
175 stack.MarkComplete(k)
179 // When skipping a token, the previous state was popped off the stack.
180 // To maintain the correct state, the previous state will be pushed
184 if k.Kind != ASTKindStart {
185 stack.MarkComplete(k)
187 expr := newExpression(tok)
189 case StatementPrimeState:
190 if tok.Type() != TokenOp {
191 stack.MarkComplete(k)
195 if k.Kind != ASTKindExpr {
196 return nil, NewParseError(
197 fmt.Sprintf("invalid expression: expected Expr type, but found %T type", k),
202 expr := newEqualExpr(k, tok)
205 // ValueState requires the previous state to either be an equal expression
206 // or an expression statement.
208 // This grammar occurs when the RHS is a number, word, or quoted string.
209 // equal_expr -> lit op equal_expr'
210 // equal_expr' -> number | string | quoted_string
211 // quoted_string -> " quoted_string'
212 // quoted_string' -> string quoted_string_end
213 // quoted_string_end -> "
216 // expr_stmt -> equal_expr (expr_stmt')*
217 // expr_stmt' -> ws S | op S | MarkComplete
218 // S -> equal_expr' expr_stmt'
220 case ASTKindEqualExpr:
221 // assiging a value to some key
222 k.AppendChild(newExpression(tok))
223 stack.Push(newExprStatement(k))
225 k.Root.raw = append(k.Root.raw, tok.Raw()...)
227 case ASTKindExprStatement:
229 children := root.GetChildren()
230 if len(children) == 0 {
231 return nil, NewParseError(
232 fmt.Sprintf("invalid expression: AST contains no children %s", k.Kind),
236 rhs := children[len(children)-1]
238 if rhs.Root.ValueType != QuotedStringType {
239 rhs.Root.ValueType = StringType
240 rhs.Root.raw = append(rhs.Root.raw, tok.Raw()...)
244 children[len(children)-1] = rhs
245 k.SetChildren(children)
250 if !runeCompare(tok.Raw(), openBrace) {
251 return nil, NewParseError("expected '['")
254 stmt := newStatement()
256 case CloseScopeState:
257 if !runeCompare(tok.Raw(), closeBrace) {
258 return nil, NewParseError("expected ']'")
262 stack.Push(newCompletedSectionStatement(k))
267 case ASTKindStatement:
268 // If there are multiple literals inside of a scope declaration,
269 // then the current token's raw value will be appended to the Name.
271 // This handles cases like [ profile default ]
273 // k will represent a SectionStatement with the children representing
274 // the label of the section
275 stmt = newSectionStatement(tok)
276 case ASTKindSectionStatement:
277 k.Root.raw = append(k.Root.raw, tok.Raw()...)
280 return nil, NewParseError(
281 fmt.Sprintf("invalid statement: expected statement: %v", k.Kind),
286 case MarkCompleteState:
287 if k.Kind != ASTKindStart {
288 stack.MarkComplete(k)
291 if stack.Len() == 0 {
295 stack.Push(newSkipStatement(k))
298 if k.Kind == ASTKindStart {
301 stack.MarkComplete(k)
304 stmt := newCommentStatement(tok)
307 return nil, NewParseError(fmt.Sprintf("invalid state with ASTKind %v and TokenType %v", k, tok))
315 // this occurs when a statement has not been completed
317 return nil, NewParseError(fmt.Sprintf("incomplete expression: %v", stack.container))
320 // returns a sublist which excludes the start symbol
321 return stack.List(), nil
324 // trimSpaces will trim spaces on the left and right hand side of
326 func trimSpaces(k AST) AST {
327 // trim left hand side of spaces
328 for i := 0; i < len(k.Root.raw); i++ {
329 if !isWhitespace(k.Root.raw[i]) {
333 k.Root.raw = k.Root.raw[1:]
337 // trim right hand side of spaces
338 for i := len(k.Root.raw) - 1; i >= 0; i-- {
339 if !isWhitespace(k.Root.raw[i]) {
343 k.Root.raw = k.Root.raw[:len(k.Root.raw)-1]