ini_parser.go 8.9 KB

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