liquor 0.1.1 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -9
  5. data/Gemfile +7 -0
  6. data/Guardfile +11 -0
  7. data/MIT-LICENSE +6 -2
  8. data/README.md +4 -122
  9. data/Rakefile +20 -23
  10. data/doc/language-spec.html +768 -0
  11. data/doc/language-spec.md +698 -0
  12. data/lib/liquor.rb +39 -68
  13. data/lib/liquor/ast_tools.rb +28 -0
  14. data/lib/liquor/compiler.rb +110 -0
  15. data/lib/liquor/context.rb +76 -254
  16. data/lib/liquor/diagnostics.rb +151 -0
  17. data/lib/liquor/drop/drop.rb +168 -0
  18. data/lib/liquor/drop/drop_delegation.rb +24 -0
  19. data/lib/liquor/drop/drop_scope.rb +118 -0
  20. data/lib/liquor/drop/dropable.rb +17 -0
  21. data/lib/liquor/emitter.rb +313 -0
  22. data/lib/liquor/extensions/kaminari.rb +14 -0
  23. data/lib/liquor/extensions/pagination.rb +235 -0
  24. data/lib/liquor/extensions/rails.rb +97 -0
  25. data/lib/liquor/extensions/thinking_sphinx.rb +14 -0
  26. data/lib/liquor/extensions/tire.rb +30 -0
  27. data/lib/liquor/external.rb +79 -0
  28. data/lib/liquor/function.rb +94 -0
  29. data/lib/liquor/grammar/lexer.rb +1223 -0
  30. data/lib/liquor/grammar/lexer.rl +297 -0
  31. data/lib/liquor/grammar/parser.racc +288 -0
  32. data/lib/liquor/grammar/parser.rb +885 -0
  33. data/lib/liquor/library.rb +41 -0
  34. data/lib/liquor/manager.rb +146 -0
  35. data/lib/liquor/runtime.rb +167 -0
  36. data/lib/liquor/stdlib/builtin_functions.rb +315 -0
  37. data/lib/liquor/stdlib/builtin_tags.rb +228 -0
  38. data/lib/liquor/stdlib/html_truncater.rb +162 -0
  39. data/lib/liquor/stdlib/partial_tags.rb +76 -0
  40. data/lib/liquor/tag.rb +83 -14
  41. data/lib/liquor/version.rb +1 -1
  42. data/liquor.gemspec +29 -6
  43. data/spec/builtins_spec.rb +264 -0
  44. data/spec/compiler_spec.rb +136 -0
  45. data/spec/context_spec.rb +49 -0
  46. data/spec/drop_delegation_spec.rb +21 -0
  47. data/spec/drop_spec.rb +222 -0
  48. data/spec/errors_spec.rb +40 -0
  49. data/spec/external_spec.rb +207 -0
  50. data/spec/function_spec.rb +80 -0
  51. data/spec/lexer_spec.rb +173 -0
  52. data/spec/library_spec.rb +18 -0
  53. data/spec/manager_spec.rb +84 -0
  54. data/spec/parser_spec.rb +381 -0
  55. data/spec/partials_spec.rb +74 -0
  56. data/spec/runtime_spec.rb +97 -0
  57. data/spec/spec_helper.rb +94 -0
  58. data/spec/tag_spec.rb +7 -0
  59. metadata +216 -173
  60. data/AUTHORS +0 -2
  61. data/CHANGELOG +0 -48
  62. data/Gemfile.lock +0 -91
  63. data/History.txt +0 -44
  64. data/LICENSE +0 -23
  65. data/example/server/example_servlet.rb +0 -37
  66. data/example/server/liquid_servlet.rb +0 -28
  67. data/example/server/liquor_servlet.rb +0 -28
  68. data/example/server/server.rb +0 -12
  69. data/example/server/templates/index.liquid +0 -6
  70. data/example/server/templates/index.liquor +0 -6
  71. data/example/server/templates/products.liquid +0 -45
  72. data/example/server/templates/products.liquor +0 -45
  73. data/init.rb +0 -8
  74. data/lib/extras/liquid_view.rb +0 -51
  75. data/lib/extras/liquor_view.rb +0 -51
  76. data/lib/liquor/block.rb +0 -101
  77. data/lib/liquor/condition.rb +0 -120
  78. data/lib/liquor/document.rb +0 -17
  79. data/lib/liquor/drop.rb +0 -256
  80. data/lib/liquor/errors.rb +0 -11
  81. data/lib/liquor/extensions.rb +0 -72
  82. data/lib/liquor/file_system.rb +0 -62
  83. data/lib/liquor/htmltags.rb +0 -74
  84. data/lib/liquor/module_ex.rb +0 -60
  85. data/lib/liquor/standardfilters.rb +0 -315
  86. data/lib/liquor/strainer.rb +0 -58
  87. data/lib/liquor/tags/assign.rb +0 -33
  88. data/lib/liquor/tags/capture.rb +0 -35
  89. data/lib/liquor/tags/case.rb +0 -83
  90. data/lib/liquor/tags/comment.rb +0 -9
  91. data/lib/liquor/tags/content_for.rb +0 -54
  92. data/lib/liquor/tags/cycle.rb +0 -59
  93. data/lib/liquor/tags/for.rb +0 -136
  94. data/lib/liquor/tags/if.rb +0 -80
  95. data/lib/liquor/tags/ifchanged.rb +0 -20
  96. data/lib/liquor/tags/include.rb +0 -56
  97. data/lib/liquor/tags/unless.rb +0 -33
  98. data/lib/liquor/tags/yield.rb +0 -49
  99. data/lib/liquor/template.rb +0 -181
  100. data/lib/liquor/variable.rb +0 -52
  101. data/performance/shopify.rb +0 -92
  102. data/performance/shopify/comment_form.rb +0 -33
  103. data/performance/shopify/database.rb +0 -45
  104. data/performance/shopify/json_filter.rb +0 -7
  105. data/performance/shopify/liquid.rb +0 -18
  106. data/performance/shopify/liquor.rb +0 -18
  107. data/performance/shopify/money_filter.rb +0 -18
  108. data/performance/shopify/paginate.rb +0 -93
  109. data/performance/shopify/shop_filter.rb +0 -98
  110. data/performance/shopify/tag_filter.rb +0 -25
  111. data/performance/shopify/vision.database.yml +0 -945
  112. data/performance/shopify/weight_filter.rb +0 -11
  113. data/performance/tests/dropify/article.liquid +0 -74
  114. data/performance/tests/dropify/blog.liquid +0 -33
  115. data/performance/tests/dropify/cart.liquid +0 -66
  116. data/performance/tests/dropify/collection.liquid +0 -22
  117. data/performance/tests/dropify/index.liquid +0 -47
  118. data/performance/tests/dropify/page.liquid +0 -8
  119. data/performance/tests/dropify/product.liquid +0 -68
  120. data/performance/tests/dropify/theme.liquid +0 -105
  121. data/performance/tests/ripen/article.liquid +0 -74
  122. data/performance/tests/ripen/blog.liquid +0 -13
  123. data/performance/tests/ripen/cart.liquid +0 -54
  124. data/performance/tests/ripen/collection.liquid +0 -29
  125. data/performance/tests/ripen/index.liquid +0 -32
  126. data/performance/tests/ripen/page.liquid +0 -4
  127. data/performance/tests/ripen/product.liquid +0 -75
  128. data/performance/tests/ripen/theme.liquid +0 -85
  129. data/performance/tests/tribble/404.liquid +0 -56
  130. data/performance/tests/tribble/article.liquid +0 -98
  131. data/performance/tests/tribble/blog.liquid +0 -41
  132. data/performance/tests/tribble/cart.liquid +0 -134
  133. data/performance/tests/tribble/collection.liquid +0 -70
  134. data/performance/tests/tribble/index.liquid +0 -94
  135. data/performance/tests/tribble/page.liquid +0 -56
  136. data/performance/tests/tribble/product.liquid +0 -116
  137. data/performance/tests/tribble/search.liquid +0 -51
  138. data/performance/tests/tribble/theme.liquid +0 -90
  139. data/performance/tests/vogue/article.liquid +0 -66
  140. data/performance/tests/vogue/blog.liquid +0 -32
  141. data/performance/tests/vogue/cart.liquid +0 -58
  142. data/performance/tests/vogue/collection.liquid +0 -19
  143. data/performance/tests/vogue/index.liquid +0 -22
  144. data/performance/tests/vogue/page.liquid +0 -3
  145. data/performance/tests/vogue/product.liquid +0 -62
  146. data/performance/tests/vogue/theme.liquid +0 -122
  147. data/test/assign_test.rb +0 -11
  148. data/test/block_test.rb +0 -58
  149. data/test/capture_test.rb +0 -41
  150. data/test/condition_test.rb +0 -115
  151. data/test/content_for_test.rb +0 -15
  152. data/test/context_test.rb +0 -479
  153. data/test/drop_test.rb +0 -162
  154. data/test/error_handling_test.rb +0 -89
  155. data/test/extra/breakpoint.rb +0 -547
  156. data/test/extra/caller.rb +0 -80
  157. data/test/file_system_test.rb +0 -30
  158. data/test/filter_test.rb +0 -147
  159. data/test/helper.rb +0 -24
  160. data/test/html_tag_test.rb +0 -31
  161. data/test/if_else_test.rb +0 -139
  162. data/test/include_tag_test.rb +0 -129
  163. data/test/module_ex_test.rb +0 -89
  164. data/test/output_test.rb +0 -121
  165. data/test/parsing_quirks_test.rb +0 -54
  166. data/test/regexp_test.rb +0 -45
  167. data/test/security_test.rb +0 -41
  168. data/test/standard_filter_test.rb +0 -170
  169. data/test/standard_tag_test.rb +0 -405
  170. data/test/statements_test.rb +0 -137
  171. data/test/strainer_test.rb +0 -27
  172. data/test/template_test.rb +0 -82
  173. data/test/test_helper.rb +0 -28
  174. data/test/unless_else_test.rb +0 -27
  175. data/test/variable_test.rb +0 -173
  176. data/test/yield_test.rb +0 -24
@@ -1,72 +1,43 @@
1
- # Copyright (c) 2005 Tobias Luetke
2
- #
3
- # Permission is hereby granted, free of charge, to any person obtaining
4
- # a copy of this software and associated documentation files (the
5
- # "Software"), to deal in the Software without restriction, including
6
- # without limitation the rights to use, copy, modify, merge, publish,
7
- # distribute, sublicense, and/or sell copies of the Software, and to
8
- # permit persons to whom the Software is furnished to do so, subject to
9
- # the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be
12
- # included in all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
-
22
- $LOAD_PATH.unshift(File.dirname(__FILE__))
1
+ require "liquor/version"
23
2
 
24
3
  module Liquor
25
- FilterSeparator = /\|/
26
- ArgumentSeparator = ','
27
- FilterArgumentSeparator = ':'
28
- VariableAttributeSeparator = '.'
29
- TagStart = /\{\%/
30
- TagEnd = /\%\}/
31
- VariableSignature = /\(?[\w\-\.\[\]]\)?/
32
- VariableSegment = /[\w\-]/
33
- VariableStart = /\{\{/
34
- VariableEnd = /\}\}/
35
- VariableIncompleteEnd = /\}\}?/
36
- QuotedString = /"[^"]*"|'[^']*'/
37
- QuotedFragment = /#{QuotedString}|(?:[^\s,\|'"]|#{QuotedString})+/
38
- StrictQuotedFragment = /"[^"]+"|'[^']+'|[^\s,\|,\:,\,]+/
39
- FirstFilterArgument = /#{FilterArgumentSeparator}(?:#{StrictQuotedFragment})/
40
- OtherFilterArgument = /#{ArgumentSeparator}(?:#{StrictQuotedFragment})/
41
- SpacelessFilter = /#{FilterSeparator}(?:#{StrictQuotedFragment})(?:#{FirstFilterArgument}(?:#{OtherFilterArgument})*)?/
42
- Expression = /(?:#{QuotedFragment}(?:#{SpacelessFilter})*)/
43
- TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
44
- AnyStartingTag = /\{\{|\{\%/
45
- PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableIncompleteEnd}/
46
- TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/
47
- VariableParser = /\[[^\]]+\]|#{VariableSegment}+\??/
48
- end
4
+ require "liquor/diagnostics"
5
+ require "liquor/grammar/lexer"
6
+ require "liquor/grammar/parser"
7
+
8
+ require "liquor/runtime"
9
+
10
+ require "liquor/ast_tools"
11
+ require "liquor/emitter"
12
+ require "liquor/context"
13
+
14
+ require "liquor/tag"
15
+ require "liquor/function"
16
+ require "liquor/external"
17
+
18
+ require "liquor/library"
49
19
 
50
- require 'active_support'
51
- require 'active_support/core_ext/module/delegation' # Bug in AS?
52
- require 'action_dispatch'
53
- require 'liquor/version'
54
- require 'liquor/drop'
55
- require 'liquor/extensions'
56
- require 'liquor/errors'
57
- require 'liquor/strainer'
58
- require 'liquor/context'
59
- require 'liquor/tag'
60
- require 'liquor/block'
61
- require 'liquor/document'
62
- require 'liquor/variable'
63
- require 'liquor/file_system'
64
- require 'liquor/template'
65
- require 'liquor/htmltags'
66
- require 'liquor/standardfilters'
67
- require 'liquor/condition'
68
- require 'liquor/module_ex'
20
+ require "liquor/stdlib/builtin_tags"
21
+ require "liquor/stdlib/builtin_functions"
22
+ require "liquor/stdlib/partial_tags"
69
23
 
70
- # Load all the tags of the standard library
71
- #
72
- Dir[File.dirname(__FILE__) + '/liquor/tags/*.rb'].each { |f| require f }
24
+ require "liquor/compiler"
25
+ require "liquor/manager"
26
+
27
+ if defined?(::ActiveRecord)
28
+ require "liquor/drop/drop"
29
+ end
30
+
31
+ if defined?(::Rails)
32
+ require "liquor/extensions/rails"
33
+ require "liquor/extensions/pagination"
34
+ end
35
+
36
+ if defined?(::Kaminari)
37
+ require "liquor/extensions/kaminari"
38
+ end
39
+
40
+ if defined?(::ThinkingSphinx)
41
+ require "liquor/extensions/thinking_sphinx"
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ module Liquor
2
+ module ASTTools
3
+ private
4
+
5
+ def ntype(node)
6
+ node[0]
7
+ end
8
+
9
+ def nloc(node)
10
+ node[1]
11
+ end
12
+
13
+ def nvalue(node)
14
+ node[2..-1]
15
+ end
16
+
17
+ def kwname(node)
18
+ kw, val = nvalue(node)
19
+ name, = nvalue(kw)
20
+ name
21
+ end
22
+
23
+ def kwvalue(node)
24
+ kw, val = nvalue(node)
25
+ val
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,110 @@
1
+ module Liquor
2
+ class Compiler
3
+ attr_reader :manager
4
+ attr_reader :diagnostics, :source, :code
5
+ attr_reader :tags, :functions
6
+
7
+ def initialize(options={})
8
+ @tags = {}
9
+ @functions = {}
10
+
11
+ @diagnostics = []
12
+ @code = nil
13
+
14
+ options = options.dup
15
+ import_builtins = options.delete(:import_builtins)
16
+ @manager = options.delete(:manager)
17
+
18
+ if options.any?
19
+ raise "Unknown compiler options #{options.keys.join ", "}"
20
+ end
21
+
22
+ if import_builtins || import_builtins.nil?
23
+ Builtins.export self
24
+ Partials.export self
25
+ end
26
+ end
27
+
28
+ def register_tag(tag)
29
+ if @tags.include? tag.name
30
+ raise Exception, "attempt to register tag #{tag.name} twict"
31
+ end
32
+
33
+ @tags[tag.name] = tag
34
+
35
+ self
36
+ end
37
+
38
+ def has_tag?(name)
39
+ @tags.include? name
40
+ end
41
+
42
+ def tag(name)
43
+ @tags[name]
44
+ end
45
+
46
+ def register_function(function)
47
+ if @functions.include? function.name
48
+ raise Exception, "attempt to register function #{function.name} twice"
49
+ end
50
+
51
+ @functions[function.name] = function
52
+
53
+ self
54
+ end
55
+
56
+ def has_function?(name)
57
+ @functions.include? name
58
+ end
59
+
60
+ def function(name)
61
+ @functions[name]
62
+ end
63
+
64
+ def compile(ast, externals=[], template_name=nil)
65
+ @diagnostics.clear
66
+
67
+ externals = externals.map(&:to_sym)
68
+ context = Liquor::Context.new(self, externals)
69
+ source = context.emitter.compile_toplevel(ast)
70
+
71
+ if success?
72
+ if template_name
73
+ source_identity = "(liquor:#{template_name})"
74
+ else
75
+ source_identity = "(liquor)"
76
+ end
77
+
78
+ @source = source
79
+ @code = eval(source, nil, source_identity)
80
+ else
81
+ @source = nil
82
+ @code = nil
83
+ end
84
+
85
+ success?
86
+ end
87
+
88
+ def compile!(ast, externals=[], template_name=nil)
89
+ compile ast, externals, template_name
90
+
91
+ if success?
92
+ @code
93
+ else
94
+ raise diagnostics.first
95
+ end
96
+ end
97
+
98
+ def errors
99
+ @diagnostics.select &:error?
100
+ end
101
+
102
+ def add_diagnostic(diagnostic)
103
+ @diagnostics << diagnostic
104
+ end
105
+
106
+ def success?
107
+ errors.empty?
108
+ end
109
+ end
110
+ end
@@ -1,294 +1,116 @@
1
- module Liquor
1
+ require 'set'
2
2
 
3
- # Context keeps the variable stack and resolves variables, as well as keywords
4
- #
5
- # context['variable'] = 'testing'
6
- # context['variable'] #=> 'testing'
7
- # context['true'] #=> true
8
- # context['10.2232'] #=> 10.2232
9
- #
10
- # context.stack do
11
- # context['bob'] = 'bobsen'
12
- # end
13
- #
14
- # context['bob'] #=> nil class Context
3
+ module Liquor
15
4
  class Context
16
- attr_reader :scopes, :errors, :registers, :environments
5
+ attr_reader :compiler, :emitter
6
+ attr_reader :externals, :variables
7
+ attr_reader :nesting
17
8
 
18
- def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
19
- @environments = [environments].flatten
20
- @scopes = [(outer_scope || {})]
21
- @registers = registers
22
- @errors = []
23
- @rethrow_errors = rethrow_errors
24
- squash_instance_assigns_with_environments
25
- end
9
+ RESERVED_NAMES = %w(_env _buf _storage
10
+ __LINE__ __FILE__ __ENCODING__ BEGIN END alias and begin
11
+ break case class def do else elsif end ensure false for in
12
+ module next nil not or redo rescue retry return self super
13
+ then true undef when yield if unless while until).freeze
26
14
 
27
- def strainer
28
- @strainer ||= Strainer.create(self)
29
- end
15
+ def initialize(compiler, externals)
16
+ @compiler = compiler
17
+ @externals = externals
30
18
 
31
- # adds filters to this context.
32
- # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
33
- # for that
34
- def add_filters(filters)
35
- filters = [filters].flatten.compact
19
+ @emitter = Emitter.new(self)
36
20
 
37
- filters.each do |f|
38
- raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
39
- strainer.extend(f)
40
- end
41
- end
21
+ @variables = Set[]
22
+ @var_stack = []
42
23
 
43
- def handle_error(e)
44
- errors.push(e)
45
- raise if @rethrow_errors
24
+ @mapping = {}
25
+ @map_stack = []
46
26
 
47
- case e
48
- when SyntaxError
49
- "Liquor syntax error: #{e.message.gsub("<", "&lt;").gsub(">", "&gt;")}"
50
- else
51
- "Liquor error: #{e.message.gsub("<", "&lt;").gsub(">", "&gt;")}"
52
- end
53
- end
27
+ @retired = Set[]
54
28
 
29
+ @nesting = 1
55
30
 
56
- def invoke(method, *args)
57
- if args[0].class == Drop::DropProxy && args[0].has_scope?(method)
58
- scope_args = args[1..args.length-1]
59
- return args[0].send(method, *scope_args)
31
+ @externals.each do |external|
32
+ declare external
60
33
  end
61
-
62
- if strainer.respond_to?(method)
63
- strainer.__send__(method, *args)
64
- else
65
- args.first
66
- end
67
- end
68
-
69
- # push new local scope on the stack. use <tt>Context#stack</tt> instead
70
- def push(new_scope={})
71
- raise StackLevelError, "Nesting too deep" if @scopes.length > 100
72
- @scopes.unshift(new_scope)
73
- end
74
-
75
- # merge a hash of variables in the current local scope
76
- def merge(new_scopes)
77
- @scopes[0].merge!(new_scopes)
78
34
  end
79
35
 
80
- # pop from the stack. use <tt>Context#stack</tt> instead
81
- def pop
82
- raise ContextError if @scopes.size == 1
83
- @scopes.shift
36
+ def builtin?(name)
37
+ %w(true false null).include? name
84
38
  end
85
39
 
86
- # pushes a new local scope on the stack, pops it at the end of the block
87
- #
88
- # Example:
89
- #
90
- # context.stack do
91
- # context['var'] = 'hi'
92
- # end
93
- # context['var] #=> nil
94
- #
95
- def stack(new_scope={},&block)
96
- result = nil
97
- push(new_scope)
98
- begin
99
- result = yield
100
- ensure
101
- pop
102
- end
103
- result
104
- end
105
-
106
- def clear_instance_assigns
107
- @scopes[0] = {}
40
+ def function?(name)
41
+ @compiler.has_function? name
108
42
  end
109
43
 
110
- # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquor::Drop</tt>
111
- def []=(key, value)
112
- @scopes[0][key] = value
44
+ def variable?(name)
45
+ @variables.include? name
113
46
  end
114
47
 
115
- def [](key)
116
- resolve(key)
48
+ def allocated?(name)
49
+ builtin?(name) || function?(name) || variable?(name)
117
50
  end
118
51
 
119
- def has_key?(key)
120
- resolve(key) != nil
52
+ def type(name)
53
+ if builtin?(name)
54
+ :builtin
55
+ elsif function?(name)
56
+ :function
57
+ elsif variable?(name)
58
+ :variable
59
+ else
60
+ :free
61
+ end
121
62
  end
122
63
 
123
- private
64
+ def declare(name, loc=nil)
65
+ name = name.to_s
124
66
 
125
- # Look up variable, either resolve directly after considering the name. We can directly handle
126
- # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
127
- # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
128
- # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
129
- #
130
- # Example:
131
- #
132
- # products == empty #=> products.empty?
133
- #
134
- def resolve(key)
135
- case key
136
- when nil, 'nil', 'null', ''
137
- nil
138
- when 'true'
139
- true
140
- when 'false'
141
- false
142
- when 'blank'
143
- :blank?
144
- when 'empty'
145
- :empty?
146
- # Single quoted strings
147
- when /^'(.*)'$/
148
- $1.to_s
149
- # Double quoted strings
150
- when /^"(.*)"$/
151
- $1.to_s
152
- # Integer
153
- when /^([+-]?\d+)$/
154
- $1.to_i
155
- # Ranges
156
- when /^\((\S+)\.\.(\S+)\)$/
157
- (resolve($1).to_i..resolve($2).to_i)
158
- # Floats
159
- when /^([+-]?\d[\d\.]+)$/
160
- $1.to_f
161
- # filtered variables
162
- when SpacelessFilter
163
- filtered_variable(key)
164
- else
165
- variable(key)
67
+ if builtin?(name) || function?(name)
68
+ raise NameError.new("identifier `#{name}' is already occupied by #{type name}", loc)
166
69
  end
167
- end
168
70
 
169
- # fetches an object starting at the local scope and then moving up
170
- # the hierachy
171
- def find_variable(key)
172
- scope = @scopes.find { |s| s.has_key?(key) }
173
- if scope.nil?
174
- @environments.each do |e|
175
- if variable = lookup_and_evaluate(e, key)
176
- scope = e
177
- break
178
- end
71
+ shadow = @var_stack.count > 0 && @var_stack[-1].include?(name)
72
+
73
+ if !@variables.include?(name) || shadow
74
+ mapped, idx = name, 0
75
+ while RESERVED_NAMES.include?(mapped) ||
76
+ @mapping.values.include?(mapped) ||
77
+ @retired.include?(mapped)
78
+ mapped = "#{name}_m#{idx}" # `m' stands for `mangled'
79
+ idx += 1
179
80
  end
81
+
82
+ @variables.add name
83
+ @mapping[name] = mapped
180
84
  end
181
- scope ||= @environments.last || @scopes.last
182
- variable ||= lookup_and_evaluate(scope, key)
183
-
184
- variable = variable.to_liquor
185
-
186
- if variable.class != ActiveRecord::Relation
187
- variable.context = self if variable.respond_to?(:context=)
188
- end
189
- return variable
190
85
  end
191
86
 
192
- # resolves namespaced queries gracefully.
193
- #
194
- # Example
195
- #
196
- # @context['hash'] = {"name" => 'tobi'}
197
- # assert_equal 'tobi', @context['hash.name']
198
- # assert_equal 'tobi', @context['hash["name"]']
199
- #
200
- def variable(markup)
201
- parts = markup.scan(VariableParser)
202
- square_bracketed = /^\[(.*)\]$/
87
+ def access(name, loc=nil)
88
+ name = name.to_s
203
89
 
204
- first_part = parts.shift
205
- if first_part =~ square_bracketed
206
- first_part = resolve($1)
90
+ if variable?(name)
91
+ @mapping[name]
92
+ else
93
+ raise NameError.new("variable `#{name}' is undefined", loc)
207
94
  end
95
+ end
208
96
 
209
- if object = find_variable(first_part)
210
- parts.each do |part|
211
- part = resolve($1) if part_resolved = (part =~ square_bracketed)
212
- # DropProxy was designed like named_scopes for implementing has_many and named_scope wrappers in LiquorDrops.
213
- # DropProxy delegates most Array methods to its internal function to convert an array of ActiveRecord objects to
214
- # an array of liquor drops when you work with it like with an array.
215
- #
216
- # Also it is absolutely safe not to call here to_liquor because when you will try to work with the result it will be automatically converted
217
- # to an array of drops. So you have no chance to do something crucial.
218
- #
219
- # Also when 'my_object.referenced_objects' expression evaluates, the 'object' should be a DropProxy, but you can find
220
- # that 'object = res.to_liquor' will convert it into an array. So 'to_liquor' method was defined in the DropProxy and always returns the itself.
221
- #
222
- if object.class == ActiveRecord::Relation && (['size', 'first', 'last', 'paginate'].include?(part) || part.is_a?(Integer))
223
- if part.is_a?(Integer)
224
- res = object[part]
225
- else
226
- res = object.send(part.intern)
227
- end
228
-
229
- object = res.to_liquor
230
-
231
- elsif object.class == Drop::DropProxy || object.is_a?(Drop) && (object.has_scope?(part) || object.has_many?(part))
232
- if part.is_a?(Integer)
233
- object = object[part]
234
- else
235
- object = object.send(part.intern) rescue nil
236
- end
97
+ def nest
98
+ @var_stack.push @variables
99
+ @variables = @variables.dup
237
100
 
238
- # If object is a hash- or array-like object we look for the
239
- # presence of the key and if its available we return it
240
- elsif object.respond_to?(:[]) and
241
- ((object.respond_to?(:has_key?) and object.has_key?(part)) or
242
- (object.respond_to?(:fetch) and part.is_a?(Integer)))
243
-
244
- # if its a proc we will replace the entry with the proc
245
- # res = object[part]
246
- # res = object[part] = res.call(self) if res.is_a?(Proc) and object.respond_to?(:[]=)
247
- res = lookup_and_evaluate(object, part)
248
- object = res.to_liquor
101
+ @map_stack.push @mapping
102
+ @mapping = @mapping.dup
249
103
 
250
- # Some special cases. If the part wasn't in square brackets and
251
- # no key with the same name was found we interpret following calls
252
- # as commands and call them on the current object
253
- elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
254
- object = object.send(part.intern).to_liquor
104
+ @nesting += 1
255
105
 
256
- # No key was present with the desired value and it wasn't one of the directly supported
257
- # keywords either. The only thing we got left is to return nil
258
- else
259
- return nil
260
- end
106
+ yield
107
+ ensure
108
+ @retired += @mapping.values
261
109
 
262
- # If we are dealing with a drop here we have to
263
- object.context = self if object.respond_to?(:context=)
264
- end
265
- end
110
+ @variables = @var_stack.pop
111
+ @mapping = @map_stack.pop
266
112
 
267
- object
268
- end
269
-
270
- def lookup_and_evaluate(obj, key)
271
- if obj.class != ActiveRecord::Relation && (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
272
- obj[key] = (value.arity == 0 ) ? value.call : value.call(self)
273
- else
274
- value
275
- end
276
- end
277
-
278
- def squash_instance_assigns_with_environments
279
- @scopes.last.each_key do |k|
280
- @environments.each do |env|
281
- if env.has_key?(k)
282
- scopes.last[k] = lookup_and_evaluate(env, k)
283
- break
284
- end
285
- end
286
- end
287
- end
288
-
289
- def filtered_variable(markup)
290
- Variable.new(markup).render(self)
113
+ @nesting -= 1
291
114
  end
292
-
293
115
  end
294
- end
116
+ end