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
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b6aaaa1fc5ee7b1d155a83d5115ac742c51dab44
4
+ data.tar.gz: 14edd85a5da9900b0f30b22a6cdb277f74dbda62
5
+ SHA512:
6
+ metadata.gz: 863ee97c983e2745cc6043a465c121db2bf626ca3361c0ff6bb0ced57069cd8ae94b9e0e8e10f9c0efedbb78f01c2f2c4814fa752f7acbe61bcd2be84431b86f
7
+ data.tar.gz: 1c18c06f865e5f097f87050bf5601b16e935c65ba5149c148b8ec1262eb48fba80ff0454983fdd03e1bfb40f58ad880f60986900685086acd58cc43d026c56d8
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ *.sw?
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -1,12 +1,6 @@
1
1
  language: ruby
2
- script: "rake test"
3
2
  rvm:
4
- - 1.8.7
5
- - 1.9.2
6
3
  - 1.9.3
7
- gemfile:
8
- - Gemfile
9
- branches:
10
- only:
11
- - master
12
- - rails32
4
+ - 2.0.0
5
+ script:
6
+ - bundle exec rspec
data/Gemfile CHANGED
@@ -2,3 +2,10 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in liquor.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ if RUBY_PLATFORM =~ /linux/i
8
+ gem 'rb-inotify', '~> 0.8.8'
9
+ gem 'libnotify'
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch('lib/liquor.rb') { "spec" }
4
+ watch(%r{^lib/liquor/(.+)\.rb$}) { "spec" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+
7
+ watch(%r{^lib/liquor/grammar/.+\.(rl|racc)$}) { `rake` }
8
+ watch('doc/language-spec.md') { `rake` }
9
+
10
+ notification :libnotify
11
+ end
@@ -1,4 +1,8 @@
1
- Copyright (c) 2005, 2006 Tobias Luetke
1
+ Copyright (c) 2012-2013 Peter Zotov <whitequark@whitequark.org>
2
+ 2012 Yaroslav Markin <yaroslav@markin.net>
3
+ 2012 Nate Gadgibalaev <nat@xnsv.ru>
4
+
5
+ MIT License
2
6
 
3
7
  Permission is hereby granted, free of charge, to any person obtaining
4
8
  a copy of this software and associated documentation files (the
@@ -17,4 +21,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
21
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
22
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
23
  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.
24
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
- <a href="http://travis-ci.org/evilmartians/liquor"><img src="https://secure.travis-ci.org/evilmartians/liquor.png"></a>
1
+ # Liquor
2
2
 
3
- # Liquor template engine
4
-
5
- Liquor is a safe (not evaling) template language based on Liquid template language. Safe means that templates cannot affect security of the server they are rendered on. So it is usable when you need to give an ability to edit templates to your users (HTML or email).
3
+ TODO: Write a gem description
6
4
 
7
5
  ## Installation
8
6
 
@@ -18,125 +16,9 @@ Or install it yourself as:
18
16
 
19
17
  $ gem install liquor
20
18
 
21
- ## What does it look like?
22
-
23
- ```html
24
- <ul id="products">
25
- {% for product in products %}
26
- <li>
27
- <h2>{{product.name}}</h2>
28
- Only {{product.price | price }}
29
-
30
- {{product.description | prettyprint | paragraph }}
31
- </li>
32
- {% endfor %}
33
- </ul>
34
- ```
35
-
36
- ## Howto use Liquor
37
-
38
- Liquid supports a very simple API based around the Liquor::Template class.
39
- For standard use you can just pass it the content of a file and call render with a parameters hash.
40
-
41
- ```ruby
42
- @template = Liquid::Template.parse("hi {{name}}") # Parses and compiles the template
43
- @template.render( 'name' => '2kan' ) # => "hi 2kan"
44
- ```
45
-
46
- ## Differs from Liquid
47
-
48
- You can find Liquid docs here: http://github.com/tobi/liquid/wiki
49
-
50
- ### Liquor Drops
51
- Liquor drops are really powerful now. Now you can define access to methods, named scopes and relations.
52
-
53
- #### Attributes
54
- To define attributes you need just add line with with attributes you want to provide access to:
55
-
56
- ```ruby
57
- class MyDrop < Liquor::Drop
58
- self.liquor_attributes << :title << :body
59
- end
60
- ```
61
-
62
- #### Named Scopes
63
- To define access to named scopes (result automatically will be converted to array of liquor drops):
64
-
65
- ```ruby
66
- class MyDrop < Liquor::Drop
67
- self.liquor_scopes << :recent << :limit << :scoped_to_user
68
- end
69
- ```
70
-
71
- Named scopes works like filters in templates. Don't worry about passing drop objects in templates as params in real calls they will be converted back to objects and then result will be converted to array of drops.
72
-
73
- #### Relations
74
-
75
- We have now has_many, has_one and belongs_to relations. You don't need to pass any additional parameters to has_one or belongs_to relations because you just define ability to call real methods (results will be converted to liquid drops).
76
-
77
- But has_many method a bit more complicated since it defines a special proxy object from which you can call your named_scopes. There are several options for has_many relation:
78
-
79
- * class_name — A drop class name (ex. PostDrop)
80
- * sort_scope — Default scope for sorting objects in relations. If you class responds_to recent it will be used as the default one.
81
- * source_class_name — Class name of the source class (ex. Post)
82
- * with_scope — this scope always will be used for this relation
83
-
84
- ### Named Scope
85
-
86
- Sometimes you need to pass a Named Scope object to a template. Now it is possible. When you assign a NamedScope to a template it assigns as is. But you are not able to execute any methods except array methods and paginate method, the last one was added for for better integration with will_paginate plugin.
87
-
88
- ### Tags
89
-
90
- Two new tags were added: content_for and yield.
91
-
92
- Within the context of a layout, yield identifies a section where content from the view should be inserted.
93
- The simplest way to use this is to have a single yield, into which the entire contents of the view currently being rendered is inserted.
94
-
95
- In your layout:
96
- ```erb
97
- <title>{% yield title %}</title>
98
- <body>{% yield %}</body>
99
- ```
100
-
101
- In the view:
102
- ```erb
103
- {% content_for title %} The title {% end_content_for %}
104
- The body
105
- ```
106
-
107
- Will produce:
108
- ```html
109
- <title>The title</title>
110
- <body>The body</body>
111
- ```
112
-
113
- ### Filters
114
-
115
- Few filters were added:
116
-
117
- * yield — yield for content_for tag
118
- * in_groups_of — splits over the array in groups of size num padding any remaining slots with fill_with unless it is false
119
- * in_groups — splits or iterates over the array in number of groups, padding any remaining slots with fill_with unless it is false
120
- * include — returns true if the given object is present in self (that is, if any object == anObject), false otherwise.
121
- * to_json — return a JSON string representing the model drop (using accepted attributes, methods and named_scopes) to_include is a list of related drops through associations
122
- * url_escape — escape url
123
- * reverse — returns a new array containing self’s elements in reverse order.
124
- * decode_html_entities — decodes html entities
125
- * split — divides str into substrings based on a delimiter, returning an array of these substrings.
126
- * compact — returns a copy of self with all nil elements removed.
127
- * concat — concatenates two arrays
128
-
129
- ### Expressions
130
-
131
- You can execute expressions in tags using standard filters syntax but spaces around separator are not allowed.
132
- This is correct expression:
133
- {% assign playlist_array = site.playlists|by_name:artist.name %}
134
-
135
- And this is not:
136
- {% assign playlist_array = site.playlists | by_name:artist.name %}
19
+ ## Usage
137
20
 
138
- You can use expressions in other tags for example in for loops:
139
- {% for artist in site.artists.active.with_orders|scoped_to:genre %}
21
+ TODO: Write usage instructions here
140
22
 
141
23
  ## Contributing
142
24
 
data/Rakefile CHANGED
@@ -1,31 +1,28 @@
1
- #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'rake'
4
- require 'rake/testtask'
5
1
  require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
6
3
 
7
- PKG_VERSION = "1.0.0"
8
- PKG_NAME = "liquor"
9
- PKG_DESC = "A secure non evaling end user template engine with aesthetic markup based on Liquor template engine."
10
-
11
- Rake::TestTask.new(:test) do |t|
12
- t.libs << '.' << 'lib' << 'test'
13
- t.pattern = 'test/*_test.rb'
14
- t.verbose = false
4
+ file 'lib/liquor/grammar/lexer.rb' => 'lib/liquor/grammar/lexer.rl' do
5
+ sh "ragel -R lib/liquor/grammar/lexer.rl -o lib/liquor/grammar/lexer.rb"
15
6
  end
16
7
 
17
- namespace :profile do
18
- task :default => [:run]
19
-
20
- desc "Run the liquor profile/perforamce coverage"
21
- task :run do
8
+ file 'lib/liquor/grammar/parser.rb' => 'lib/liquor/grammar/parser.racc' do
9
+ sh "racc lib/liquor/grammar/parser.racc -o lib/liquor/grammar/parser.rb"
10
+ end
22
11
 
23
- ruby "performance/shopify.rb"
12
+ file 'doc/language-spec.html' => 'doc/language-spec.md' do
13
+ sh "kramdown --template document doc/language-spec.md >doc/language-spec.html"
14
+ end
24
15
 
25
- end
16
+ desc "Regenerate everything (grammar, docs) and run specs."
17
+ task :default => [
18
+ 'lib/liquor/grammar/lexer.rb',
19
+ 'lib/liquor/grammar/parser.rb',
20
+ 'doc/language-spec.html',
21
+ :spec
22
+ ]
26
23
 
27
- desc "Run KCacheGrind"
28
- task :grind => :run do
29
- system "kcachegrind /tmp/liquor.rubyprof_calltreeprinter.txt"
30
- end
24
+ desc "Run specs"
25
+ RSpec::Core::RakeTask.new do |t|
26
+ t.pattern = FileList['spec/**/*_spec.rb']
27
+ t.rspec_opts = %w(-fs --color)
31
28
  end
@@ -0,0 +1,768 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+
5
+ <meta http-equiv="Content-type" content="text/html;UTF-8">
6
+
7
+
8
+ <title>Liquor 2.0 Language Specification</title>
9
+ <meta name="generator" content="kramdown 1.3.0" />
10
+ </head>
11
+ <body>
12
+ <style>
13
+ body {
14
+ margin: 0 auto;
15
+ color: #444444;
16
+ line-height: 1;
17
+ max-width: 960px;
18
+ padding: 30px;
19
+ }
20
+ h1, h2, h3, h4 {
21
+ color: #111111;
22
+ font-weight: 400;
23
+ }
24
+ h1, h2, h3, h4, h5 {
25
+ margin-bottom: 24px;
26
+ padding: 0;
27
+ }
28
+ h1 {
29
+ font-size: 48px;
30
+ }
31
+ h2 {
32
+ font-size: 36px;
33
+ }
34
+ h3 {
35
+ font-size: 24px;
36
+ }
37
+ h4 {
38
+ font-size: 21px;
39
+ }
40
+ h5 {
41
+ font-size: 18px;
42
+ }
43
+ a {
44
+ color: #0099ff;
45
+ margin: 0;
46
+ padding: 0;
47
+ vertical-align: baseline;
48
+ }
49
+ a:visited {
50
+ color: #0047ff;
51
+ }
52
+ a:hover {
53
+ text-decoration: none;
54
+ color: #ff6600;
55
+ }
56
+ li {
57
+ line-height: 24px;
58
+ }
59
+ p, ul, ol {
60
+ font-size: 16px;
61
+ line-height: 24px;
62
+ max-width: 740px;
63
+ margin-bottom: 14px;
64
+ }
65
+ pre {
66
+ padding: 0px 24px;
67
+ max-width: 800px;
68
+ white-space: pre-wrap;
69
+ }
70
+ code {
71
+ line-height: 1.5;
72
+ font-size: 13px;
73
+ background: #E9E8E7;
74
+ }
75
+ pre > code {
76
+ border: none;
77
+ background: none;
78
+ }
79
+ #markdown-toc, #markdown-toc ul {
80
+ list-style-type: none;
81
+ margin: 0 0 0 1em;
82
+ padding: 0;
83
+ }
84
+ #markdown-toc {
85
+ margin: 20px 0 0 0;
86
+ }
87
+ code, dd {
88
+ font-family: Consolas, Monaco, Andale Mono, monospace;
89
+ }
90
+ body, em {
91
+ font-family: Georgia, Palatino, serif;
92
+ }
93
+ dl {
94
+ font-size: 16px;
95
+ line-height: 24px;
96
+ }
97
+ dt {
98
+ font-style: italic;
99
+ margin-top: 10px;
100
+ }
101
+ dt:after {
102
+ font-style: normal;
103
+ padding-left: 0.5em;
104
+ content: '::';
105
+ }
106
+ dd strong, code {
107
+ padding: 1px;
108
+ border: 1px solid #E0DDDA;
109
+ border-radius: 0.2em;
110
+ }
111
+ </style>
112
+
113
+ <h1 id="liquor-20-language-specification">Liquor 2.0 Language Specification</h1>
114
+
115
+ <p style="text-align: right"><em>This version of specification is a working draft.</em></p>
116
+
117
+ <h2 id="table-of-contents">Table of Contents</h2>
118
+
119
+ <ul id="markdown-toc">
120
+ <li><a href="#liquor-20-language-specification">Liquor 2.0 Language Specification</a> <ul>
121
+ <li><a href="#table-of-contents">Table of Contents</a></li>
122
+ <li><a href="#preface">1 Preface</a></li>
123
+ <li><a href="#overview">2 Overview</a> <ul>
124
+ <li><a href="#introduction">2.1 Introduction</a></li>
125
+ <li><a href="#types-and-values">2.2 Types and Values</a></li>
126
+ <li><a href="#type-conversion">2.3 Type Conversion</a></li>
127
+ <li><a href="#expressions">2.4 Expressions</a> <ul>
128
+ <li><a href="#literals">2.4.1 Literals</a></li>
129
+ <li><a href="#operators">2.4.2 Operators</a> <ul>
130
+ <li><a href="#arithmetic-operators">2.4.2.1 Arithmetic Operators</a></li>
131
+ <li><a href="#boolean-operators">2.4.2.2 Boolean Operators</a></li>
132
+ <li><a href="#comparison-operators">2.4.2.3 Comparison Operators</a></li>
133
+ <li><a href="#indexing-operator">2.4.2.4 Indexing Operator</a></li>
134
+ </ul>
135
+ </li>
136
+ <li><a href="#function-calls">2.4.3 Function Calls</a></li>
137
+ <li><a href="#access-operators">2.4.4 Access Operators</a></li>
138
+ <li><a href="#variable-access">2.4.5 Variable Access</a></li>
139
+ <li><a href="#filter-expressions">2.4.6 Filter Expressions</a></li>
140
+ </ul>
141
+ </li>
142
+ <li><a href="#blocks">2.5 Blocks</a></li>
143
+ <li><a href="#interpolations">2.6 Interpolations</a></li>
144
+ <li><a href="#tags">2.7 Tags</a></li>
145
+ </ul>
146
+ </li>
147
+ <li><a href="#grammar">3 Grammar</a> <ul>
148
+ <li><a href="#basic-syntax">3.1 Basic Syntax</a></li>
149
+ <li><a href="#expressions-1">3.2 Expressions</a></li>
150
+ <li><a href="#blocks-1">3.3 Blocks</a></li>
151
+ </ul>
152
+ </li>
153
+ <li><a href="#compile-time-behavior">4 Compile-time Behavior</a> <ul>
154
+ <li><a href="#compile-time-errors">4.1 Errors</a> <ul>
155
+ <li><a href="#syntax-error">4.1.1 Syntax Error</a></li>
156
+ <li><a href="#argument-error">4.1.2 Argument Error</a></li>
157
+ <li><a href="#name-error">4.1.3 Name Error</a></li>
158
+ </ul>
159
+ </li>
160
+ <li><a href="#scope-resolution">4.2 Scope Resolution</a></li>
161
+ </ul>
162
+ </li>
163
+ <li><a href="#runtime-behavior">5 Runtime Behavior</a></li>
164
+ <li><a href="#builtins">6 Builtins</a> <ul>
165
+ <li><a href="#builtin-tags">6.1 Required tags</a> <ul>
166
+ <li><a href="#declare">6.1.1 declare</a></li>
167
+ <li><a href="#assign">6.1.2 assign</a></li>
168
+ <li><a href="#for">6.1.3 for</a></li>
169
+ <li><a href="#if">6.1.4 if</a></li>
170
+ <li><a href="#unless">6.1.5 unless</a></li>
171
+ <li><a href="#capture">6.1.6 capture</a></li>
172
+ <li><a href="#contentfor">6.1.7 content_for</a></li>
173
+ <li><a href="#yield">6.1.8 yield</a></li>
174
+ <li><a href="#include">6.1.9 include</a></li>
175
+ </ul>
176
+ </li>
177
+ <li><a href="#functions">6.2 Functions</a></li>
178
+ </ul>
179
+ </li>
180
+ <li><a href="#appendix-layouts">Appendix A: Layouts</a></li>
181
+ </ul>
182
+ </li>
183
+ </ul>
184
+
185
+ <h2 id="preface">1 Preface</h2>
186
+
187
+ <p>Liquor 2.0 language is developed with several goals in mind.</p>
188
+
189
+ <ul>
190
+ <li>First, it should be secure. There must not be a way to bypass sandbox restrictions.</li>
191
+ <li>Second, it should not necessarily be compatible with Liquor 1.0, but should not have vastly different syntax. Liquor 1.0 syntax is easy to understand by frontend developers, and it should remain so.</li>
192
+ <li>Third, it should be as much statically verifiable as it is rationally possible. The amount of errors which can be detected only at runtime should be minimal. This will also lead to efficient implementations.</li>
193
+ <li>Fourth, it should be elegant and minimalistic.</li>
194
+ </ul>
195
+
196
+ <p>This specification is primarily targeted at language implementors.</p>
197
+
198
+ <h2 id="overview">2 Overview</h2>
199
+
200
+ <h3 id="introduction">2.1 Introduction</h3>
201
+
202
+ <p>Liquor 2.0 language is a templating language for text-based content, e.g. HTML pages. As Liquor is a templating language, it is useless without extension with domain-specific features from a host environment; it is similar to <a href="http://www.lua.org/">Lua</a> in this aspect.</p>
203
+
204
+ <p>Liquor is meant to be statically compiled to another language for efficiency, typically to the one the host environment is executed in. It also provides sandbox restrictions, which allow Liquor code to invoke certain methods on the host objects, but only ones explicitly marked as scriptable.</p>
205
+
206
+ <p>Liquor is a statically scoped, weakly and dynamically typed imperative language with lazily evaluated expressions. As it is essentially a domain-specific language for string concatenation, it has an unusual syntax where code is embedded in a text stream, and a final result of executing a Liquor program is always a string. All language constructs are similarly centered around string manipulation.</p>
207
+
208
+ <p>Liquor has four basic elements: <em>blocks</em>, <em>tags</em>, <em>interpolations</em> and <em>expressions</em>. All four of these elements can be <em>executed</em> and return a value. <em>Blocks</em>, <em>tags</em> and <em>interpolations</em> always return a string value.</p>
209
+
210
+ <p>Liquor does not have non-local control flow constructs by itself, such as exceptions and function definitions. This was done intentionally in order to simplify the language.</p>
211
+
212
+ <p>Liquor has distinct compile-time and runtime error checking. There are no fatal runtime errors, i.e. a Liquor program always evaluates to some value.</p>
213
+
214
+ <h3 id="types-and-values">2.2 Types and Values</h3>
215
+
216
+ <p>Liquor has the following basic types: <strong>Null</strong>, <strong>Boolean</strong>, <strong>Integer</strong>, <strong>String</strong>, <strong>Tuple</strong> and <strong>External</strong>. A value of every type except <strong>External</strong> can be created from within a Liquor program. Values of type <strong>External</strong> can only be returned by the host environment.</p>
217
+
218
+ <p>All Liquor values are immutable; once created, a value cannot change.</p>
219
+
220
+ <p>There is exactly one value of type <strong>Null</strong>, and it is called <em>null</em>.</p>
221
+
222
+ <p>There are exactly two values of type <strong>Boolean</strong>, and they are called <em>true</em> and <em>false</em>.</p>
223
+
224
+ <p>The only values considered “falseful” in a conditional context are <em>null</em> and <em>false</em>. Every other value, including <strong>Integer</strong> 0 (zero), is considered “truthful”.</p>
225
+
226
+ <p>Type <strong>Integer</strong> denotes an integer value of unspecified size. Implementation may impose additional restrictions on the representable range of <strong>Integer</strong> type.</p>
227
+
228
+ <p>Type <strong>String</strong> denotes a sequence of <a href="http://unicode.org/">Unicode</a> codepoints. Note that codepoints are not the same as characters or graphemes; there may exist an implementation-specific way of handling composite characters. See also the relevant <a href="http://www.unicode.org/faq/char_combmark.html">Unicode FAQ entry</a>.</p>
229
+
230
+ <p>Type <strong>Tuple</strong> denotes a heteromorphic sequence of values.</p>
231
+
232
+ <p>Type <strong>External</strong> denotes an object belonging to the host environment.</p>
233
+
234
+ <h3 id="type-conversion">2.3 Type Conversion</h3>
235
+
236
+ <p>Liquor supports exactly one implicit type conversion. In any context where a <strong>String</strong> value is expected, an <strong>Integer</strong> value can be provided. The <strong>Integer</strong> value will then be converted to a corresponding decimal ASCII representation without any leading zeroes.</p>
237
+
238
+ <h3 id="expressions">2.4 Expressions</h3>
239
+
240
+ <p>Liquor features <em>expressions</em>, which can be used to perform computations with values. This section does not define a normative grammar; the full grammar is provided in section <a href="#grammar">Grammar</a>.</p>
241
+
242
+ <p>Order of evaluation of Liquor expressions is not defined. As every value is immutable, the value of the entire expression should not depend upon the order of evaluation. Implementation-provided functions must not access or mutate global state; implementation-provided tags may access or mutate global state, but this is highly discouraged.</p>
243
+
244
+ <h4 id="literals">2.4.1 Literals</h4>
245
+
246
+ <p>All Liquor types except <strong>External</strong> can be specified as literals in expressions.</p>
247
+
248
+ <p>Identifiers <em>null</em>, <em>true</em> and <em>false</em> evaluate to the corresponding values.</p>
249
+
250
+ <p>Numeric literals evaluate to a corresponding <strong>Integer</strong> value, and always use base 10. Numeric literals can be specified with any amount of leading zeroes. There are no negative numeric literals.</p>
251
+
252
+ <p>String literals evaluate to a corresponding <strong>String</strong> value. String literals can be equivalently specified with single or double quotes. Strings support escaping with backslash, and there are exactly two escape sequences: one inserts a literal backslash, and the other one inserts a literal quote. More specifically, single quoted string supports escape sequences <code>\\</code> and <code>\'</code>, and double quoted string supports escape sequences <code>\\</code> and <code>\"</code>. A single backslash followed by any character not specified above is translated to a literal backslash.</p>
253
+
254
+ <p>Tuple literals evaluate to a corresponding <strong>Tuple</strong> value. Tuple literals are surrounded by square brackets and delimited with commas; that is, <code>[ 1, 2, 3 ]</code> is a tuple literal containing three integer values, one, two and three, in that exact order.</p>
255
+
256
+ <h4 id="operators">2.4.2 Operators</h4>
257
+
258
+ <p>Liquor supports unary and binary infix operators in expressions. All operators are left-associative.</p>
259
+
260
+ <p>Liquor operators are listed in order of precedence, from highest to lowest, by the following table:</p>
261
+
262
+ <ol>
263
+ <li><code>[]</code>, <code>.</code>, <code>()</code>, <code>.()</code></li>
264
+ <li>unary <code>-</code>, <code>!</code></li>
265
+ <li><code>*</code>, <code>/</code>, <code>%</code></li>
266
+ <li><code>+</code>, binary <code>-</code></li>
267
+ <li><code>==</code>, <code>!=</code>, <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code></li>
268
+ <li><code>&amp;&amp;</code></li>
269
+ <li><code>||</code></li>
270
+ </ol>
271
+
272
+ <p>The following operators are infix and binary: <code>*</code>, <code>/</code>, <code>%</code>, <code>+</code>, <code>-</code>, <code>==</code>, <code>!=</code>, <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code>, <code>&gt;=</code>, <code>&amp;&amp;</code>, <code>||</code>.
273
+ The following operators are infix and unary: <code>-</code>, <code>!</code>.</p>
274
+
275
+ <p>The operators <code>[]</code>, <code>.</code>, <code>()</code>, <code>.()</code> are not infix and are provided in this table only to define precedence rules.</p>
276
+
277
+ <h5 id="arithmetic-operators">2.4.2.1 Arithmetic Operators</h5>
278
+
279
+ <p>Arithmetic operators are <code>*</code> (multiplication), <code>/</code> (division), <code>%</code> (modulo), <code>+</code> (plus) and <code>-</code> (minus; binary and unary).</p>
280
+
281
+ <p>All arithmetic operators except <code>+</code>, whether unary or binary, require every argument to be of type <strong>Integer</strong>. If this is not the case, a runtime error condition is signaled.</p>
282
+
283
+ <p>Operator <code>+</code> requires both arguments to be of the same type, and only accepts arguments of type <strong>Integer</strong>, <strong>String</strong> or <strong>Tuple</strong>. If any of the conditions is not satisfied, a runtime error condition is signaled. For arguments of type <strong>String</strong> or <strong>Tuple</strong>, the <code>+</code> operator evaluates to the concatenation of left and right arguments in that order.</p>
284
+
285
+ <p>If the result of an arithmetic operation, except operator <code>+</code> with non-<strong>Integer</strong> arguments, exceeds the range an implementation can represent, the behavior is implementation-defined.</p>
286
+
287
+ <h5 id="boolean-operators">2.4.2.2 Boolean Operators</h5>
288
+
289
+ <p>Boolean operators are <code>!</code> (not; unary), <code>&amp;&amp;</code> (and) and <code>||</code> (or).</p>
290
+
291
+ <p>All boolean operators, whether unary or binary, convert each argument to type <strong>Boolean</strong> prior to evaluation. The rules of conversion are:</p>
292
+
293
+ <ol>
294
+ <li>If the value equals <em>null</em> or <em>false</em>, it is assumed to be <em>false</em>.</li>
295
+ <li>Else, the value is assumed to be <em>true</em>.</li>
296
+ </ol>
297
+
298
+ <p>All boolean operators return a value of type <strong>Boolean</strong>. Binary boolean operators do not provide any guarantees on order or sequence of evaluation. However, a correct implementation which does not feature functions with side effects will not suffer from this behavior.</p>
299
+
300
+ <h5 id="comparison-operators">2.4.2.3 Comparison Operators</h5>
301
+
302
+ <p>Comparison operators are <code>==</code> (equals), <code>!=</code> (not equals), <code>&lt;</code> (less), <code>&lt;=</code> (less or equal), <code>&gt;</code> (greater) and <code>&gt;=</code> (greater or equal).</p>
303
+
304
+ <p>Operators <code>==</code> and <code>!=</code> compare values by equality, not identity. Thus, the expression <code>[ 1, 2 ] == [ 1, 2 ]</code> evluates to <em>true</em>. These operators never signal an error condition or implicitly convert types.</p>
305
+
306
+ <p>Operators <code>&lt;</code>, <code>&lt;=</code>, <code>&gt;</code> and <code>&gt;=</code> require both arguments to be of type <strong>Integer</strong>. If this is not the case, a runtime error condition is signaled. Otherwise, a corresponding value of type <strong>Boolean</strong> is returned.</p>
307
+
308
+ <h5 id="indexing-operator">2.4.2.4 Indexing Operator</h5>
309
+
310
+ <p>Indexing operator is <code>[]</code>.</p>
311
+
312
+ <p>Indexing operator requires its left-hand side argument to be of type <strong>Tuple</strong> or <strong>External</strong>, and right-hand side argument to be of type <strong>Integer</strong>. If this is not the case, a runtime error condition is signaled.</p>
313
+
314
+ <p>If the left-hand side argument is of type <strong>External</strong>, the behavior is implementation-defined. A runtime error condition can be signaled if the particular external value does not support indexing.</p>
315
+
316
+ <p>Indexing operator of form <code><em>t</em>[<em>n</em>]</code> evaluates to <em>n</em>-th value from tuple <em>t</em> with zero-based indexing. If <code>n</code> is negative, then <em>n</em>+1-th element from the end of tuple is returned. For example, <code><em>t</em>[-1]</code> will evaluate to the last element of the tuple <em>t</em>.</p>
317
+
318
+ <p>If the requested element does not exist in the tuple, the indexing operator evaluates to <em>null</em>.</p>
319
+
320
+ <h4 id="function-calls">2.4.3 Function Calls</h4>
321
+
322
+ <p>Identifiers can be bound to functions prior to compilation. Identifiers <em>null</em>, <em>true</em> and <em>false</em> cannot be bound to a function.</p>
323
+
324
+ <p>Functions are defined in an implementation-specific way. Functions can have zero to one unnamed formal parameters and any amount of named formal parameters. If an unnamed formal parameter is accepted, it is mandatory. Named formal parameters can be either mandatory or optional. Absence of a mandatory formal parameter will result in a compile-time error (<a href="#argument-error">argument error</a>). Named formal parameter order is irrelevant.</p>
325
+
326
+ <p>Function calls have mandatory parentheses, and arguments are whitespace-delimited.</p>
327
+
328
+ <p>If a function call includes two named parameters with the same name, a compile-time error (<a href="#syntax-error">syntax error</a>) is raised.</p>
329
+
330
+ <p>If a hypothetical function <em>substr</em> has one unnamed formal parameter and two optional named formal parameters <em>from</em> and <em>length</em>, then all of the following expressions are syntactically valid and will not result in a compile-time error: <code>substr("foobar")</code>, <code>substr("foobar" from: 1)</code>, <code>substr("foobar" from: 1 length:(5 - 2))</code>. The following expression, however, is syntactically valid but will result in a compile-time error: <code>substr(from: 1)</code>.</p>
331
+
332
+ <h4 id="access-operators">2.4.4 Access Operators</h4>
333
+
334
+ <p>Access operators are <code>.</code> and <code>.()</code>.</p>
335
+
336
+ <p>The <code>.</code> form is syntactic sugar for <code>.()</code> form without any arguments. That is, <code><em>e</em>.<em>f</em></code> is completely equivalent to <code><em>e</em>.<em>f</em>()</code>.</p>
337
+
338
+ <p>Access operator requires its left-hand side argument to be of type <strong>External</strong>. If this is not the case, a runtime error condition is signaled.</p>
339
+
340
+ <p>Access operator of form <code><em>e</em>.<em>f</em>(<em>arg</em> <em>kw:</em> <em>value</em>)</code> evaluates to the result of calling method <em>f</em> of external object <em>e</em> with the corresponding arguments. Argument syntax is the same as for <a href="#function-calls">function calls</a>.</p>
341
+
342
+ <p>This evaluation is done in an implementation-defined way. Access operator can evaluate to any type.</p>
343
+
344
+ <p>If the requested method does not exist in the external object or cannot successfully evaluate, a runtime error condition is signaled. Errors in the called method must not interrupt execution of the calling Liquor program.</p>
345
+
346
+ <h4 id="variable-access">2.4.5 Variable Access</h4>
347
+
348
+ <p>Every identifier except <em>null</em>, <em>true</em> and <em>false</em> which is not bound to a function name is available to be bound as a variable name. Such identifier would evaluate to a value of the variable.</p>
349
+
350
+ <p>Variable definition and scoping will be further discussed in section <a href="#tags">Tags</a>.</p>
351
+
352
+ <p>Referencing an undefined variable will result in a compile-time error (<a href="#name-error">name error</a>).</p>
353
+
354
+ <h4 id="filter-expressions">2.4.6 Filter Expressions</h4>
355
+
356
+ <p>Filter expressions are a syntactic sugar for chaining method calls.</p>
357
+
358
+ <p>Filter expressions consist of a linear chain of function calls where <em>n</em>-th function’s return value is passed to <em>n+1</em>-th function’s unnamed parameter. Named parameters may be specified without parentheses within a corresponding chain element.</p>
359
+
360
+ <p>All functions used in a filter expression should accept an unnamed parameter. If this is not the case, a compile-time error (<a href="#argument-error">argument error</a>) is raised. Semantics of mandatory and optional named parameters are the same as for <a href="#function-calls">regular function calls</a>.</p>
361
+
362
+ <p>
363
+ In essence, <code><em>e</em> | <em>f</em> a: 1 | <em>g</em></code> is equivalent to <code><em>g</em>(<em>f</em>(<em>e</em>() a: 1))</code>.
364
+ </p>
365
+
366
+ <h3 id="blocks">2.5 Blocks</h3>
367
+
368
+ <p>A block is a chunk of plaintext with <em>tags</em> and <em>interpolations</em> embedded into it. Every Liquor program has at least one block: the toplevel one.</p>
369
+
370
+ <p>A block consisting only of plaintext would return its literal value upon execution. Thus, the famous Hello World program would be as follows:</p>
371
+
372
+ <pre><code>Hello World!
373
+ </code></pre>
374
+
375
+ <p>This program would evaluate to a string <code>Hello World!</code>.</p>
376
+
377
+ <p>A block can have other elements embedded into it. When such a block is executed, these elements are executed in lexical order and are replaced with the value returned by the element.</p>
378
+
379
+ <h3 id="interpolations">2.6 Interpolations</h3>
380
+
381
+ <p>An interpolation is a syntactic construct of form <code>{{ expr }}</code> which can be embedded in a block. The expression <code>expr</code> should evaluate to a value of type <strong>String</strong> or <strong>Null</strong>; an <a href="#type-conversion">implicit conversion</a> might take place. If this is not the case, a runtime error condition is signaled.</p>
382
+
383
+ <p>If <em>expr</em> evaluates to a <strong>String</strong>, the interpolation returns it. Otherwise, the interpolation returns an empty string.</p>
384
+
385
+ <p>An example of using an interpolation would be:</p>
386
+
387
+ <pre><code>The sum of two and three is: {{ 2 + 3 }}
388
+ </code></pre>
389
+
390
+ <p>This program would evaluate to a string <code>The sum of two and three is: 5</code>.</p>
391
+
392
+ <h3 id="tags">2.7 Tags</h3>
393
+
394
+ <p>A tag is a syntactic construct of form <code>{% tag expr kw: arg do: %} ... {% end tag %}</code>. A tag has a syntax similar to a function call, but it can receive blocks of code as argument values and lazily evaluate passed expressions and blocks of code.</p>
395
+
396
+ <p>Tags have full control upon parameter evaluation. Tags can require arguments to be of a certain lexical form, e.g. a <code>for</code> tag could require its unnamed formal parameter to be a lexical identifier.</p>
397
+
398
+ <p>To pass a block of code to a tag, the closing tag delimiter should immediately follow a parameter name. Everything from the closing tag delimiter to the matching opening tag delimiter should be parsed as a block and passed as a value of the corresponding parameter. After the matching opening tag delimiter, the parameter list is continued.</p>
399
+
400
+ <p>If a tag <code><em>t</em></code> does not include any embedded blocks, it ends after a first matching closing tag delimiter. Otherwise, the tag ends after a first matching construct of the form <code>{% end <em>t</em> %}</code>.</p>
401
+
402
+ <p>Unlike functions, tags can receive multiple named parameters with the same name. Named parameters of tags are a syntactic tool and should be thoroughly verified by the implementation. Specifying incorrect names or order of named parameters may result in a compile-time error (<a href="#syntax-error">syntax error</a>).</p>
403
+
404
+ <p>All of the following are examples of syntactically valid tags:</p>
405
+
406
+ <pre><code>{% yield %}
407
+
408
+ {% if var &gt; 10 do: %}
409
+ Var is greater than 10.
410
+ {% end if %}
411
+
412
+ {% for i in: [ 1, 2, 3 ] do: %}
413
+ Value: {{ i }}
414
+ {% end for %}
415
+
416
+ {% if length(params.test) == 1 then: %}
417
+ Test has length 1.
418
+ {% elsif: length(params.test) == 2 then: %}
419
+ Test has length 2.
420
+ {% else: %}
421
+ Test has unidentified length.
422
+ {% end if %}
423
+
424
+ {% capture "buffer" do: %}
425
+ This text will be printed twice.
426
+ {% end capture %}
427
+ {% yield from: "buffer" %}
428
+ {% yield from: "buffer" %}
429
+ </code></pre>
430
+
431
+ <h2 id="grammar">3 Grammar</h2>
432
+
433
+ <p>The following Extended Backus-Naur Form grammar is normative. The native character set of Liquor is Unicode, and every character literal specified is an explicit codepoint.</p>
434
+
435
+ <p>Statement <code><em>a</em> to <em>b</em></code> is equivalent to codepoint set which includes every codepoint from <em>a</em> to <em>b</em> inclusive. Statement <code><em>a</em> except <em>b</em></code> means that both <em>a</em> and <em>b</em> are tokens which consist of exactly one codepoint, and every character satisfying <em>a</em> and not satisfying in <em>b</em> is accepted. Statement <code>lookahead <em>a</em></code> means that the current token should only be produced if the codepoint immediately following it satisfies <em>a</em>.</p>
436
+
437
+ <p>Strictly speaking, this grammar lies within <em>GLR</em> domain, but if, as it is usually the case, an implementation has separate lexer and parser, a <em>LALR(1)</em> parser could be used. This will be further explained in section <a href="#blocks-1">Blocks</a>.</p>
438
+
439
+ <h3 id="basic-syntax">3.1 Basic Syntax</h3>
440
+
441
+ <dl>
442
+ <dt>Whitespace</dt>
443
+ <dd><strong>U+0007</strong> | <strong>U+000A</strong> | <strong>U+0020</strong></dd>
444
+ <dt>Alpha</dt>
445
+ <dd><strong>a</strong> to <strong>z</strong> | <strong>A</strong> to <strong>Z</strong></dd>
446
+ <dt>Digit</dt>
447
+ <dd><strong>0</strong> to <strong>9</strong></dd>
448
+ <dt>Any</dt>
449
+ <dd>any Unicode character</dd>
450
+ <dt>Symbol</dt>
451
+ <dd><em>Alpha</em> | <strong>_</strong></dd>
452
+ <dt>Identifier</dt>
453
+ <dd><em>Symbol</em> ( <em>Symbol</em> | <em>Digit</em> )* lookahead ( <em>Any</em> except <strong>:</strong> )</dd>
454
+ <dt>Keyword</dt>
455
+ <dd><em>Symbol</em> ( <em>Symbol</em> | <em>Digit</em> )* <strong>:</strong></dd>
456
+ <dd><strong>=</strong></dd>
457
+ <dt>IntegerLiteral</dt>
458
+ <dd><em>Digit</em>+ lookahead ( <em>Any</em> except <em>Symbol</em> )</dd>
459
+ <dt>StringLiteral</dt>
460
+ <dd><strong>"</strong> ( <strong>\\</strong> | <strong>\"</strong> | <em>Any</em> except <strong>"</strong> )* <strong>"</strong></dd>
461
+ <dd><strong>'</strong> ( <strong>\\</strong> | <strong>\'</strong> | <em>Any</em> except <strong>'</strong> )* <strong>'</strong></dd>
462
+ <dt>TupleLiteral</dt>
463
+ <dd><strong>[</strong> <em>TupleLiteralContent</em> <strong>]</strong></dd>
464
+ <dt>TupleLiteralContent</dt>
465
+ <dd><em>Expression</em> <strong>,</strong> <em>TupleLiteralContent</em></dd>
466
+ <dd><em>Expression</em></dd>
467
+ <dd>empty</dd>
468
+ </dl>
469
+
470
+ <h3 id="expressions-1">3.2 Expressions</h3>
471
+
472
+ <p>Operator precedence table is provided in section <a href="#operators">Operators</a>.</p>
473
+
474
+ <dl>
475
+ <dt>PrimaryExpression</dt>
476
+ <dd><em>Identifier</em></dd>
477
+ <dd><strong>(</strong> <em>Expression</em> <strong>)</strong></dd>
478
+ <dt>Expression</dt>
479
+ <dd><em>IntegerLiteral</em></dd>
480
+ <dd><em>StringLiteral</em></dd>
481
+ <dd><em>TupleLiteral</em></dd>
482
+ <dd><em>Identifier</em> <em>FunctionArguments</em></dd>
483
+ <dd><em>PrimaryExpression</em> <strong>[</strong> <em>Expression</em> <strong>]</strong></dd>
484
+ <dd><em>Expression</em> <strong>.</strong> <em>Identifier</em> <em>FunctionArguments</em>?</dd>
485
+ <dd><strong>-</strong> <em>Expression</em></dd>
486
+ <dd><strong>!</strong> <em>Expression</em></dd>
487
+ <dd><em>Expression</em> <strong>*</strong> <em>Expression</em></dd>
488
+ <dd><em>Expression</em> <strong>/</strong> <em>Expression</em></dd>
489
+ <dd><em>Expression</em> <strong>%</strong> <em>Expression</em></dd>
490
+ <dd><em>Expression</em> <strong>+</strong> <em>Expression</em></dd>
491
+ <dd><em>Expression</em> <strong>-</strong> <em>Expression</em></dd>
492
+ <dd><em>Expression</em> <strong>==</strong> <em>Expression</em></dd>
493
+ <dd><em>Expression</em> <strong>!=</strong> <em>Expression</em></dd>
494
+ <dd><em>Expression</em> <strong>&lt;</strong> <em>Expression</em></dd>
495
+ <dd><em>Expression</em> <strong>&lt;=</strong> <em>Expression</em></dd>
496
+ <dd><em>Expression</em> <strong>&gt;</strong> <em>Expression</em></dd>
497
+ <dd><em>Expression</em> <strong>&gt;=</strong> <em>Expression</em></dd>
498
+ <dd><em>Expression</em> <strong>&amp;&amp;</strong> <em>Expression</em></dd>
499
+ <dd><em>Expression</em> <strong>||</strong> <em>Expression</em></dd>
500
+ <dt>KeywordArguments</dt>
501
+ <dd>( <em>Keyword</em> <em>Expression</em> )*</dd>
502
+ <dt>FunctionArguments</dt>
503
+ <dd><strong>(</strong> <em>Expression</em>? <em>KeywordArguments</em> <strong>)</strong></dd>
504
+ <dt>FilterChain</dt>
505
+ <dd><em>Expression</em> <strong>|</strong> <em>FilterChainContinuation</em></dd>
506
+ <dt>FilterChainContinuation</dt>
507
+ <dd><em>FilterFunctionCall</em> <strong>|</strong> <em>FilterChainContinuation</em></dd>
508
+ <dd><em>FilterFunctionCall</em></dd>
509
+ <dt>FilterFunctionCall</dt>
510
+ <dd><em>Identifier</em> <em>FunctionKeywordArguments</em></dd>
511
+ </dl>
512
+
513
+ <h3 id="blocks-1">3.3 Blocks</h3>
514
+
515
+ <p>Inside a <em>Tag</em> or <em>Interpolation</em> body any <em>Whitespace</em> is used to separate adjacent tokens, but is otherwise ignored. The cases where naïvely removing <em>Whitespace</em> would cause ambiguity can be determined by watching for <code>lookahead</code> clauses.</p>
516
+
517
+ <p>The <em>Tag</em>, <em>TagFirstContinuation</em> and <em>EndTag</em> production rules deviate from canonical <em>LR(1)</em> grammar structure. To parse these rules correctly, a <em>LALR(1)</em> parser should maintain a stack of tag identifiers and correctly decide on ambiguous reduction of rules <em>Identifier</em> and <em>EndTag</em>.</p>
518
+
519
+ <p>When the parser follows the second reduction for rule <em>TagFirstContinuation</em>, it should push the corresponding <em>Tag</em> <em>Identifier</em> on the top of the tag stack.</p>
520
+
521
+ <p>When the parser is about to decide whether it should reduce the sequence satisfying <em>Identifier</em> to <em>EndTag</em> or leave it as is, it should only reduce the sequence to <em>EndTag</em> if the <em>Identifier</em> part of the <em>EndTag</em> rule equals the value at the top of the tag stack. If this is the case, the topmost value is popped from the tag stack.</p>
522
+
523
+ <dl>
524
+ <dt>Block</dt>
525
+ <dd><em>Plaintext</em> <em>Block</em></dd>
526
+ <dd><em>Interpolation</em> <em>Block</em></dd>
527
+ <dd><em>Tag</em> <em>Block</em></dd>
528
+ <dd><em>Comment</em> <em>Block</em></dd>
529
+ <dd>empty</dd>
530
+ <dt>Comment</dt>
531
+ <dd><strong>{!</strong> ( <em>Comment</em> | <em>Any</em>* )+ <strong>!}</strong></dd>
532
+ <dt>Plaintext</dt>
533
+ <dd>( <em>Any</em> except <strong>{</strong> | <strong>{</strong> <em>Any</em> except ( <strong>{</strong> | <strong>%</strong> ) )+</dd>
534
+ <dt>Interpolation</dt>
535
+ <dd><strong>{{</strong> ( <em>Expression</em> | <em>FilterChain</em> ) <strong>}}</strong></dd>
536
+ <dt>Tag</dt>
537
+ <dd><strong>{%</strong> <em>Identifier</em> <em>Expression</em>? <em>KeywordArguments</em> <em>TagFirstContinuation</em></dd>
538
+ <dt>TagFirstContinuation</dt>
539
+ <dd><strong>%}</strong></dd>
540
+ <dd><em>TagBlock</em> <em>TagNextContinuation</em></dd>
541
+ <dt>TagNextContinuation</dt>
542
+ <dd><em>KeywordArguments</em> <em>TagBlock</em> <em>TagNextContinuation</em></dd>
543
+ <dd><em>EndTag</em> <strong>%}</strong></dd>
544
+ <dt>TagBlock</dt>
545
+ <dd><em>Keyword</em> <strong>%}</strong> <em>Block</em> <strong>{%</strong></dd>
546
+ <dt>EndTag</dt>
547
+ <dd><strong>end</strong> <strong>U+0020</strong> <em>Identifier</em> at the top of tag stack</dd>
548
+ </dl>
549
+
550
+ <h2 id="compile-time-behavior">4 Compile-time Behavior</h2>
551
+
552
+ <p>Liquor compiling process consists of three distinct parts: <em>parsing</em>, <em>scope resolution</em> and <em>translation</em>. Each stage includes exhaustive error checking; additionally, translation and scope resolution are heavily dependent on the defined tags and their behavior.</p>
553
+
554
+ <h3 id="compile-time-errors">4.1 Errors</h3>
555
+
556
+ <p>To ease development process, an implementation generally should not stop compilation after encountering an error. As an exception to the general rule, implementation must stop parsing and abandon any intermediate result after encountering a syntax error. Rationale to this behavior is that with Liquor’s interleaved structure, successful error recovery after parsing errors is unlikely.</p>
557
+
558
+ <p>Every error must carry precise location information: in particular, an error location must feature <em>line</em>, <em>start column</em> and <em>end column</em>.</p>
559
+
560
+ <p>The following algorithm can be used to calculate precise location information for every Unicode character in the source code:</p>
561
+
562
+ <ol>
563
+ <li>The initial line and column numbers equal 1.</li>
564
+ <li>For each character in the source, in order, perform the following:
565
+ <ol>
566
+ <li>If the character is <strong>U+000A</strong>, increase line number by 1 and set column number to 1.</li>
567
+ <li>If the character is <strong>U+0007</strong>, increase column number by 1 until it equals zero modulo 8. If the column number already equals zero modulo 8, increase it by 8.</li>
568
+ <li>If the character is a combining character, the implementation may recognize this fact and do nothing.</li>
569
+ <li>If nothing of the above applies, increase column number by 1.</li>
570
+ </ol>
571
+ </li>
572
+ </ol>
573
+
574
+ <p>This algorithm, unlike the rest of Liquor, is specified in terms of characters and not codepoints. This means that an implementation must recognize surrogate pairs and compose them into one character.</p>
575
+
576
+ <h4 id="syntax-error">4.1.1 Syntax Error</h4>
577
+
578
+ <p>Syntax error will be signaled upon encountering any of the following conditions:</p>
579
+
580
+ <ol>
581
+ <li>Parsing failure (section <a href="#grammar">Grammar</a>)</li>
582
+ <li>Duplicate function keyword arguments (section <a href="#function-calls">Function Calls</a>)</li>
583
+ <li>Incorrect tag syntax (section <a href="#tags">Tags</a>)</li>
584
+ </ol>
585
+
586
+ <p>Syntax errors must include source location information and point to the exact token which caused the error.</p>
587
+
588
+ <h4 id="argument-error">4.1.2 Argument Error</h4>
589
+
590
+ <p>Argument error will be signaled upon encountering any of the following conditions:</p>
591
+
592
+ <ol>
593
+ <li>Absence of a mandatory parameter, or presence of non-accepted parameter (sections <a href="#function-calls">Function Calls</a>, <a href="#tags">Tags</a>)</li>
594
+ </ol>
595
+
596
+ <p>Argument errors must include source location information and point either to the exact parameter which caused the error, or to the argument list in case of a missing parameter.</p>
597
+
598
+ <h4 id="name-error">4.1.3 Name Error</h4>
599
+
600
+ <p>Name error will be signaled upon encountering any of the following conditions:</p>
601
+
602
+ <ol>
603
+ <li>Referencing an undefined variable (sections <a href="#variable-access">Variable Access</a>, <a href="#scope-resolution">Scope Resolution</a>)</li>
604
+ <li>Referencing an undefined function (section <a href="#function-calls">Function Calls</a>)</li>
605
+ <li>Encountering an undefined tag (section <a href="#tags">Tags</a>)</li>
606
+ <li>Trying to bind an already bound identifier (section <a href="#scope-resolution">Scope Resolution</a>)</li>
607
+ </ol>
608
+
609
+ <p>Name errors must include source location information and point to the exact token which caused the error.</p>
610
+
611
+ <h3 id="scope-resolution">4.2 Scope Resolution</h3>
612
+
613
+ <p><a href="#tags">Tags</a> control every aspect of scope construction and resolution.</p>
614
+
615
+ <p>Basically, tags can perform three scope-related actions: <em>declare</em> a variable, <em>assign</em> a variable and create a <em>nested scope</em>.</p>
616
+
617
+ <p>Declaring a variable binds the identifier to a value. To declare a variable, the identifier should not be bound in the current scope. If this is not the case, a compile-time error (<a href="#name-error">name error</a>) is raised. If the identifier is bound in an outer scope, it will be rebound in the current scope. Such a binding ceases to exist when the current scope is left.</p>
618
+
619
+ <p>Assigning a variable, similarly to accessing, requires the variable to be declared in any of the scopes. Assigning a variable changes its value in the innermost scope.</p>
620
+
621
+ <p>Creating a nested scope allows for shadowing of the variables. Tags must only execute contents of the passed blocks in a nested scope. Passed expressions are always executed in the tag’s scope. A tag must ensure that every scope it created will be left before the tag will finish executing.</p>
622
+
623
+ <p>An implementation should have a way to inject variables into the outermost scope.</p>
624
+
625
+ <h2 id="runtime-behavior">5 Runtime Behavior</h2>
626
+
627
+ <p>TODO</p>
628
+
629
+ <h2 id="builtins">6 Builtins</h2>
630
+
631
+ <p>Implementations must implement every builtin tag and function mentioned in this section. Implementations may implement any additional tags, but must not alter behavior of the described ones.</p>
632
+
633
+ <h3 id="builtin-tags">6.1 Required tags</h3>
634
+
635
+ <h4 id="declare">6.1.1 declare</h4>
636
+
637
+ <p>Tag <em>declare</em> has one valid syntactic form:</p>
638
+
639
+ <pre><code>{% declare <em>var</em> = <em>expr</em> %}</code></pre>
640
+
641
+ <p><em>Declare</em> binds the name <em>var</em> to the result of executing <em>expr</em> in the current scope. If <em>var</em> is already bound in current scope, <em>declare</em> mutates the binding. If <em>var</em> is already bound in an outer scope, <em>declare</em> creates a new binding in the current scope.</p>
642
+
643
+ <p>The <em>declare</em> tag itself evaluates to an empty string.</p>
644
+
645
+ <h4 id="assign">6.1.2 assign</h4>
646
+
647
+ <p>Tag <em>assign</em> has one valid syntactic form:</p>
648
+
649
+ <pre><code>{% assign <em>var</em> = <em>expr</em> %}</code></pre>
650
+
651
+ <p><em>Assign</em> binds the name <em>var</em> to the result of executing <em>expr</em> in the current scope. If <em>var</em> is already bound, <em>assign</em> mutates the binding.</p>
652
+
653
+ <p>The <em>assign</em> tag itself evaluates to an empty string.</p>
654
+
655
+ <h4 id="for">6.1.3 for</h4>
656
+
657
+ <p>Tag <em>for</em> has two valid syntactic forms:</p>
658
+
659
+ <pre><code>{% for <em>var</em> in: <em>list</em> do: %}
660
+ <em>code</em>
661
+ {% end for %}</code></pre>
662
+
663
+ <pre><code>{% for <em>var</em> from: <em>lower-limit</em> to: <em>upper-limit</em> do: %}
664
+ <em>code</em>
665
+ {% end for %}</code></pre>
666
+
667
+ <p>In the <em>for..in</em> form, this tag invokes <em>code</em> with <em>var</em> bound to each element of <em>list</em> sequentally. If <em>list</em> is not a <em>Tuple</em>, a runtime error condition is signaled.</p>
668
+
669
+ <p>In the <em>for..from..to</em> form, this tag invokes <em>code</em> with <em>var</em> bound to each integer between <em>lower-limit</em> and <em>upper-limit</em>, inclusive. If <em>lower-limit</em> or <em>upper-limit</em> is not an <em>Integer</em>, a [runtime error condition] is signaled.</p>
670
+
671
+ <p>The <em>for</em> tag returns the concatenation of the values its <em>code</em> has evaluated to.</p>
672
+
673
+ <h4 id="if">6.1.4 if</h4>
674
+
675
+ <p>Tag <em>if</em> has one valid syntactic form:</p>
676
+
677
+ <pre><code>{% if <em>cond-1</em> then: %}
678
+ <em>code-1</em>
679
+ <em>[</em>{% elsif: <em>cond-2</em> then: %}
680
+ <em>code-2</em><em>] ...</em>
681
+ <em>[</em>{% else: %}
682
+ <em>code-else</em><em>]</em>
683
+ {% end if %}
684
+ </code></pre>
685
+
686
+ <p>This tag can optionally have any amount of <em>elsif</em> clauses and only one <em>else</em> clause.</p>
687
+
688
+ <p>The <em>if</em> tag sequentally evaluates each passed condition <em>cond-1</em>, <em>cond-2</em>, … until a <a href="#boolean-operators">truthful</a> value is computed. Then, it executes the corresponding code. If none of the conditions evaluate to a truthful value, the tag executes <em>code-else</em> if it exists.</p>
689
+
690
+ <p>The <em>if</em> tag returns the result of evaluating the corresponding code block, or an empty string if none of the blocks were executed.</p>
691
+
692
+ <h4 id="unless">6.1.5 unless</h4>
693
+
694
+ <p>Tag <em>unless</em> has one valid syntactic form:</p>
695
+
696
+ <pre><code>{% unless <em>cond</em> then: %}
697
+ <em>code</em>
698
+ {% end unless %}</code></pre>
699
+
700
+ <p>The <em>unless</em> tag evaluates <em>cond</em>. Unless it yields a <a href="#boolean-operators">truthful</a>, <em>code</em> is also evaluated.</p>
701
+
702
+ <p>The <em>unless</em> tag returns the result of evaluating <em>code</em>, or an empty string.</p>
703
+
704
+ <h4 id="capture">6.1.6 capture</h4>
705
+
706
+ <p>Tag <em>capture</em> has one valid syntactic form:</p>
707
+
708
+ <pre><code>{% capture <em>var</em> = %}
709
+ <em>code</em>
710
+ {% end capture %}</code></pre>
711
+
712
+ <p>The <em>capture</em> tag evaluates <em>code</em> and binds the name <em>var</em> to the result. If <em>var</em> is already bound, <em>capture</em> mutates the binding.</p>
713
+
714
+ <p>The <em>capture</em> tag returns an empty string.</p>
715
+
716
+ <h4 id="contentfor">6.1.7 content_for</h4>
717
+
718
+ <p>Tag <em>content_for</em> has one valid syntactic form:</p>
719
+
720
+ <pre><code>{% content_for <em>"handle"</em> capture: %}
721
+ <em>code</em>
722
+ {% end content_for %}</code></pre>
723
+
724
+ <p>The <em>content_for</em> tag accepts a <strong>String</strong> handle as an immediate value. It evaluates <em>code</em> and assigns the result to the handle <em>handle</em>, which must be stored in an implementation-specific way.</p>
725
+
726
+ <p>The <em>content_for</em> tag returns an empty string.</p>
727
+
728
+ <p>See also notes on <a href="#appendix-layouts">Layout implementation</a>.</p>
729
+
730
+ <h4 id="yield">6.1.8 yield</h4>
731
+
732
+ <p>Tag <em>yield</em> has three valid syntactic forms:</p>
733
+
734
+ <pre><code>{% yield %}</code></pre>
735
+
736
+ <p>In this form, the <em>yield</em> tag evaluates to the content of inner template.</p>
737
+
738
+ <pre><code>{% yield <em>"handle"</em> %}</code></pre>
739
+ <pre><code>{% yield <em>"handle"</em> if_none: %}
740
+ <em>code</em>
741
+ {% end yield %}</code></pre>
742
+
743
+ <p>The <em>yield</em> tag accepts a <strong>String</strong> <em>handle</em> as an immediate value. If a string with handle <em>handle</em> was captured previously with <a href="#content_for">{% content_for %}</a>, then <em>yield</em> returns that string. If there is no captured string with that handle, <em>yield</em> either returns the result of evaluating <em>if_none</em> block if it exists, or an empty string.</p>
744
+
745
+ <p>See also notes on <a href="#appendix-layouts">Layout implementation</a>.</p>
746
+
747
+ <h4 id="include">6.1.9 include</h4>
748
+
749
+ <p>Tag <em>include</em> has one valid syntactic form:</p>
750
+
751
+ <pre><code>{% include <em>"partial_name"</em> %}</code></pre>
752
+
753
+ <p>The <em>include</em> tag accepts a <strong>String</strong> <em>partial_name</em> as an immediate value. It lexically includes the code of partial template <em>partial_name</em> in a newly created scope.</p>
754
+
755
+ <p>The <em>include</em> tag must not allow infinite recursion to happen. If such a condition is encountered, a compile-time error (<a href="#syntax-error">syntax error</a>) is signaled.</p>
756
+
757
+ <p>See also notes on <a href="#appendix-layouts">Layout implementation</a>.</p>
758
+
759
+ <h3 id="functions">6.2 Functions</h3>
760
+
761
+ <p>TODO</p>
762
+
763
+ <h2 id="appendix-layouts">Appendix A: Layouts</h2>
764
+
765
+ <p>TODO</p>
766
+
767
+ </body>
768
+ </html>