github.com/aavshr/aws-sdk-go@v1.41.3/internal/ini/ini_parser.go (about)

     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  		TokenNone: SkipState,
    75  	},
    76  	ASTKindStatement: {
    77  		TokenLit:     SectionState,
    78  		TokenSep:     CloseScopeState,
    79  		TokenWS:      SkipTokenState,
    80  		TokenNL:      SkipTokenState,
    81  		TokenComment: CommentState,
    82  		TokenNone:    MarkCompleteState,
    83  	},
    84  	ASTKindExprStatement: {
    85  		TokenLit:     ValueState,
    86  		TokenSep:     ValueState,
    87  		TokenOp:      ValueState,
    88  		TokenWS:      ValueState,
    89  		TokenNL:      MarkCompleteState,
    90  		TokenComment: CommentState,
    91  		TokenNone:    TerminalState,
    92  		TokenComma:   SkipState,
    93  	},
    94  	ASTKindSectionStatement: {
    95  		TokenLit: SectionState,
    96  		TokenOp:  SectionState,
    97  		TokenSep: CloseScopeState,
    98  		TokenWS:  SectionState,
    99  		TokenNL:  SkipTokenState,
   100  	},
   101  	ASTKindCompletedSectionStatement: {
   102  		TokenWS:      SkipTokenState,
   103  		TokenNL:      SkipTokenState,
   104  		TokenLit:     StatementState,
   105  		TokenSep:     OpenScopeState,
   106  		TokenComment: CommentState,
   107  		TokenNone:    MarkCompleteState,
   108  	},
   109  	ASTKindSkipStatement: {
   110  		TokenLit:     StatementState,
   111  		TokenSep:     OpenScopeState,
   112  		TokenWS:      SkipTokenState,
   113  		TokenNL:      SkipTokenState,
   114  		TokenComment: CommentState,
   115  		TokenNone:    TerminalState,
   116  	},
   117  }
   118  
   119  // ParseAST will parse input from an io.Reader using
   120  // an LL(1) parser.
   121  func ParseAST(r io.Reader) ([]AST, error) {
   122  	lexer := iniLexer{}
   123  	tokens, err := lexer.Tokenize(r)
   124  	if err != nil {
   125  		return []AST{}, err
   126  	}
   127  
   128  	return parse(tokens)
   129  }
   130  
   131  // ParseASTBytes will parse input from a byte slice using
   132  // an LL(1) parser.
   133  func ParseASTBytes(b []byte) ([]AST, error) {
   134  	lexer := iniLexer{}
   135  	tokens, err := lexer.tokenize(b)
   136  	if err != nil {
   137  		return []AST{}, err
   138  	}
   139  
   140  	return parse(tokens)
   141  }
   142  
   143  func parse(tokens []Token) ([]AST, error) {
   144  	start := Start
   145  	stack := newParseStack(3, len(tokens))
   146  
   147  	stack.Push(start)
   148  	s := newSkipper()
   149  
   150  loop:
   151  	for stack.Len() > 0 {
   152  		k := stack.Pop()
   153  
   154  		var tok Token
   155  		if len(tokens) == 0 {
   156  			// this occurs when all the tokens have been processed
   157  			// but reduction of what's left on the stack needs to
   158  			// occur.
   159  			tok = emptyToken
   160  		} else {
   161  			tok = tokens[0]
   162  		}
   163  
   164  		step := parseTable[k.Kind][tok.Type()]
   165  		if s.ShouldSkip(tok) {
   166  			// being in a skip state with no tokens will break out of
   167  			// the parse loop since there is nothing left to process.
   168  			if len(tokens) == 0 {
   169  				break loop
   170  			}
   171  			// if should skip is true, we skip the tokens until should skip is set to false.
   172  			step = SkipTokenState
   173  		}
   174  
   175  		switch step {
   176  		case TerminalState:
   177  			// Finished parsing. Push what should be the last
   178  			// statement to the stack. If there is anything left
   179  			// on the stack, an error in parsing has occurred.
   180  			if k.Kind != ASTKindStart {
   181  				stack.MarkComplete(k)
   182  			}
   183  			break loop
   184  		case SkipTokenState:
   185  			// When skipping a token, the previous state was popped off the stack.
   186  			// To maintain the correct state, the previous state will be pushed
   187  			// onto the stack.
   188  			stack.Push(k)
   189  		case StatementState:
   190  			if k.Kind != ASTKindStart {
   191  				stack.MarkComplete(k)
   192  			}
   193  			expr := newExpression(tok)
   194  			stack.Push(expr)
   195  		case StatementPrimeState:
   196  			if tok.Type() != TokenOp {
   197  				stack.MarkComplete(k)
   198  				continue
   199  			}
   200  
   201  			if k.Kind != ASTKindExpr {
   202  				return nil, NewParseError(
   203  					fmt.Sprintf("invalid expression: expected Expr type, but found %T type", k),
   204  				)
   205  			}
   206  
   207  			k = trimSpaces(k)
   208  			expr := newEqualExpr(k, tok)
   209  			stack.Push(expr)
   210  		case ValueState:
   211  			// ValueState requires the previous state to either be an equal expression
   212  			// or an expression statement.
   213  			switch k.Kind {
   214  			case ASTKindEqualExpr:
   215  				// assigning a value to some key
   216  				k.AppendChild(newExpression(tok))
   217  				stack.Push(newExprStatement(k))
   218  			case ASTKindExpr:
   219  				k.Root.raw = append(k.Root.raw, tok.Raw()...)
   220  				stack.Push(k)
   221  			case ASTKindExprStatement:
   222  				root := k.GetRoot()
   223  				children := root.GetChildren()
   224  				if len(children) == 0 {
   225  					return nil, NewParseError(
   226  						fmt.Sprintf("invalid expression: AST contains no children %s", k.Kind),
   227  					)
   228  				}
   229  
   230  				rhs := children[len(children)-1]
   231  
   232  				if rhs.Root.ValueType != QuotedStringType {
   233  					rhs.Root.ValueType = StringType
   234  					rhs.Root.raw = append(rhs.Root.raw, tok.Raw()...)
   235  
   236  				}
   237  
   238  				children[len(children)-1] = rhs
   239  				root.SetChildren(children)
   240  
   241  				stack.Push(k)
   242  			}
   243  		case OpenScopeState:
   244  			if !runeCompare(tok.Raw(), openBrace) {
   245  				return nil, NewParseError("expected '['")
   246  			}
   247  			// If OpenScopeState is not at the start, we must mark the previous ast as complete
   248  			//
   249  			// for example: if previous ast was a skip statement;
   250  			// we should mark it as complete before we create a new statement
   251  			if k.Kind != ASTKindStart {
   252  				stack.MarkComplete(k)
   253  			}
   254  
   255  			stmt := newStatement()
   256  			stack.Push(stmt)
   257  		case CloseScopeState:
   258  			if !runeCompare(tok.Raw(), closeBrace) {
   259  				return nil, NewParseError("expected ']'")
   260  			}
   261  
   262  			k = trimSpaces(k)
   263  			stack.Push(newCompletedSectionStatement(k))
   264  		case SectionState:
   265  			var stmt AST
   266  
   267  			switch k.Kind {
   268  			case ASTKindStatement:
   269  				// If there are multiple literals inside of a scope declaration,
   270  				// then the current token's raw value will be appended to the Name.
   271  				//
   272  				// This handles cases like [ profile default ]
   273  				//
   274  				// k will represent a SectionStatement with the children representing
   275  				// the label of the section
   276  				stmt = newSectionStatement(tok)
   277  			case ASTKindSectionStatement:
   278  				k.Root.raw = append(k.Root.raw, tok.Raw()...)
   279  				stmt = k
   280  			default:
   281  				return nil, NewParseError(
   282  					fmt.Sprintf("invalid statement: expected statement: %v", k.Kind),
   283  				)
   284  			}
   285  
   286  			stack.Push(stmt)
   287  		case MarkCompleteState:
   288  			if k.Kind != ASTKindStart {
   289  				stack.MarkComplete(k)
   290  			}
   291  
   292  			if stack.Len() == 0 {
   293  				stack.Push(start)
   294  			}
   295  		case SkipState:
   296  			stack.Push(newSkipStatement(k))
   297  			s.Skip()
   298  		case CommentState:
   299  			if k.Kind == ASTKindStart {
   300  				stack.Push(k)
   301  			} else {
   302  				stack.MarkComplete(k)
   303  			}
   304  
   305  			stmt := newCommentStatement(tok)
   306  			stack.Push(stmt)
   307  		default:
   308  			return nil, NewParseError(
   309  				fmt.Sprintf("invalid state with ASTKind %v and TokenType %v",
   310  					k, tok.Type()))
   311  		}
   312  
   313  		if len(tokens) > 0 {
   314  			tokens = tokens[1:]
   315  		}
   316  	}
   317  
   318  	// this occurs when a statement has not been completed
   319  	if stack.top > 1 {
   320  		return nil, NewParseError(fmt.Sprintf("incomplete ini expression"))
   321  	}
   322  
   323  	// returns a sublist which excludes the start symbol
   324  	return stack.List(), nil
   325  }
   326  
   327  // trimSpaces will trim spaces on the left and right hand side of
   328  // the literal.
   329  func trimSpaces(k AST) AST {
   330  	// trim left hand side of spaces
   331  	for i := 0; i < len(k.Root.raw); i++ {
   332  		if !isWhitespace(k.Root.raw[i]) {
   333  			break
   334  		}
   335  
   336  		k.Root.raw = k.Root.raw[1:]
   337  		i--
   338  	}
   339  
   340  	// trim right hand side of spaces
   341  	for i := len(k.Root.raw) - 1; i >= 0; i-- {
   342  		if !isWhitespace(k.Root.raw[i]) {
   343  			break
   344  		}
   345  
   346  		k.Root.raw = k.Root.raw[:len(k.Root.raw)-1]
   347  	}
   348  
   349  	return k
   350  }