mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			358 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			358 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package ini
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| )
 | |
| 
 | |
| // State enums for the parse table
 | |
| const (
 | |
| 	InvalidState = iota
 | |
| 	// stmt -> value stmt'
 | |
| 	StatementState
 | |
| 	// stmt' -> MarkComplete | op stmt
 | |
| 	StatementPrimeState
 | |
| 	// value -> number | string | boolean | quoted_string
 | |
| 	ValueState
 | |
| 	// section -> [ section'
 | |
| 	OpenScopeState
 | |
| 	// section' -> value section_close
 | |
| 	SectionState
 | |
| 	// section_close -> ]
 | |
| 	CloseScopeState
 | |
| 	// SkipState will skip (NL WS)+
 | |
| 	SkipState
 | |
| 	// SkipTokenState will skip any token and push the previous
 | |
| 	// state onto the stack.
 | |
| 	SkipTokenState
 | |
| 	// comment -> # comment' | ; comment'
 | |
| 	// comment' -> MarkComplete | value
 | |
| 	CommentState
 | |
| 	// MarkComplete state will complete statements and move that
 | |
| 	// to the completed AST list
 | |
| 	MarkCompleteState
 | |
| 	// TerminalState signifies that the tokens have been fully parsed
 | |
| 	TerminalState
 | |
| )
 | |
| 
 | |
| // parseTable is a state machine to dictate the grammar above.
 | |
| var parseTable = map[ASTKind]map[TokenType]int{
 | |
| 	ASTKindStart: map[TokenType]int{
 | |
| 		TokenLit:     StatementState,
 | |
| 		TokenSep:     OpenScopeState,
 | |
| 		TokenWS:      SkipTokenState,
 | |
| 		TokenNL:      SkipTokenState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    TerminalState,
 | |
| 	},
 | |
| 	ASTKindCommentStatement: map[TokenType]int{
 | |
| 		TokenLit:     StatementState,
 | |
| 		TokenSep:     OpenScopeState,
 | |
| 		TokenWS:      SkipTokenState,
 | |
| 		TokenNL:      SkipTokenState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    MarkCompleteState,
 | |
| 	},
 | |
| 	ASTKindExpr: map[TokenType]int{
 | |
| 		TokenOp:      StatementPrimeState,
 | |
| 		TokenLit:     ValueState,
 | |
| 		TokenSep:     OpenScopeState,
 | |
| 		TokenWS:      ValueState,
 | |
| 		TokenNL:      SkipState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    MarkCompleteState,
 | |
| 	},
 | |
| 	ASTKindEqualExpr: map[TokenType]int{
 | |
| 		TokenLit:     ValueState,
 | |
| 		TokenWS:      SkipTokenState,
 | |
| 		TokenNL:      SkipState,
 | |
| 		TokenNone:    SkipState,
 | |
| 	},
 | |
| 	ASTKindStatement: map[TokenType]int{
 | |
| 		TokenLit:     SectionState,
 | |
| 		TokenSep:     CloseScopeState,
 | |
| 		TokenWS:      SkipTokenState,
 | |
| 		TokenNL:      SkipTokenState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    MarkCompleteState,
 | |
| 	},
 | |
| 	ASTKindExprStatement: map[TokenType]int{
 | |
| 		TokenLit:     ValueState,
 | |
| 		TokenSep:     OpenScopeState,
 | |
| 		TokenOp:      ValueState,
 | |
| 		TokenWS:      ValueState,
 | |
| 		TokenNL:      MarkCompleteState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    TerminalState,
 | |
| 		TokenComma:   SkipState,
 | |
| 	},
 | |
| 	ASTKindSectionStatement: map[TokenType]int{
 | |
| 		TokenLit: SectionState,
 | |
| 		TokenOp:  SectionState,
 | |
| 		TokenSep: CloseScopeState,
 | |
| 		TokenWS:  SectionState,
 | |
| 		TokenNL:  SkipTokenState,
 | |
| 	},
 | |
| 	ASTKindCompletedSectionStatement: map[TokenType]int{
 | |
| 		TokenWS:      SkipTokenState,
 | |
| 		TokenNL:      SkipTokenState,
 | |
| 		TokenLit:     StatementState,
 | |
| 		TokenSep:     OpenScopeState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    MarkCompleteState,
 | |
| 	},
 | |
| 	ASTKindSkipStatement: map[TokenType]int{
 | |
| 		TokenLit:     StatementState,
 | |
| 		TokenSep:     OpenScopeState,
 | |
| 		TokenWS:      SkipTokenState,
 | |
| 		TokenNL:      SkipTokenState,
 | |
| 		TokenComment: CommentState,
 | |
| 		TokenNone:    TerminalState,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| // ParseAST will parse input from an io.Reader using
 | |
| // an LL(1) parser.
 | |
| func ParseAST(r io.Reader) ([]AST, error) {
 | |
| 	lexer := iniLexer{}
 | |
| 	tokens, err := lexer.Tokenize(r)
 | |
| 	if err != nil {
 | |
| 		return []AST{}, err
 | |
| 	}
 | |
| 
 | |
| 	return parse(tokens)
 | |
| }
 | |
| 
 | |
| // ParseASTBytes will parse input from a byte slice using
 | |
| // an LL(1) parser.
 | |
| func ParseASTBytes(b []byte) ([]AST, error) {
 | |
| 	lexer := iniLexer{}
 | |
| 	tokens, err := lexer.tokenize(b)
 | |
| 	if err != nil {
 | |
| 		return []AST{}, err
 | |
| 	}
 | |
| 
 | |
| 	return parse(tokens)
 | |
| }
 | |
| 
 | |
| func parse(tokens []Token) ([]AST, error) {
 | |
| 	start := Start
 | |
| 	stack := newParseStack(3, len(tokens))
 | |
| 
 | |
| 	stack.Push(start)
 | |
| 	s := newSkipper()
 | |
| 
 | |
| loop:
 | |
| 	for stack.Len() > 0 {
 | |
| 		k := stack.Pop()
 | |
| 
 | |
| 		var tok Token
 | |
| 		if len(tokens) == 0 {
 | |
| 			// this occurs when all the tokens have been processed
 | |
| 			// but reduction of what's left on the stack needs to
 | |
| 			// occur.
 | |
| 			tok = emptyToken
 | |
| 		} else {
 | |
| 			tok = tokens[0]
 | |
| 		}
 | |
| 
 | |
| 		step := parseTable[k.Kind][tok.Type()]
 | |
| 		if s.ShouldSkip(tok) {
 | |
| 			// being in a skip state with no tokens will break out of
 | |
| 			// the parse loop since there is nothing left to process.
 | |
| 			if len(tokens) == 0 {
 | |
| 				break loop
 | |
| 			}
 | |
| 			// if should skip is true, we skip the tokens until should skip is set to false.
 | |
| 			step = SkipTokenState
 | |
| 		}
 | |
| 
 | |
| 		switch step {
 | |
| 		case TerminalState:
 | |
| 			// Finished parsing. Push what should be the last
 | |
| 			// statement to the stack. If there is anything left
 | |
| 			// on the stack, an error in parsing has occurred.
 | |
| 			if k.Kind != ASTKindStart {
 | |
| 				stack.MarkComplete(k)
 | |
| 			}
 | |
| 			break loop
 | |
| 		case SkipTokenState:
 | |
| 			// When skipping a token, the previous state was popped off the stack.
 | |
| 			// To maintain the correct state, the previous state will be pushed
 | |
| 			// onto the stack.
 | |
| 			stack.Push(k)
 | |
| 		case StatementState:
 | |
| 			if k.Kind != ASTKindStart {
 | |
| 				stack.MarkComplete(k)
 | |
| 			}
 | |
| 			expr := newExpression(tok)
 | |
| 			stack.Push(expr)
 | |
| 		case StatementPrimeState:
 | |
| 			if tok.Type() != TokenOp {
 | |
| 				stack.MarkComplete(k)
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			if k.Kind != ASTKindExpr {
 | |
| 				return nil, NewParseError(
 | |
| 					fmt.Sprintf("invalid expression: expected Expr type, but found %T type", k),
 | |
| 				)
 | |
| 			}
 | |
| 
 | |
| 			k = trimSpaces(k)
 | |
| 			expr := newEqualExpr(k, tok)
 | |
| 			stack.Push(expr)
 | |
| 		case ValueState:
 | |
| 			// ValueState requires the previous state to either be an equal expression
 | |
| 			// or an expression statement.
 | |
| 			//
 | |
| 			// This grammar occurs when the RHS is a number, word, or quoted string.
 | |
| 			// equal_expr -> lit op equal_expr'
 | |
| 			// equal_expr' -> number | string | quoted_string
 | |
| 			// quoted_string -> " quoted_string'
 | |
| 			// quoted_string' -> string quoted_string_end
 | |
| 			// quoted_string_end -> "
 | |
| 			//
 | |
| 			// otherwise
 | |
| 			// expr_stmt -> equal_expr (expr_stmt')*
 | |
| 			// expr_stmt' -> ws S | op S | MarkComplete
 | |
| 			// S -> equal_expr' expr_stmt'
 | |
| 			switch k.Kind {
 | |
| 			case ASTKindEqualExpr:
 | |
| 				// assigning a value to some key
 | |
| 				k.AppendChild(newExpression(tok))
 | |
| 				stack.Push(newExprStatement(k))
 | |
| 			case ASTKindExpr:
 | |
| 				k.Root.raw = append(k.Root.raw, tok.Raw()...)
 | |
| 				stack.Push(k)
 | |
| 			case ASTKindExprStatement:
 | |
| 				root := k.GetRoot()
 | |
| 				children := root.GetChildren()
 | |
| 				if len(children) == 0 {
 | |
| 					return nil, NewParseError(
 | |
| 						fmt.Sprintf("invalid expression: AST contains no children %s", k.Kind),
 | |
| 					)
 | |
| 				}
 | |
| 
 | |
| 				rhs := children[len(children)-1]
 | |
| 
 | |
| 				if rhs.Root.ValueType != QuotedStringType {
 | |
| 					rhs.Root.ValueType = StringType
 | |
| 					rhs.Root.raw = append(rhs.Root.raw, tok.Raw()...)
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				children[len(children)-1] = rhs
 | |
| 				k.SetChildren(children)
 | |
| 
 | |
| 				stack.Push(k)
 | |
| 			}
 | |
| 		case OpenScopeState:
 | |
| 			if !runeCompare(tok.Raw(), openBrace) {
 | |
| 				return nil, NewParseError("expected '['")
 | |
| 			}
 | |
| 			// If OpenScopeState is not at the start, we must mark the previous ast as complete
 | |
| 			//
 | |
| 			// for example: if previous ast was a skip statement;
 | |
| 			// we should mark it as complete before we create a new statement
 | |
| 			if k.Kind != ASTKindStart {
 | |
| 				stack.MarkComplete(k)
 | |
| 			}
 | |
| 
 | |
| 			stmt := newStatement()
 | |
| 			stack.Push(stmt)
 | |
| 		case CloseScopeState:
 | |
| 			if !runeCompare(tok.Raw(), closeBrace) {
 | |
| 				return nil, NewParseError("expected ']'")
 | |
| 			}
 | |
| 
 | |
| 			k = trimSpaces(k)
 | |
| 			stack.Push(newCompletedSectionStatement(k))
 | |
| 		case SectionState:
 | |
| 			var stmt AST
 | |
| 
 | |
| 			switch k.Kind {
 | |
| 			case ASTKindStatement:
 | |
| 				// If there are multiple literals inside of a scope declaration,
 | |
| 				// then the current token's raw value will be appended to the Name.
 | |
| 				//
 | |
| 				// This handles cases like [ profile default ]
 | |
| 				//
 | |
| 				// k will represent a SectionStatement with the children representing
 | |
| 				// the label of the section
 | |
| 				stmt = newSectionStatement(tok)
 | |
| 			case ASTKindSectionStatement:
 | |
| 				k.Root.raw = append(k.Root.raw, tok.Raw()...)
 | |
| 				stmt = k
 | |
| 			default:
 | |
| 				return nil, NewParseError(
 | |
| 					fmt.Sprintf("invalid statement: expected statement: %v", k.Kind),
 | |
| 				)
 | |
| 			}
 | |
| 
 | |
| 			stack.Push(stmt)
 | |
| 		case MarkCompleteState:
 | |
| 			if k.Kind != ASTKindStart {
 | |
| 				stack.MarkComplete(k)
 | |
| 			}
 | |
| 
 | |
| 			if stack.Len() == 0 {
 | |
| 				stack.Push(start)
 | |
| 			}
 | |
| 		case SkipState:
 | |
| 			stack.Push(newSkipStatement(k))
 | |
| 			s.Skip()
 | |
| 		case CommentState:
 | |
| 			if k.Kind == ASTKindStart {
 | |
| 				stack.Push(k)
 | |
| 			} else {
 | |
| 				stack.MarkComplete(k)
 | |
| 			}
 | |
| 
 | |
| 			stmt := newCommentStatement(tok)
 | |
| 			stack.Push(stmt)
 | |
| 		default:
 | |
| 			return nil, NewParseError(
 | |
| 				fmt.Sprintf("invalid state with ASTKind %v and TokenType %v",
 | |
| 					k, tok.Type()))
 | |
| 		}
 | |
| 
 | |
| 		if len(tokens) > 0 {
 | |
| 			tokens = tokens[1:]
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// this occurs when a statement has not been completed
 | |
| 	if stack.top > 1 {
 | |
| 		return nil, NewParseError(fmt.Sprintf("incomplete ini expression"))
 | |
| 	}
 | |
| 
 | |
| 	// returns a sublist which excludes the start symbol
 | |
| 	return stack.List(), nil
 | |
| }
 | |
| 
 | |
| // trimSpaces will trim spaces on the left and right hand side of
 | |
| // the literal.
 | |
| func trimSpaces(k AST) AST {
 | |
| 	// trim left hand side of spaces
 | |
| 	for i := 0; i < len(k.Root.raw); i++ {
 | |
| 		if !isWhitespace(k.Root.raw[i]) {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		k.Root.raw = k.Root.raw[1:]
 | |
| 		i--
 | |
| 	}
 | |
| 
 | |
| 	// trim right hand side of spaces
 | |
| 	for i := len(k.Root.raw) - 1; i >= 0; i-- {
 | |
| 		if !isWhitespace(k.Root.raw[i]) {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		k.Root.raw = k.Root.raw[:len(k.Root.raw)-1]
 | |
| 	}
 | |
| 
 | |
| 	return k
 | |
| }
 |