parser.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. package pongo2
  2. import (
  3. "bytes"
  4. "fmt"
  5. "strings"
  6. )
  7. type INode interface {
  8. Execute(*ExecutionContext, *bytes.Buffer) *Error
  9. }
  10. type IEvaluator interface {
  11. INode
  12. GetPositionToken() *Token
  13. Evaluate(*ExecutionContext) (*Value, *Error)
  14. FilterApplied(name string) bool
  15. }
  16. // The parser provides you a comprehensive and easy tool to
  17. // work with the template document and arguments provided by
  18. // the user for your custom tag.
  19. //
  20. // The parser works on a token list which will be provided by pongo2.
  21. // A token is a unit you can work with. Tokens are either of type identifier,
  22. // string, number, keyword, HTML or symbol.
  23. //
  24. // (See Token's documentation for more about tokens)
  25. type Parser struct {
  26. name string
  27. idx int
  28. tokens []*Token
  29. last_token *Token
  30. // if the parser parses a template document, here will be
  31. // a reference to it (needed to access the template through Tags)
  32. template *Template
  33. }
  34. // Creates a new parser to parse tokens.
  35. // Used inside pongo2 to parse documents and to provide an easy-to-use
  36. // parser for tag authors
  37. func newParser(name string, tokens []*Token, template *Template) *Parser {
  38. p := &Parser{
  39. name: name,
  40. tokens: tokens,
  41. template: template,
  42. }
  43. if len(tokens) > 0 {
  44. p.last_token = tokens[len(tokens)-1]
  45. }
  46. return p
  47. }
  48. // Consume one token. It will be gone forever.
  49. func (p *Parser) Consume() {
  50. p.ConsumeN(1)
  51. }
  52. // Consume N tokens. They will be gone forever.
  53. func (p *Parser) ConsumeN(count int) {
  54. p.idx += count
  55. }
  56. // Returns the current token.
  57. func (p *Parser) Current() *Token {
  58. return p.Get(p.idx)
  59. }
  60. // Returns the CURRENT token if the given type matches.
  61. // Consumes this token on success.
  62. func (p *Parser) MatchType(typ TokenType) *Token {
  63. if t := p.PeekType(typ); t != nil {
  64. p.Consume()
  65. return t
  66. }
  67. return nil
  68. }
  69. // Returns the CURRENT token if the given type AND value matches.
  70. // Consumes this token on success.
  71. func (p *Parser) Match(typ TokenType, val string) *Token {
  72. if t := p.Peek(typ, val); t != nil {
  73. p.Consume()
  74. return t
  75. }
  76. return nil
  77. }
  78. // Returns the CURRENT token if the given type AND *one* of
  79. // the given values matches.
  80. // Consumes this token on success.
  81. func (p *Parser) MatchOne(typ TokenType, vals ...string) *Token {
  82. for _, val := range vals {
  83. if t := p.Peek(typ, val); t != nil {
  84. p.Consume()
  85. return t
  86. }
  87. }
  88. return nil
  89. }
  90. // Returns the CURRENT token if the given type matches.
  91. // It DOES NOT consume the token.
  92. func (p *Parser) PeekType(typ TokenType) *Token {
  93. return p.PeekTypeN(0, typ)
  94. }
  95. // Returns the CURRENT token if the given type AND value matches.
  96. // It DOES NOT consume the token.
  97. func (p *Parser) Peek(typ TokenType, val string) *Token {
  98. return p.PeekN(0, typ, val)
  99. }
  100. // Returns the CURRENT token if the given type AND *one* of
  101. // the given values matches.
  102. // It DOES NOT consume the token.
  103. func (p *Parser) PeekOne(typ TokenType, vals ...string) *Token {
  104. for _, v := range vals {
  105. t := p.PeekN(0, typ, v)
  106. if t != nil {
  107. return t
  108. }
  109. }
  110. return nil
  111. }
  112. // Returns the tokens[current position + shift] token if the
  113. // given type AND value matches for that token.
  114. // DOES NOT consume the token.
  115. func (p *Parser) PeekN(shift int, typ TokenType, val string) *Token {
  116. t := p.Get(p.idx + shift)
  117. if t != nil {
  118. if t.Typ == typ && t.Val == val {
  119. return t
  120. }
  121. }
  122. return nil
  123. }
  124. // Returns the tokens[current position + shift] token if the given type matches.
  125. // DOES NOT consume the token for that token.
  126. func (p *Parser) PeekTypeN(shift int, typ TokenType) *Token {
  127. t := p.Get(p.idx + shift)
  128. if t != nil {
  129. if t.Typ == typ {
  130. return t
  131. }
  132. }
  133. return nil
  134. }
  135. // Returns the UNCONSUMED token count.
  136. func (p *Parser) Remaining() int {
  137. return len(p.tokens) - p.idx
  138. }
  139. // Returns the total token count.
  140. func (p *Parser) Count() int {
  141. return len(p.tokens)
  142. }
  143. // Returns tokens[i] or NIL (if i >= len(tokens))
  144. func (p *Parser) Get(i int) *Token {
  145. if i < len(p.tokens) {
  146. return p.tokens[i]
  147. }
  148. return nil
  149. }
  150. // Returns tokens[current-position + shift] or NIL
  151. // (if (current-position + i) >= len(tokens))
  152. func (p *Parser) GetR(shift int) *Token {
  153. i := p.idx + shift
  154. return p.Get(i)
  155. }
  156. // Produces a nice error message and returns an error-object.
  157. // The 'token'-argument is optional. If provided, it will take
  158. // the token's position information. If not provided, it will
  159. // automatically use the CURRENT token's position information.
  160. func (p *Parser) Error(msg string, token *Token) *Error {
  161. if token == nil {
  162. // Set current token
  163. token = p.Current()
  164. if token == nil {
  165. // Set to last token
  166. if len(p.tokens) > 0 {
  167. token = p.tokens[len(p.tokens)-1]
  168. }
  169. }
  170. }
  171. var line, col int
  172. if token != nil {
  173. line = token.Line
  174. col = token.Col
  175. }
  176. return &Error{
  177. Template: p.template,
  178. Filename: p.name,
  179. Sender: "parser",
  180. Line: line,
  181. Column: col,
  182. Token: token,
  183. ErrorMsg: msg,
  184. }
  185. }
  186. // Wraps all nodes between starting tag and "{% endtag %}" and provides
  187. // one simple interface to execute the wrapped nodes.
  188. // It returns a parser to process provided arguments to the tag.
  189. func (p *Parser) WrapUntilTag(names ...string) (*NodeWrapper, *Parser, *Error) {
  190. wrapper := &NodeWrapper{}
  191. tagArgs := make([]*Token, 0)
  192. for p.Remaining() > 0 {
  193. // New tag, check whether we have to stop wrapping here
  194. if p.Peek(TokenSymbol, "{%") != nil {
  195. tag_ident := p.PeekTypeN(1, TokenIdentifier)
  196. if tag_ident != nil {
  197. // We've found a (!) end-tag
  198. found := false
  199. for _, n := range names {
  200. if tag_ident.Val == n {
  201. found = true
  202. break
  203. }
  204. }
  205. // We only process the tag if we've found an end tag
  206. if found {
  207. // Okay, endtag found.
  208. p.ConsumeN(2) // '{%' tagname
  209. for {
  210. if p.Match(TokenSymbol, "%}") != nil {
  211. // Okay, end the wrapping here
  212. wrapper.Endtag = tag_ident.Val
  213. return wrapper, newParser(p.template.name, tagArgs, p.template), nil
  214. } else {
  215. t := p.Current()
  216. p.Consume()
  217. if t == nil {
  218. return nil, nil, p.Error("Unexpected EOF.", p.last_token)
  219. }
  220. tagArgs = append(tagArgs, t)
  221. }
  222. }
  223. }
  224. }
  225. }
  226. // Otherwise process next element to be wrapped
  227. node, err := p.parseDocElement()
  228. if err != nil {
  229. return nil, nil, err
  230. }
  231. wrapper.nodes = append(wrapper.nodes, node)
  232. }
  233. return nil, nil, p.Error(fmt.Sprintf("Unexpected EOF, expected tag %s.", strings.Join(names, " or ")),
  234. p.last_token)
  235. }