haparanda 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "sexp_processor"
4
+
5
+ module Haparanda
6
+ # Process the handlebars AST just to combine subsequent :content items
7
+ class ContentCombiner < SexpProcessor
8
+ def initialize
9
+ super
10
+
11
+ self.require_empty = false
12
+ end
13
+
14
+ def process_statements(expr)
15
+ statements = expr.sexp_body
16
+
17
+ statements = combine_contents(statements)
18
+ statements = statements.map { process(_1) }
19
+
20
+ s(:statements, *statements)
21
+ end
22
+
23
+ private
24
+
25
+ def combine_contents(statements)
26
+ return statements if statements.length < 2
27
+
28
+ prev = nil
29
+ result = []
30
+
31
+ statements.each do |item|
32
+ if prev
33
+ if item.sexp_type == :content
34
+ prev[1] += item[1]
35
+ else
36
+ result << item
37
+ prev = nil
38
+ end
39
+ else
40
+ result << item
41
+ prev = item if item.sexp_type == :content
42
+ end
43
+ end
44
+
45
+ result
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "content_combiner"
4
+ require_relative "whitespace_handler"
5
+
6
+ module Haparanda
7
+ # Process the handlebars AST just to combine subsequent :content items
8
+ class HandlebarsCompiler
9
+ def initialize(ignore_standalone: false, **)
10
+ @ignore_standalone = ignore_standalone
11
+ end
12
+
13
+ def process(expr)
14
+ expr = ContentCombiner.new.process(expr)
15
+ WhitespaceHandler.new(ignore_standalone: @ignore_standalone).process(expr)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,297 @@
1
+ #--
2
+ # DO NOT MODIFY!!!!
3
+ # This file is automatically generated by rex 1.0.8
4
+ # from lexical definition file "lib/haparanda/handlebars_lexer.rex".
5
+ #++
6
+
7
+ # Based on src/handlebars.l in handlebars-parser.
8
+ #
9
+ # src/handlebars.l in handlebars-parser is covered by the ICS license. See README.md
10
+ # for details.
11
+
12
+ class Haparanda::HandlebarsLexer
13
+ require 'strscan'
14
+
15
+ class ScanError < StandardError ; end
16
+
17
+ attr_reader :lineno
18
+ attr_reader :filename
19
+ attr_accessor :state
20
+
21
+ def scan_setup(str)
22
+ @ss = StringScanner.new(str)
23
+ @lineno = 1
24
+ @state = nil
25
+ end
26
+
27
+ def action
28
+ yield
29
+ end
30
+
31
+ def scan_str(str)
32
+ scan_setup(str)
33
+ do_parse
34
+ end
35
+ alias :scan :scan_str
36
+
37
+ def load_file( filename )
38
+ @filename = filename
39
+ File.open(filename, "r") do |f|
40
+ scan_setup(f.read)
41
+ end
42
+ end
43
+
44
+ def scan_file( filename )
45
+ load_file(filename)
46
+ do_parse
47
+ end
48
+
49
+
50
+ def next_token
51
+ return if @ss.eos?
52
+
53
+ # skips empty actions
54
+ until token = _next_token or @ss.eos?; end
55
+ token
56
+ end
57
+
58
+ def _next_token
59
+ text = @ss.peek(1)
60
+ @lineno += 1 if text == "\n"
61
+ token = case @state
62
+ when nil
63
+ case
64
+ when (text = @ss.scan(/\n/))
65
+ action { [:CONTENT, text] }
66
+
67
+ when (text = @ss.scan(/[^\x00\n]*?(?={{)/))
68
+ action {
69
+ if(text.slice(-2, 2) === "\\\\")
70
+ text = text[0..-2]
71
+ @state = :MU
72
+ elsif(text.slice(-1) === "\\")
73
+ text = text[0..-2]
74
+ @state = :EMU
75
+ else
76
+ @state = :MU
77
+ end
78
+ [:CONTENT, text] unless(text.empty?)
79
+ }
80
+
81
+
82
+ when (text = @ss.scan(/[^\x00\n]+/))
83
+ action { [:CONTENT, text] }
84
+
85
+ when (text = @ss.scan(/<INITIAL,mu><<EOF>>/))
86
+ action { [:EOF, text] }
87
+
88
+
89
+ else
90
+ text = @ss.string[@ss.pos .. -1]
91
+ raise ScanError, "can not match: '" + text + "'"
92
+ end # if
93
+
94
+ when :EMU
95
+ case
96
+ when (text = @ss.scan(/{{/))
97
+ action { @state = nil; [:CONTENT, text] }
98
+
99
+
100
+ else
101
+ text = @ss.string[@ss.pos .. -1]
102
+ raise ScanError, "can not match: '" + text + "'"
103
+ end # if
104
+
105
+ when :RAW
106
+ case
107
+ when (text = @ss.scan(/{{{{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+}}}}/))
108
+ action {
109
+ ending_delimiter = text[5...-4]
110
+ if ending_delimiter == @raw_delimiter
111
+ @state = nil
112
+ [:END_RAW_BLOCK, ending_delimiter]
113
+ else
114
+ [:CONTENT, text]
115
+ end
116
+ }
117
+
118
+
119
+ when (text = @ss.scan(/[^\x00]+?(?={{{{)/))
120
+ action { [:CONTENT, text] }
121
+
122
+
123
+ else
124
+ text = @ss.string[@ss.pos .. -1]
125
+ raise ScanError, "can not match: '" + text + "'"
126
+ end # if
127
+
128
+ when :COM
129
+ case
130
+ when (text = @ss.scan(/[\s\S]*?\-\-~?}}/))
131
+ action { @state = nil; [:COMMENT, text] }
132
+
133
+
134
+ else
135
+ text = @ss.string[@ss.pos .. -1]
136
+ raise ScanError, "can not match: '" + text + "'"
137
+ end # if
138
+
139
+ when :MU
140
+ case
141
+ when (text = @ss.scan(/\(/))
142
+ action { [:OPEN_SEXPR, text] }
143
+
144
+ when (text = @ss.scan(/\)/))
145
+ action { [:CLOSE_SEXPR, text] }
146
+
147
+ when (text = @ss.scan(/{{{{/))
148
+ action {
149
+ @expect_raw_block_id = true
150
+ [:OPEN_RAW_BLOCK, text]
151
+ }
152
+
153
+
154
+ when (text = @ss.scan(/}}}}/))
155
+ action {
156
+ @state = :RAW
157
+ [:CLOSE_RAW_BLOCK, text]
158
+ }
159
+
160
+
161
+ when (text = @ss.scan(/{{~?>/))
162
+ action { [:OPEN_PARTIAL, text] }
163
+
164
+ when (text = @ss.scan(/{{~?#>/))
165
+ action { [:OPEN_PARTIAL_BLOCK, text] }
166
+
167
+ when (text = @ss.scan(/{{~?#\*?/))
168
+ action { [:OPEN_BLOCK, text] }
169
+
170
+ when (text = @ss.scan(/{{~?\//))
171
+ action { [:OPEN_ENDBLOCK, text] }
172
+
173
+ when (text = @ss.scan(/{{~?\^\s*~?}}/))
174
+ action { @state = nil; [:INVERSE, text] }
175
+
176
+ when (text = @ss.scan(/{{~?\s*else\s*~?}}/))
177
+ action { @state = nil; [:INVERSE, text] }
178
+
179
+ when (text = @ss.scan(/{{~?\^/))
180
+ action { [:OPEN_INVERSE, text] }
181
+
182
+ when (text = @ss.scan(/{{~?\s*else/))
183
+ action { [:OPEN_INVERSE_CHAIN, text] }
184
+
185
+ when (text = @ss.scan(/{{~?{/))
186
+ action { [:OPEN_UNESCAPED, text] }
187
+
188
+ when (text = @ss.scan(/{{~?&/))
189
+ action { [:OPEN, text] }
190
+
191
+ when (text = @ss.scan(/{{~?!--/))
192
+ action { @state = :COM; [:COMMENT, text] }
193
+
194
+ when (text = @ss.scan(/{{~?![\s\S]*?}}/))
195
+ action { @state = nil; [:COMMENT, text] }
196
+
197
+ when (text = @ss.scan(/{{~?\*?/))
198
+ action { [:OPEN, text] }
199
+
200
+ when (text = @ss.scan(/=/))
201
+ action { [:EQUALS, text] }
202
+
203
+ when (text = @ss.scan(/\.\./))
204
+ action { [:ID, text] }
205
+
206
+ when (text = @ss.scan(/\.(?=[=~}\s\/.)|])/))
207
+ action { [:ID, text] }
208
+
209
+ when (text = @ss.scan(/[\/.]/))
210
+ action { [:SEP, text] }
211
+
212
+ when (text = @ss.scan(/\s+/))
213
+ ;
214
+
215
+ when (text = @ss.scan(/}~?}}/))
216
+ action { @state = nil; [:CLOSE_UNESCAPED, text] }
217
+
218
+ when (text = @ss.scan(/~?}}/))
219
+ action { @state = nil; [:CLOSE, text] }
220
+
221
+ when (text = @ss.scan(/"(\\"|[^"])*"/))
222
+ action { text = handle_stringescape(text, '"'); [:STRING, text] }
223
+
224
+ when (text = @ss.scan(/'(\\'|[^'])*'/))
225
+ action { text = handle_stringescape(text, "'"); [:STRING, text] }
226
+
227
+ when (text = @ss.scan(/@/))
228
+ action { [:DATA, text] }
229
+
230
+ when (text = @ss.scan(/true(?=[~}\s)])/))
231
+ action { [:BOOLEAN, text] }
232
+
233
+ when (text = @ss.scan(/false(?=[~}\s)])/))
234
+ action { [:BOOLEAN, text] }
235
+
236
+ when (text = @ss.scan(/undefined(?=[~}\s)])/))
237
+ action { [:UNDEFINED, text] }
238
+
239
+ when (text = @ss.scan(/null(?=[~}\s)])/))
240
+ action { [:NULL, text] }
241
+
242
+ when (text = @ss.scan(/\-?[0-9]+(?:\.[0-9]+)?(?=[~}\s)])/))
243
+ action { [:NUMBER, text] }
244
+
245
+ when (text = @ss.scan(/as\s+\|/))
246
+ action { [:OPEN_BLOCK_PARAMS, text] }
247
+
248
+ when (text = @ss.scan(/\|/))
249
+ action { [:CLOSE_BLOCK_PARAMS, text] }
250
+
251
+ when (text = @ss.scan(/[^\s!"\#%-,\.\/;->@\[-\^`\{-~]+(?=[=~}\s\/.)|])=/))
252
+ action { [:KEY_ASSIGN, text[0..-2]] }
253
+
254
+ when (text = @ss.scan(/[^\s!"\#%-,\.\/;->@\[-\^`\{-~]+(?=[=~}\s\/.)|])/))
255
+ action {
256
+ if @expect_raw_block_id
257
+ @raw_delimiter = text
258
+ @expect_raw_block_id = false
259
+ end
260
+ [:ID, text]
261
+ }
262
+
263
+
264
+ when (text = @ss.scan(/\[(\\\]|[^\]])*\]/))
265
+ action { text = text.gsub(/\\([\\\]])/, '\1'); [:ID, text] }
266
+
267
+ when (text = @ss.scan(/./))
268
+ action { [:INVALID, text] }
269
+
270
+
271
+ else
272
+ text = @ss.string[@ss.pos .. -1]
273
+ raise ScanError, "can not match: '" + text + "'"
274
+ end # if
275
+
276
+ else
277
+ raise ScanError, "undefined state: '" + state.to_s + "'"
278
+ end # case state
279
+ token
280
+ end # def _next_token
281
+
282
+ def do_parse
283
+ result = []
284
+ while token = next_token
285
+ result << token
286
+ end
287
+ result
288
+ end
289
+ def handle_stringescape(str, delimiter)
290
+ case delimiter
291
+ when '"', "'"
292
+ str[1..-2].gsub(%r{\\(.)}, "\\1")
293
+ else
294
+ raise NotImplementedError
295
+ end
296
+ end
297
+ end # class
@@ -0,0 +1,135 @@
1
+ # Based on src/handlebars.l in handlebars-parser.
2
+ #
3
+ # src/handlebars.l in handlebars-parser is covered by the ICS license. See README.md
4
+ # for details.
5
+
6
+ class Haparanda::HandlebarsLexer
7
+ %x mu emu com raw
8
+
9
+ inner
10
+
11
+ def do_parse
12
+ result = []
13
+ while token = next_token
14
+ result << token
15
+ end
16
+ result
17
+ end
18
+
19
+ def handle_stringescape(str, delimiter)
20
+ case delimiter
21
+ when '"', "'"
22
+ str[1..-2].gsub(%r{\\(.)}, "\\1")
23
+ else
24
+ raise NotImplementedError
25
+ end
26
+ end
27
+
28
+ macro
29
+ LEFT_STRIP ~
30
+ RIGHT_STRIP ~
31
+
32
+ LOOKAHEAD [=~}\s\/.)|]
33
+ LIT_LOOKAHEAD [~}\s)]
34
+
35
+ # ID is the inverse of control characters.
36
+ # Control characters ranges:
37
+ # [\s] Whitespace
38
+ # [!"#%-,\./] !, ", #, %, &, ', (, ), *, +, ,, ., /, Exceptions in range: $, -
39
+ # [;->@] ;, <, =, >, @, Exceptions in range: :, ?
40
+ # [\[-\^`] [, \, ], ^, `, Exceptions in range: _
41
+ # [\{-~] {, |, }, ~
42
+ ID [^\s!"\#%-,\.\/;->@\[-\^`\{-~]+(?={LOOKAHEAD})
43
+
44
+ rule
45
+
46
+ \n { [:CONTENT, text] }
47
+ [^\x00\n]*?(?={{) {
48
+ if(text.slice(-2, 2) === "\\\\")
49
+ text = text[0..-2]
50
+ @state = :MU
51
+ elsif(text.slice(-1) === "\\")
52
+ text = text[0..-2]
53
+ @state = :EMU
54
+ else
55
+ @state = :MU
56
+ end
57
+ [:CONTENT, text] unless(text.empty?)
58
+ }
59
+
60
+ [^\x00\n]+ { [:CONTENT, text] }
61
+
62
+ # consume escaped mustache
63
+ :EMU {{ { @state = nil; [:CONTENT, text] }
64
+
65
+ # End of raw block if delimiter text matches opening text
66
+ :RAW {{{{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+}}}} {
67
+ ending_delimiter = text[5...-4]
68
+ if ending_delimiter == @raw_delimiter
69
+ @state = nil
70
+ [:END_RAW_BLOCK, ending_delimiter]
71
+ else
72
+ [:CONTENT, text]
73
+ end
74
+ }
75
+ :RAW [^\x00]+?(?={{{{) { [:CONTENT, text] }
76
+
77
+ :COM [\s\S]*?\-\-{RIGHT_STRIP}?}} { @state = nil; [:COMMENT, text] }
78
+
79
+ :MU \( { [:OPEN_SEXPR, text] }
80
+ :MU \) { [:CLOSE_SEXPR, text] }
81
+
82
+ :MU {{{{ {
83
+ @expect_raw_block_id = true
84
+ [:OPEN_RAW_BLOCK, text]
85
+ }
86
+ :MU }}}} {
87
+ @state = :RAW
88
+ [:CLOSE_RAW_BLOCK, text]
89
+ }
90
+ :MU {{{LEFT_STRIP}?> { [:OPEN_PARTIAL, text] }
91
+ :MU {{{LEFT_STRIP}?#> { [:OPEN_PARTIAL_BLOCK, text] }
92
+ :MU {{{LEFT_STRIP}?#\*? { [:OPEN_BLOCK, text] }
93
+ :MU {{{LEFT_STRIP}?\/ { [:OPEN_ENDBLOCK, text] }
94
+ :MU {{{LEFT_STRIP}?\^\s*{RIGHT_STRIP}?}} { @state = nil; [:INVERSE, text] }
95
+ :MU {{{LEFT_STRIP}?\s*else\s*{RIGHT_STRIP}?}} { @state = nil; [:INVERSE, text] }
96
+ :MU {{{LEFT_STRIP}?\^ { [:OPEN_INVERSE, text] }
97
+ :MU {{{LEFT_STRIP}?\s*else { [:OPEN_INVERSE_CHAIN, text] }
98
+ :MU {{{LEFT_STRIP}?{ { [:OPEN_UNESCAPED, text] }
99
+ :MU {{{LEFT_STRIP}?& { [:OPEN, text] }
100
+ :MU {{{LEFT_STRIP}?!-- { @state = :COM; [:COMMENT, text] }
101
+ :MU {{{LEFT_STRIP}?![\s\S]*?}} { @state = nil; [:COMMENT, text] }
102
+ :MU {{{LEFT_STRIP}?\*? { [:OPEN, text] }
103
+
104
+ :MU = { [:EQUALS, text] }
105
+ :MU \.\. { [:ID, text] }
106
+ :MU \.(?={LOOKAHEAD}) { [:ID, text] }
107
+ :MU [\/.] { [:SEP, text] }
108
+ :MU \s+ // ignore whitespace
109
+ :MU }{RIGHT_STRIP}?}} { @state = nil; [:CLOSE_UNESCAPED, text] }
110
+ :MU {RIGHT_STRIP}?}} { @state = nil; [:CLOSE, text] }
111
+ :MU "(\\"|[^"])*" { text = handle_stringescape(text, '"'); [:STRING, text] }
112
+ :MU '(\\'|[^'])*' { text = handle_stringescape(text, "'"); [:STRING, text] }
113
+ :MU @ { [:DATA, text] }
114
+ :MU true(?={LIT_LOOKAHEAD}) { [:BOOLEAN, text] }
115
+ :MU false(?={LIT_LOOKAHEAD}) { [:BOOLEAN, text] }
116
+ :MU undefined(?={LIT_LOOKAHEAD}) { [:UNDEFINED, text] }
117
+ :MU null(?={LIT_LOOKAHEAD}) { [:NULL, text] }
118
+ :MU \-?[0-9]+(?:\.[0-9]+)?(?={LIT_LOOKAHEAD}) { [:NUMBER, text] }
119
+ :MU as\s+\| { [:OPEN_BLOCK_PARAMS, text] }
120
+ :MU \| { [:CLOSE_BLOCK_PARAMS, text] }
121
+
122
+ :MU {ID}= { [:KEY_ASSIGN, text[0..-2]] }
123
+ :MU {ID} {
124
+ if @expect_raw_block_id
125
+ @raw_delimiter = text
126
+ @expect_raw_block_id = false
127
+ end
128
+ [:ID, text]
129
+ }
130
+
131
+ :MU \[(\\\]|[^\]])*\] { text = text.gsub(/\\([\\\]])/, '\1'); [:ID, text] }
132
+ :MU . { [:INVALID, text] }
133
+
134
+ <INITIAL,mu><<EOF>> { [:EOF, text] }
135
+ end