dispenser.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. // Copyright 2015 Light Code Labs, LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package caddyfile
  15. import (
  16. "errors"
  17. "fmt"
  18. "io"
  19. "strings"
  20. )
  21. // Dispenser is a type that dispenses tokens, similarly to a lexer,
  22. // except that it can do so with some notion of structure and has
  23. // some really convenient methods.
  24. type Dispenser struct {
  25. filename string
  26. tokens []Token
  27. cursor int
  28. nesting int
  29. }
  30. // NewDispenser returns a Dispenser, ready to use for parsing the given input.
  31. func NewDispenser(filename string, input io.Reader) Dispenser {
  32. tokens, _ := allTokens(input) // ignoring error because nothing to do with it
  33. return Dispenser{
  34. filename: filename,
  35. tokens: tokens,
  36. cursor: -1,
  37. }
  38. }
  39. // NewDispenserTokens returns a Dispenser filled with the given tokens.
  40. func NewDispenserTokens(filename string, tokens []Token) Dispenser {
  41. return Dispenser{
  42. filename: filename,
  43. tokens: tokens,
  44. cursor: -1,
  45. }
  46. }
  47. // Next loads the next token. Returns true if a token
  48. // was loaded; false otherwise. If false, all tokens
  49. // have been consumed.
  50. func (d *Dispenser) Next() bool {
  51. if d.cursor < len(d.tokens)-1 {
  52. d.cursor++
  53. return true
  54. }
  55. return false
  56. }
  57. // NextArg loads the next token if it is on the same
  58. // line. Returns true if a token was loaded; false
  59. // otherwise. If false, all tokens on the line have
  60. // been consumed. It handles imported tokens correctly.
  61. func (d *Dispenser) NextArg() bool {
  62. if d.cursor < 0 {
  63. d.cursor++
  64. return true
  65. }
  66. if d.cursor >= len(d.tokens) {
  67. return false
  68. }
  69. if d.cursor < len(d.tokens)-1 &&
  70. d.tokens[d.cursor].File == d.tokens[d.cursor+1].File &&
  71. d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) == d.tokens[d.cursor+1].Line {
  72. d.cursor++
  73. return true
  74. }
  75. return false
  76. }
  77. // NextLine loads the next token only if it is not on the same
  78. // line as the current token, and returns true if a token was
  79. // loaded; false otherwise. If false, there is not another token
  80. // or it is on the same line. It handles imported tokens correctly.
  81. func (d *Dispenser) NextLine() bool {
  82. if d.cursor < 0 {
  83. d.cursor++
  84. return true
  85. }
  86. if d.cursor >= len(d.tokens) {
  87. return false
  88. }
  89. if d.cursor < len(d.tokens)-1 &&
  90. (d.tokens[d.cursor].File != d.tokens[d.cursor+1].File ||
  91. d.tokens[d.cursor].Line+d.numLineBreaks(d.cursor) < d.tokens[d.cursor+1].Line) {
  92. d.cursor++
  93. return true
  94. }
  95. return false
  96. }
  97. // NextBlock can be used as the condition of a for loop
  98. // to load the next token as long as it opens a block or
  99. // is already in a block. It returns true if a token was
  100. // loaded, or false when the block's closing curly brace
  101. // was loaded and thus the block ended. Nested blocks are
  102. // not supported.
  103. func (d *Dispenser) NextBlock() bool {
  104. if d.nesting > 0 {
  105. d.Next()
  106. if d.Val() == "}" {
  107. d.nesting--
  108. return false
  109. }
  110. return true
  111. }
  112. if !d.NextArg() { // block must open on same line
  113. return false
  114. }
  115. if d.Val() != "{" {
  116. d.cursor-- // roll back if not opening brace
  117. return false
  118. }
  119. d.Next()
  120. if d.Val() == "}" {
  121. // Open and then closed right away
  122. return false
  123. }
  124. d.nesting++
  125. return true
  126. }
  127. // Val gets the text of the current token. If there is no token
  128. // loaded, it returns empty string.
  129. func (d *Dispenser) Val() string {
  130. if d.cursor < 0 || d.cursor >= len(d.tokens) {
  131. return ""
  132. }
  133. return d.tokens[d.cursor].Text
  134. }
  135. // Line gets the line number of the current token. If there is no token
  136. // loaded, it returns 0.
  137. func (d *Dispenser) Line() int {
  138. if d.cursor < 0 || d.cursor >= len(d.tokens) {
  139. return 0
  140. }
  141. return d.tokens[d.cursor].Line
  142. }
  143. // File gets the filename of the current token. If there is no token loaded,
  144. // it returns the filename originally given when parsing started.
  145. func (d *Dispenser) File() string {
  146. if d.cursor < 0 || d.cursor >= len(d.tokens) {
  147. return d.filename
  148. }
  149. if tokenFilename := d.tokens[d.cursor].File; tokenFilename != "" {
  150. return tokenFilename
  151. }
  152. return d.filename
  153. }
  154. // Args is a convenience function that loads the next arguments
  155. // (tokens on the same line) into an arbitrary number of strings
  156. // pointed to in targets. If there are fewer tokens available
  157. // than string pointers, the remaining strings will not be changed
  158. // and false will be returned. If there were enough tokens available
  159. // to fill the arguments, then true will be returned.
  160. func (d *Dispenser) Args(targets ...*string) bool {
  161. enough := true
  162. for i := 0; i < len(targets); i++ {
  163. if !d.NextArg() {
  164. enough = false
  165. break
  166. }
  167. *targets[i] = d.Val()
  168. }
  169. return enough
  170. }
  171. // RemainingArgs loads any more arguments (tokens on the same line)
  172. // into a slice and returns them. Open curly brace tokens also indicate
  173. // the end of arguments, and the curly brace is not included in
  174. // the return value nor is it loaded.
  175. func (d *Dispenser) RemainingArgs() []string {
  176. var args []string
  177. for d.NextArg() {
  178. if d.Val() == "{" {
  179. d.cursor--
  180. break
  181. }
  182. args = append(args, d.Val())
  183. }
  184. return args
  185. }
  186. // ArgErr returns an argument error, meaning that another
  187. // argument was expected but not found. In other words,
  188. // a line break or open curly brace was encountered instead of
  189. // an argument.
  190. func (d *Dispenser) ArgErr() error {
  191. if d.Val() == "{" {
  192. return d.Err("Unexpected token '{', expecting argument")
  193. }
  194. return d.Errf("Wrong argument count or unexpected line ending after '%s'", d.Val())
  195. }
  196. // SyntaxErr creates a generic syntax error which explains what was
  197. // found and what was expected.
  198. func (d *Dispenser) SyntaxErr(expected string) error {
  199. msg := fmt.Sprintf("%s:%d - Syntax error: Unexpected token '%s', expecting '%s'", d.File(), d.Line(), d.Val(), expected)
  200. return errors.New(msg)
  201. }
  202. // EOFErr returns an error indicating that the dispenser reached
  203. // the end of the input when searching for the next token.
  204. func (d *Dispenser) EOFErr() error {
  205. return d.Errf("Unexpected EOF")
  206. }
  207. // Err generates a custom parse-time error with a message of msg.
  208. func (d *Dispenser) Err(msg string) error {
  209. msg = fmt.Sprintf("%s:%d - Error during parsing: %s", d.File(), d.Line(), msg)
  210. return errors.New(msg)
  211. }
  212. // Errf is like Err, but for formatted error messages
  213. func (d *Dispenser) Errf(format string, args ...interface{}) error {
  214. return d.Err(fmt.Sprintf(format, args...))
  215. }
  216. // numLineBreaks counts how many line breaks are in the token
  217. // value given by the token index tknIdx. It returns 0 if the
  218. // token does not exist or there are no line breaks.
  219. func (d *Dispenser) numLineBreaks(tknIdx int) int {
  220. if tknIdx < 0 || tknIdx >= len(d.tokens) {
  221. return 0
  222. }
  223. return strings.Count(d.tokens[tknIdx].Text, "\n")
  224. }
  225. // isNewLine determines whether the current token is on a different
  226. // line (higher line number) than the previous token. It handles imported
  227. // tokens correctly. If there isn't a previous token, it returns true.
  228. func (d *Dispenser) isNewLine() bool {
  229. if d.cursor < 1 {
  230. return true
  231. }
  232. if d.cursor > len(d.tokens)-1 {
  233. return false
  234. }
  235. return d.tokens[d.cursor-1].File != d.tokens[d.cursor].File ||
  236. d.tokens[d.cursor-1].Line+d.numLineBreaks(d.cursor-1) < d.tokens[d.cursor].Line
  237. }