riml 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/riml +9 -7
- data/lib/ast_rewriter.rb +21 -20
- data/lib/class_map.rb +4 -0
- data/lib/compiler.rb +26 -22
- data/lib/constants.rb +1 -2
- data/lib/grammar.y +9 -9
- data/lib/lexer.rb +45 -29
- data/lib/nodes.rb +20 -14
- data/lib/parser.rb +961 -976
- data/lib/riml.rb +8 -4
- data/lib/walker.rb +1 -1
- data/version.rb +2 -2
- metadata +4 -4
data/bin/riml
CHANGED
@@ -22,12 +22,14 @@ module Riml
|
|
22
22
|
opts.separator ""
|
23
23
|
opts.separator "Specific options:"
|
24
24
|
|
25
|
-
opts.on("-c", "--compile
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
opts.on("-c", "--compile FILES", Array, "Compile riml file(s) to VimL.") do |files|
|
26
|
+
files.each do |f|
|
27
|
+
if File.exists?(f)
|
28
|
+
options.compile_files << f
|
29
|
+
else
|
30
|
+
warn "Couldn't find file #{f.inspect}."
|
31
|
+
exit 1
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -65,7 +67,7 @@ module Riml
|
|
65
67
|
options = Options.parse(ARGV)
|
66
68
|
|
67
69
|
if options.stdio
|
68
|
-
puts Riml.compile($stdin.
|
70
|
+
puts Riml.compile($stdin.read)
|
69
71
|
elsif options.compile_files.any?
|
70
72
|
Riml.compile_files(*options.compile_files)
|
71
73
|
end
|
data/lib/ast_rewriter.rb
CHANGED
@@ -7,25 +7,26 @@ module Riml
|
|
7
7
|
include Riml::Constants
|
8
8
|
|
9
9
|
attr_accessor :ast
|
10
|
-
|
10
|
+
attr_reader :classes
|
11
|
+
|
12
|
+
def initialize(ast = nil, classes = nil)
|
11
13
|
@ast = ast
|
14
|
+
@classes = classes || ClassMap.new
|
12
15
|
end
|
13
16
|
|
14
|
-
# Map of {"ClassName" => ClassDefinitionNode}
|
15
|
-
# Can also query object for superclass of a named class, etc...
|
16
|
-
#
|
17
|
-
# Ex : classes["SomeClass"].superclass_name => "SomeClassBase"
|
18
|
-
def classes
|
19
|
-
@@classes ||= ClassMap.new
|
20
|
-
end
|
21
17
|
|
22
18
|
def rewrite
|
23
19
|
establish_parents(ast)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
rewriters = [
|
21
|
+
StrictEqualsComparisonOperator.new(ast, classes),
|
22
|
+
VarEqualsComparisonOperator.new(ast, classes),
|
23
|
+
ClassDefinitionToFunctions.new(ast, classes),
|
24
|
+
ObjectInstantiationToCall.new(ast, classes),
|
25
|
+
CallToExplicitCall.new(ast, classes)
|
26
|
+
]
|
27
|
+
rewriters.each do |rewriter|
|
28
|
+
rewriter.rewrite_on_match
|
29
|
+
end
|
29
30
|
ast
|
30
31
|
end
|
31
32
|
|
@@ -100,7 +101,7 @@ module Riml
|
|
100
101
|
classes[node.name] = node
|
101
102
|
|
102
103
|
name, expressions = node.name, node.expressions
|
103
|
-
InsertInitializeMethod.new(node).rewrite_on_match
|
104
|
+
InsertInitializeMethod.new(node, classes).rewrite_on_match
|
104
105
|
constructor = node.constructor
|
105
106
|
constructor.scope_modifier = 'g:' unless constructor.scope_modifier
|
106
107
|
constructor.name = node.constructor_name
|
@@ -110,8 +111,8 @@ module Riml
|
|
110
111
|
AssignNode.new('=', GetVariableNode.new(nil, dict_name), DictionaryNode.new({}))
|
111
112
|
)
|
112
113
|
|
113
|
-
SuperToObjectExtension.new(constructor, node).rewrite_on_match
|
114
|
-
MethodToNestedFunction.new(node, constructor, dict_name).rewrite_on_match
|
114
|
+
SuperToObjectExtension.new(constructor, classes, node).rewrite_on_match
|
115
|
+
MethodToNestedFunction.new(node, classes, constructor, dict_name).rewrite_on_match
|
115
116
|
SelfToDictName.new(dict_name).rewrite_on_match(constructor)
|
116
117
|
|
117
118
|
constructor.expressions.push(
|
@@ -122,8 +123,8 @@ module Riml
|
|
122
123
|
|
123
124
|
class MethodToNestedFunction < AST_Rewriter
|
124
125
|
attr_reader :constructor, :dict_name
|
125
|
-
def initialize(class_node, constructor, dict_name)
|
126
|
-
super(class_node)
|
126
|
+
def initialize(class_node, classes, constructor, dict_name)
|
127
|
+
super(class_node, classes)
|
127
128
|
@dict_name, @constructor = dict_name, constructor
|
128
129
|
end
|
129
130
|
|
@@ -190,8 +191,8 @@ module Riml
|
|
190
191
|
|
191
192
|
class SuperToObjectExtension < AST_Rewriter
|
192
193
|
attr_reader :class_node
|
193
|
-
def initialize(constructor, class_node)
|
194
|
-
super(constructor)
|
194
|
+
def initialize(constructor, classes, class_node)
|
195
|
+
super(constructor, classes)
|
195
196
|
@class_node = class_node
|
196
197
|
end
|
197
198
|
|
data/lib/class_map.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
module Riml
|
2
2
|
class ClassNotFound < NameError; end
|
3
3
|
|
4
|
+
# Map of {"ClassName" => ClassDefinitionNode}
|
5
|
+
# Can also query object for superclass of a named class, etc...
|
6
|
+
#
|
7
|
+
# Ex : classes["SomeClass"].superclass_name => "SomeClassBase"
|
4
8
|
class ClassMap
|
5
9
|
def initialize
|
6
10
|
@map = {}
|
data/lib/compiler.rb
CHANGED
@@ -6,7 +6,7 @@ module Riml
|
|
6
6
|
|
7
7
|
# Base abstract visitor
|
8
8
|
class Visitor
|
9
|
-
|
9
|
+
attr_writer :propagate_up_tree
|
10
10
|
attr_reader :value
|
11
11
|
|
12
12
|
def initialize(options={})
|
@@ -136,7 +136,7 @@ module Riml
|
|
136
136
|
when Numeric
|
137
137
|
node.value
|
138
138
|
when String
|
139
|
-
StringNode === node ? string_surround(node) : node.value
|
139
|
+
StringNode === node ? string_surround(node) : node.value
|
140
140
|
when Array
|
141
141
|
node.value.each {|n| n.parent_node = node}
|
142
142
|
'[' <<
|
@@ -145,7 +145,7 @@ module Riml
|
|
145
145
|
n.compiled_output
|
146
146
|
end.join(', ') << ']'
|
147
147
|
when Hash
|
148
|
-
node.value.each {|k_n, v_n| k_n.parent_node, v_n.parent_node =
|
148
|
+
node.value.each {|k_n, v_n| k_n.parent_node, v_n.parent_node = node, node}
|
149
149
|
'{' <<
|
150
150
|
node.value.map do |k,v|
|
151
151
|
k.accept(visitor_for_node(k))
|
@@ -185,11 +185,9 @@ module Riml
|
|
185
185
|
ContinueNodeVisitor = LiteralNodeVisitor
|
186
186
|
BreakNodeVisitor = LiteralNodeVisitor
|
187
187
|
|
188
|
-
class
|
188
|
+
class ListUnpackNodeVisitor < ListNodeVisitor
|
189
189
|
def compile(node)
|
190
|
-
node.
|
191
|
-
node.string_node.accept(StringNodeVisitor.new)
|
192
|
-
node.compiled_output
|
190
|
+
node.compiled_output = super.reverse.sub(',', ';').reverse
|
193
191
|
end
|
194
192
|
end
|
195
193
|
|
@@ -223,15 +221,16 @@ module Riml
|
|
223
221
|
# myVariable = "override riml default scoping"
|
224
222
|
node.scope_modifier = "" if node.scope_modifier == "n:"
|
225
223
|
return node.scope_modifier if node.scope_modifier
|
226
|
-
node.scope_modifier =
|
224
|
+
node.scope_modifier = scope_modifier_for_node(node)
|
227
225
|
end
|
228
226
|
|
229
227
|
private
|
230
|
-
def
|
228
|
+
def scope_modifier_for_node(node)
|
231
229
|
if node.scope
|
232
230
|
return "a:" if node.scope.argument_variable_names.include?(node.name)
|
233
|
-
return ""
|
231
|
+
return "" unless node.is_a?(CallNode)
|
234
232
|
end
|
233
|
+
return "" if (node.is_a?(CallNode) || node.is_a?(DefNode)) && node.autoload?
|
235
234
|
"s:"
|
236
235
|
end
|
237
236
|
end
|
@@ -265,7 +264,9 @@ module Riml
|
|
265
264
|
|
266
265
|
private
|
267
266
|
def check_for_splat_match!(node, splat)
|
268
|
-
if
|
267
|
+
# if `function doIt(*options)`, then:
|
268
|
+
# *options OR options in function body becomes `a:000`
|
269
|
+
if [ splat, splat[1..-1] ].include?(node.name)
|
269
270
|
node.scope_modifier = "a:"
|
270
271
|
node.name = '000'
|
271
272
|
end
|
@@ -333,17 +334,17 @@ module Riml
|
|
333
334
|
end
|
334
335
|
end
|
335
336
|
|
336
|
-
class DefNodeVisitor <
|
337
|
+
class DefNodeVisitor < ScopedVisitor
|
337
338
|
def visit(node)
|
338
339
|
setup_local_scope_for_descendants(node)
|
339
340
|
super
|
340
341
|
end
|
341
342
|
|
342
343
|
def compile(node)
|
343
|
-
|
344
|
+
set_modifier(node)
|
344
345
|
bang = node.bang
|
345
346
|
params = process_parameters!(node)
|
346
|
-
declaration = "function#{bang} #{
|
347
|
+
declaration = "function#{bang} #{node.scope_modifier}"
|
347
348
|
declaration <<
|
348
349
|
if node.name.respond_to?(:variable)
|
349
350
|
node.name.accept(visitor_for_node(node.name))
|
@@ -474,7 +475,9 @@ module Riml
|
|
474
475
|
end
|
475
476
|
node.compiled_output << 'source'
|
476
477
|
compile_arguments(node)
|
477
|
-
node.compiled_output.gsub(/['"]/, '')
|
478
|
+
node.compiled_output.gsub!(/['"]/, '')
|
479
|
+
node.compiled_output.sub!('.riml', '.vim')
|
480
|
+
node.compiled_output
|
478
481
|
end
|
479
482
|
end
|
480
483
|
|
@@ -514,9 +517,9 @@ module Riml
|
|
514
517
|
end
|
515
518
|
|
516
519
|
class TryNodeVisitor < Visitor
|
517
|
-
# try_block, catch_nodes,
|
520
|
+
# try_block, catch_nodes, finally_block
|
518
521
|
def compile(node)
|
519
|
-
try, catches,
|
522
|
+
try, catches, finally = node.try_block, node.catch_nodes, node.finally_block
|
520
523
|
node.compiled_output = "try\n"
|
521
524
|
try.accept(visitor_for_node(try, :propagate_up_tree => false))
|
522
525
|
try.compiled_output.each_line do |line|
|
@@ -530,10 +533,10 @@ module Riml
|
|
530
533
|
end
|
531
534
|
end if catches
|
532
535
|
|
533
|
-
if
|
536
|
+
if finally
|
534
537
|
node.compiled_output << "finally\n"
|
535
|
-
|
536
|
-
|
538
|
+
finally.accept(visitor_for_node(finally, :propagate_up_tree => false))
|
539
|
+
finally.compiled_output.each_line do |line|
|
537
540
|
node.compiled_output << node.indent + line
|
538
541
|
end
|
539
542
|
end
|
@@ -603,6 +606,9 @@ module Riml
|
|
603
606
|
end
|
604
607
|
end
|
605
608
|
|
609
|
+
# root node has access to compiler instance in order to append to
|
610
|
+
# the compiler's `compile_queue`. This happens when another file is
|
611
|
+
# sourced using `riml_source`.
|
606
612
|
module CompilerAccessible
|
607
613
|
attr_accessor :current_compiler
|
608
614
|
end
|
@@ -613,8 +619,6 @@ module Riml
|
|
613
619
|
|
614
620
|
# compiles nodes into output code
|
615
621
|
def compile(root_node)
|
616
|
-
# so we can compile concurrently, as some nodes have access to the
|
617
|
-
# compiler instance itself
|
618
622
|
root_node.extend CompilerAccessible
|
619
623
|
root_node.current_compiler = self
|
620
624
|
root_node.accept(NodesVisitor.new)
|
data/lib/constants.rb
CHANGED
@@ -7,8 +7,7 @@ module Riml
|
|
7
7
|
VIML_END_KEYWORDS =
|
8
8
|
%w(endfunction endif endwhile endfor endtry)
|
9
9
|
RIML_KEYWORDS =
|
10
|
-
%w(def defm super end then unless until true false nil
|
11
|
-
class new)
|
10
|
+
%w(def defm super end then unless until true false nil class new)
|
12
11
|
DEFINE_KEYWORDS = %w(def def! defm defm! function function!)
|
13
12
|
KEYWORDS = VIML_KEYWORDS + VIML_END_KEYWORDS + RIML_KEYWORDS
|
14
13
|
|
data/lib/grammar.y
CHANGED
@@ -2,7 +2,7 @@ class Riml::Parser
|
|
2
2
|
|
3
3
|
token IF ELSE ELSEIF THEN UNLESS END
|
4
4
|
token WHILE UNTIL BREAK CONTINUE
|
5
|
-
token TRY CATCH
|
5
|
+
token TRY CATCH FINALLY
|
6
6
|
token FOR IN
|
7
7
|
token DEF DEF_BANG SPLAT CALL BUILTIN_COMMAND # such as echo "hi"
|
8
8
|
token CLASS NEW DEFM DEFM_BANG SUPER RIML_COMMAND
|
@@ -10,7 +10,7 @@ token RETURN
|
|
10
10
|
token NEWLINE
|
11
11
|
token NUMBER
|
12
12
|
token STRING_D STRING_S # single- and double-quoted
|
13
|
-
token
|
13
|
+
token EX_LITERAL
|
14
14
|
token REGEXP
|
15
15
|
token TRUE FALSE NIL
|
16
16
|
token LET UNLET UNLET_BANG IDENTIFIER
|
@@ -79,7 +79,6 @@ rule
|
|
79
79
|
| VariableRetrieval { result = val[0] }
|
80
80
|
| Literal { result = val[0] }
|
81
81
|
| Call { result = val[0] }
|
82
|
-
| Heredoc { result = val[0] }
|
83
82
|
| Ternary { result = val[0] }
|
84
83
|
| ObjectInstantiation { result = val[0] }
|
85
84
|
| BinaryOperator { result = val[0] }
|
@@ -114,10 +113,6 @@ rule
|
|
114
113
|
| STRING_D { result = StringNode.new(val[0], :d) }
|
115
114
|
;
|
116
115
|
|
117
|
-
Heredoc:
|
118
|
-
HEREDOC String { result = HeredocNode.new(val[0], val[1]) }
|
119
|
-
;
|
120
|
-
|
121
116
|
Regexp:
|
122
117
|
REGEXP { result = RegexpNode.new(val[0]) }
|
123
118
|
;
|
@@ -130,6 +125,10 @@ rule
|
|
130
125
|
ListLiteral { result = ListNode.new(val[0]) }
|
131
126
|
;
|
132
127
|
|
128
|
+
ListUnpack:
|
129
|
+
'[' ListItems ';' ValueExpression ']' { result = ListUnpackNode.new(val[1] << val[3]) }
|
130
|
+
;
|
131
|
+
|
133
132
|
ListLiteral:
|
134
133
|
'[' ListItems ']' { result = val[1] }
|
135
134
|
| '[' ListItems ',' ']' { result = val[1] }
|
@@ -299,6 +298,7 @@ rule
|
|
299
298
|
VariableRetrieval { result = val[0] }
|
300
299
|
| DictGet { result = val[0] }
|
301
300
|
| List { result = val[0] }
|
301
|
+
| ListUnpack { result = val[0] }
|
302
302
|
| ListOrDictGet { result = val[0] }
|
303
303
|
;
|
304
304
|
|
@@ -401,7 +401,7 @@ rule
|
|
401
401
|
Try:
|
402
402
|
TRY Block END { result = TryNode.new(val[1], nil, nil) }
|
403
403
|
| TRY Block Catch END { result = TryNode.new(val[1], val[2], nil) }
|
404
|
-
| TRY Block Catch
|
404
|
+
| TRY Block Catch FINALLY Block END { result = TryNode.new(val[1], val[2], val[4]) }
|
405
405
|
;
|
406
406
|
|
407
407
|
Catch:
|
@@ -480,7 +480,7 @@ end
|
|
480
480
|
raise Riml::ParseError, "line #{@lexer.lineno}: #{e.message}"
|
481
481
|
end
|
482
482
|
|
483
|
-
@ast_rewriter
|
483
|
+
@ast_rewriter ||= ast_rewriter
|
484
484
|
return ast unless @ast_rewriter
|
485
485
|
@ast_rewriter.ast = ast
|
486
486
|
@ast_rewriter.rewrite
|
data/lib/lexer.rb
CHANGED
@@ -7,12 +7,18 @@ module Riml
|
|
7
7
|
|
8
8
|
SINGLE_LINE_COMMENT_REGEX = /\A\s*"(.*)$/
|
9
9
|
OPERATOR_REGEX = /\A#{Regexp.union(['||', '&&', '===', '+=', '-=', '.='] + COMPARISON_OPERATORS)}/
|
10
|
+
INTERPOLATION_REGEX = /\A"(.*?)(\#\{(.*?)\})(.*?)"/m
|
11
|
+
INTERPOLATION_SPLIT_REGEX = /(\#{.*?})/m
|
10
12
|
|
11
13
|
attr_reader :tokens, :prev_token, :lineno, :chunk
|
12
14
|
|
13
15
|
def initialize(code)
|
14
16
|
@code = code
|
15
17
|
@code.chomp!
|
18
|
+
set_start_state!
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_start_state!
|
16
22
|
@i = 0 # number of characters consumed
|
17
23
|
@token_buf = []
|
18
24
|
@tokens = []
|
@@ -21,15 +27,15 @@ module Riml
|
|
21
27
|
@current_indent = 0
|
22
28
|
@indent_pending = false
|
23
29
|
@dedent_pending = false
|
24
|
-
@
|
30
|
+
@one_line_conditional_end_pending = false
|
25
31
|
@splat_allowed = false
|
32
|
+
@in_function_declaration = false
|
26
33
|
end
|
27
34
|
|
28
35
|
def tokenize
|
29
|
-
|
30
|
-
while
|
31
|
-
|
32
|
-
@tokens << new_token unless new_token.nil?
|
36
|
+
set_start_state!
|
37
|
+
while (token = next_token) != nil
|
38
|
+
@tokens << token
|
33
39
|
end
|
34
40
|
@tokens
|
35
41
|
end
|
@@ -38,7 +44,7 @@ module Riml
|
|
38
44
|
while @token_buf.empty? && more_code_to_tokenize?
|
39
45
|
tokenize_chunk(get_new_chunk)
|
40
46
|
end
|
41
|
-
if
|
47
|
+
if !@token_buf.empty?
|
42
48
|
return @prev_token = @token_buf.shift
|
43
49
|
end
|
44
50
|
check_indentation
|
@@ -65,7 +71,7 @@ module Riml
|
|
65
71
|
@i += splat_var.size
|
66
72
|
@token_buf << [:SCOPE_MODIFIER, 'a:'] << [:IDENTIFIER, splat_var[2..-1]]
|
67
73
|
# the 'n' scope modifier is added by riml
|
68
|
-
elsif scope_modifier = chunk[/\A([bwtglsavn]:)[\
|
74
|
+
elsif scope_modifier = chunk[/\A([bwtglsavn]:)[\w]/]
|
69
75
|
@i += 2
|
70
76
|
@token_buf << [:SCOPE_MODIFIER, $1]
|
71
77
|
elsif scope_modifier_literal = chunk[/\A([bwtglsavn]:)/]
|
@@ -85,9 +91,6 @@ module Riml
|
|
85
91
|
old_identifier = identifier.dup
|
86
92
|
identifier.sub!(/function/, "def")
|
87
93
|
@i += (old_identifier.size - identifier.size)
|
88
|
-
elsif identifier == 'finally'
|
89
|
-
identifier = 'ensure'
|
90
|
-
@i += 1 # diff b/t the two string lengths
|
91
94
|
elsif VIML_END_KEYWORDS.include? identifier
|
92
95
|
old_identifier = identifier.dup
|
93
96
|
identifier = 'end'
|
@@ -137,17 +140,10 @@ module Riml
|
|
137
140
|
elsif decimal = chunk[/\A[0-9]+(\.[0-9]+)?/]
|
138
141
|
@token_buf << [:NUMBER, decimal.to_s]
|
139
142
|
@i += decimal.size
|
140
|
-
elsif interpolation = chunk[
|
141
|
-
# "#{
|
142
|
-
|
143
|
-
|
144
|
-
@token_buf << ['.', '.']
|
145
|
-
end
|
146
|
-
@token_buf << [:IDENTIFIER, $3]
|
147
|
-
unless $4.empty?
|
148
|
-
@token_buf << ['.', '.']
|
149
|
-
@token_buf << [ :STRING_D, " #{$4[1..-1]}" ]
|
150
|
-
end
|
143
|
+
elsif interpolation = chunk[INTERPOLATION_REGEX]
|
144
|
+
# "hey there, #{name}" = "hey there, " . name
|
145
|
+
parts = interpolation[1...-1].split(INTERPOLATION_SPLIT_REGEX)
|
146
|
+
handle_interpolation(*parts)
|
151
147
|
@i += interpolation.size
|
152
148
|
elsif single_line_comment = chunk[SINGLE_LINE_COMMENT_REGEX] && (prev_token.nil? || prev_token[0] == :NEWLINE)
|
153
149
|
comment = chunk[SINGLE_LINE_COMMENT_REGEX]
|
@@ -166,8 +162,8 @@ module Riml
|
|
166
162
|
@token_buf << [:NEWLINE, "\n"] unless prev_token && prev_token[0] == :NEWLINE
|
167
163
|
|
168
164
|
# pending indents/dedents
|
169
|
-
if @
|
170
|
-
@
|
165
|
+
if @one_line_conditional_end_pending
|
166
|
+
@one_line_conditional_end_pending = false
|
171
167
|
elsif @indent_pending
|
172
168
|
@indent_pending = false
|
173
169
|
elsif @dedent_pending
|
@@ -179,11 +175,15 @@ module Riml
|
|
179
175
|
elsif heredoc_pattern = chunk[%r{\A<<(.+?)\r?\n}]
|
180
176
|
pattern = $1
|
181
177
|
@i += heredoc_pattern.size
|
182
|
-
@token_buf << [:HEREDOC, pattern]
|
183
178
|
new_chunk = get_new_chunk
|
184
|
-
heredoc_string = new_chunk[%r|(.+?\r?\n)(#{Regexp.escape(pattern)})
|
185
|
-
@i += heredoc_string.size +
|
186
|
-
|
179
|
+
heredoc_string = new_chunk[%r|(.+?\r?\n)(#{Regexp.escape(pattern)})|, 1]
|
180
|
+
@i += heredoc_string.size + pattern.size
|
181
|
+
if ('"' + heredoc_string + '"') =~ INTERPOLATION_REGEX
|
182
|
+
parts = heredoc_string.split(INTERPOLATION_SPLIT_REGEX)
|
183
|
+
handle_interpolation(*parts)
|
184
|
+
else
|
185
|
+
@token_buf << [:STRING_D, heredoc_string]
|
186
|
+
end
|
187
187
|
@lineno += (1 + heredoc_string.each_line.to_a.size)
|
188
188
|
# operators of more than 1 char
|
189
189
|
elsif operator = chunk[OPERATOR_REGEX]
|
@@ -220,13 +220,13 @@ module Riml
|
|
220
220
|
@indent_pending = true
|
221
221
|
when :if, :unless
|
222
222
|
if one_line_conditional?(chunk)
|
223
|
-
@
|
223
|
+
@one_line_conditional_end_pending = true
|
224
224
|
elsif !statement_modifier?(chunk)
|
225
225
|
@current_indent += 2
|
226
226
|
@indent_pending = true
|
227
227
|
end
|
228
228
|
when :end
|
229
|
-
unless @
|
229
|
+
unless @one_line_conditional_end_pending
|
230
230
|
@current_indent -= 2
|
231
231
|
@dedent_pending = true
|
232
232
|
end
|
@@ -258,6 +258,22 @@ module Riml
|
|
258
258
|
chunk[/^(if|unless).+?(else)?.+?end$/]
|
259
259
|
end
|
260
260
|
|
261
|
+
def handle_interpolation(*parts)
|
262
|
+
parts.delete_if {|p| p.empty?}.each_with_index do |part, i|
|
263
|
+
if part[0..1] == '#{' && part[-1] == '}'
|
264
|
+
@token_buf.concat tokenize_without_moving_pos(part[2...-1])
|
265
|
+
else
|
266
|
+
@token_buf << [:STRING_D, part]
|
267
|
+
end
|
268
|
+
# string-concatenate all the parts unless this is the last part
|
269
|
+
@token_buf << ['.', '.'] unless parts[i + 1].nil?
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def tokenize_without_moving_pos(code)
|
274
|
+
Lexer.new(code).tokenize
|
275
|
+
end
|
276
|
+
|
261
277
|
def statement_modifier?(chunk)
|
262
278
|
old_i = @i
|
263
279
|
# backtrack until the beginning of the line
|