ini_parser.go 8.9 KB

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