ini_parser.go (8626B)
1 package ini 2 3 import ( 4 "fmt" 5 "io" 6 ) 7 8 // ParseState represents the current state of the parser. 9 type ParseState uint 10 11 // State enums for the parse table 12 const ( 13 InvalidState ParseState = iota 14 // stmt -> value stmt' 15 StatementState 16 // stmt' -> MarkComplete | op stmt 17 StatementPrimeState 18 // value -> number | string | boolean | quoted_string 19 ValueState 20 // section -> [ section' 21 OpenScopeState 22 // section' -> value section_close 23 SectionState 24 // section_close -> ] 25 CloseScopeState 26 // SkipState will skip (NL WS)+ 27 SkipState 28 // SkipTokenState will skip any token and push the previous 29 // state onto the stack. 30 SkipTokenState 31 // comment -> # comment' | ; comment' 32 // comment' -> MarkComplete | value 33 CommentState 34 // MarkComplete state will complete statements and move that 35 // to the completed AST list 36 MarkCompleteState 37 // TerminalState signifies that the tokens have been fully parsed 38 TerminalState 39 ) 40 41 // parseTable is a state machine to dictate the grammar above. 42 var parseTable = map[ASTKind]map[TokenType]ParseState{ 43 ASTKindStart: { 44 TokenLit: StatementState, 45 TokenSep: OpenScopeState, 46 TokenWS: SkipTokenState, 47 TokenNL: SkipTokenState, 48 TokenComment: CommentState, 49 TokenNone: TerminalState, 50 }, 51 ASTKindCommentStatement: { 52 TokenLit: StatementState, 53 TokenSep: OpenScopeState, 54 TokenWS: SkipTokenState, 55 TokenNL: SkipTokenState, 56 TokenComment: CommentState, 57 TokenNone: MarkCompleteState, 58 }, 59 ASTKindExpr: { 60 TokenOp: StatementPrimeState, 61 TokenLit: ValueState, 62 TokenSep: OpenScopeState, 63 TokenWS: ValueState, 64 TokenNL: SkipState, 65 TokenComment: CommentState, 66 TokenNone: MarkCompleteState, 67 }, 68 ASTKindEqualExpr: { 69 TokenLit: ValueState, 70 TokenSep: ValueState, 71 TokenOp: ValueState, 72 TokenWS: SkipTokenState, 73 TokenNL: SkipState, 74 }, 75 ASTKindStatement: { 76 TokenLit: SectionState, 77 TokenSep: CloseScopeState, 78 TokenWS: SkipTokenState, 79 TokenNL: SkipTokenState, 80 TokenComment: CommentState, 81 TokenNone: MarkCompleteState, 82 }, 83 ASTKindExprStatement: { 84 TokenLit: ValueState, 85 TokenSep: ValueState, 86 TokenOp: ValueState, 87 TokenWS: ValueState, 88 TokenNL: MarkCompleteState, 89 TokenComment: CommentState, 90 TokenNone: TerminalState, 91 TokenComma: SkipState, 92 }, 93 ASTKindSectionStatement: { 94 TokenLit: SectionState, 95 TokenOp: SectionState, 96 TokenSep: CloseScopeState, 97 TokenWS: SectionState, 98 TokenNL: SkipTokenState, 99 }, 100 ASTKindCompletedSectionStatement: { 101 TokenWS: SkipTokenState, 102 TokenNL: SkipTokenState, 103 TokenLit: StatementState, 104 TokenSep: OpenScopeState, 105 TokenComment: CommentState, 106 TokenNone: MarkCompleteState, 107 }, 108 ASTKindSkipStatement: { 109 TokenLit: StatementState, 110 TokenSep: OpenScopeState, 111 TokenWS: SkipTokenState, 112 TokenNL: SkipTokenState, 113 TokenComment: CommentState, 114 TokenNone: TerminalState, 115 }, 116 } 117 118 // ParseAST will parse input from an io.Reader using 119 // an LL(1) parser. 120 func ParseAST(r io.Reader) ([]AST, error) { 121 lexer := iniLexer{} 122 tokens, err := lexer.Tokenize(r) 123 if err != nil { 124 return []AST{}, err 125 } 126 127 return parse(tokens) 128 } 129 130 // ParseASTBytes will parse input from a byte slice using 131 // an LL(1) parser. 132 func ParseASTBytes(b []byte) ([]AST, error) { 133 lexer := iniLexer{} 134 tokens, err := lexer.tokenize(b) 135 if err != nil { 136 return []AST{}, err 137 } 138 139 return parse(tokens) 140 } 141 142 func parse(tokens []Token) ([]AST, error) { 143 start := Start 144 stack := newParseStack(3, len(tokens)) 145 146 stack.Push(start) 147 s := newSkipper() 148 149 loop: 150 for stack.Len() > 0 { 151 k := stack.Pop() 152 153 var tok Token 154 if len(tokens) == 0 { 155 // this occurs when all the tokens have been processed 156 // but reduction of what's left on the stack needs to 157 // occur. 158 tok = emptyToken 159 } else { 160 tok = tokens[0] 161 } 162 163 step := parseTable[k.Kind][tok.Type()] 164 if s.ShouldSkip(tok) { 165 // being in a skip state with no tokens will break out of 166 // the parse loop since there is nothing left to process. 167 if len(tokens) == 0 { 168 break loop 169 } 170 // if should skip is true, we skip the tokens until should skip is set to false. 171 step = SkipTokenState 172 } 173 174 switch step { 175 case TerminalState: 176 // Finished parsing. Push what should be the last 177 // statement to the stack. If there is anything left 178 // on the stack, an error in parsing has occurred. 179 if k.Kind != ASTKindStart { 180 stack.MarkComplete(k) 181 } 182 break loop 183 case SkipTokenState: 184 // When skipping a token, the previous state was popped off the stack. 185 // To maintain the correct state, the previous state will be pushed 186 // onto the stack. 187 stack.Push(k) 188 case StatementState: 189 if k.Kind != ASTKindStart { 190 stack.MarkComplete(k) 191 } 192 expr := newExpression(tok) 193 stack.Push(expr) 194 case StatementPrimeState: 195 if tok.Type() != TokenOp { 196 stack.MarkComplete(k) 197 continue 198 } 199 200 if k.Kind != ASTKindExpr { 201 return nil, NewParseError( 202 fmt.Sprintf("invalid expression: expected Expr type, but found %T type", k), 203 ) 204 } 205 206 k = trimSpaces(k) 207 expr := newEqualExpr(k, tok) 208 stack.Push(expr) 209 case ValueState: 210 // ValueState requires the previous state to either be an equal expression 211 // or an expression statement. 212 switch k.Kind { 213 case ASTKindEqualExpr: 214 // assigning a value to some key 215 k.AppendChild(newExpression(tok)) 216 stack.Push(newExprStatement(k)) 217 case ASTKindExpr: 218 k.Root.raw = append(k.Root.raw, tok.Raw()...) 219 stack.Push(k) 220 case ASTKindExprStatement: 221 root := k.GetRoot() 222 children := root.GetChildren() 223 if len(children) == 0 { 224 return nil, NewParseError( 225 fmt.Sprintf("invalid expression: AST contains no children %s", k.Kind), 226 ) 227 } 228 229 rhs := children[len(children)-1] 230 231 if rhs.Root.ValueType != QuotedStringType { 232 rhs.Root.ValueType = StringType 233 rhs.Root.raw = append(rhs.Root.raw, tok.Raw()...) 234 235 } 236 237 children[len(children)-1] = rhs 238 root.SetChildren(children) 239 240 stack.Push(k) 241 } 242 case OpenScopeState: 243 if !runeCompare(tok.Raw(), openBrace) { 244 return nil, NewParseError("expected '['") 245 } 246 // If OpenScopeState is not at the start, we must mark the previous ast as complete 247 // 248 // for example: if previous ast was a skip statement; 249 // we should mark it as complete before we create a new statement 250 if k.Kind != ASTKindStart { 251 stack.MarkComplete(k) 252 } 253 254 stmt := newStatement() 255 stack.Push(stmt) 256 case CloseScopeState: 257 if !runeCompare(tok.Raw(), closeBrace) { 258 return nil, NewParseError("expected ']'") 259 } 260 261 k = trimSpaces(k) 262 stack.Push(newCompletedSectionStatement(k)) 263 case SectionState: 264 var stmt AST 265 266 switch k.Kind { 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. 270 // 271 // This handles cases like [ profile default ] 272 // 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()...) 278 stmt = k 279 default: 280 return nil, NewParseError( 281 fmt.Sprintf("invalid statement: expected statement: %v", k.Kind), 282 ) 283 } 284 285 stack.Push(stmt) 286 case MarkCompleteState: 287 if k.Kind != ASTKindStart { 288 stack.MarkComplete(k) 289 } 290 291 if stack.Len() == 0 { 292 stack.Push(start) 293 } 294 case SkipState: 295 stack.Push(newSkipStatement(k)) 296 s.Skip() 297 case CommentState: 298 if k.Kind == ASTKindStart { 299 stack.Push(k) 300 } else { 301 stack.MarkComplete(k) 302 } 303 304 stmt := newCommentStatement(tok) 305 stack.Push(stmt) 306 default: 307 return nil, NewParseError( 308 fmt.Sprintf("invalid state with ASTKind %v and TokenType %v", 309 k.Kind, tok.Type())) 310 } 311 312 if len(tokens) > 0 { 313 tokens = tokens[1:] 314 } 315 } 316 317 // this occurs when a statement has not been completed 318 if stack.top > 1 { 319 return nil, NewParseError(fmt.Sprintf("incomplete ini expression")) 320 } 321 322 // returns a sublist which exludes the start symbol 323 return stack.List(), nil 324 } 325 326 // trimSpaces will trim spaces on the left and right hand side of 327 // the literal. 328 func trimSpaces(k AST) AST { 329 // trim left hand side of spaces 330 for i := 0; i < len(k.Root.raw); i++ { 331 if !isWhitespace(k.Root.raw[i]) { 332 break 333 } 334 335 k.Root.raw = k.Root.raw[1:] 336 i-- 337 } 338 339 // trim right hand side of spaces 340 for i := len(k.Root.raw) - 1; i >= 0; i-- { 341 if !isWhitespace(k.Root.raw[i]) { 342 break 343 } 344 345 k.Root.raw = k.Root.raw[:len(k.Root.raw)-1] 346 } 347 348 return k 349 }