src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }