hunk.code_window: package web
   import (
   	b64 "encoding/base64"
   	"net/http"
   	"strconv"
   	"strings"
  
hunk.labels: keep keep keep add keep keep keep
hunk.after_edit: 	"html"
  
hunk.file_path: web/saml.go
hunk.type: add
file: 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
  }
  