riml 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CONTRIBUTING +44 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +12 -0
- data/README.md +1 -1
- data/Rakefile +15 -0
- data/bin/riml +43 -12
- data/lib/ast_rewriter.rb +3 -0
- data/lib/class_map.rb +0 -4
- data/lib/compiler.rb +15 -18
- data/lib/constants.rb +3 -1
- data/lib/errors.rb +2 -0
- data/lib/grammar.y +21 -9
- data/lib/lexer.rb +31 -16
- data/lib/nodes.rb +8 -8
- data/lib/parser.rb +1134 -1089
- data/lib/repl.rb +57 -0
- data/lib/riml.rb +19 -5
- data/version.rb +2 -2
- metadata +10 -5
data/CONTRIBUTING
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Thanks for reading this!
|
2
|
+
|
3
|
+
If you're interested in contributing to Riml, thanks! Here's a brief overview
|
4
|
+
of how it works.
|
5
|
+
|
6
|
+
Brief Overview
|
7
|
+
==============
|
8
|
+
|
9
|
+
1. The lexer lexes riml source into Array of tokens.
|
10
|
+
|
11
|
+
2. The parser, generated by Racc from the 'grammar.y' file, creates an
|
12
|
+
Abstract Syntax Tree from the nodes in the 'nodes.rb' file.
|
13
|
+
|
14
|
+
3. The AST_Rewriter rewrites portions of the tree. These portions are
|
15
|
+
Riml-only constructs, and mainly it's rewriting Riml-only syntax nodes into
|
16
|
+
VimL-compatible syntax-nodes. For example, one of the ways a ClassNode is
|
17
|
+
rewritten here is by adding an 'initialize' function to it if it doesn't have
|
18
|
+
one already. Once this is done, onto the compiler!
|
19
|
+
|
20
|
+
4. The compiler, implemented using the Visitor pattern, visits the nodes in a
|
21
|
+
top-down fashion starting with the root, and these nodes append their compiled
|
22
|
+
output (VimL) to their parent node's output. After all is done, the root node (only
|
23
|
+
node without a parent) is left with the full output.
|
24
|
+
|
25
|
+
Pull Requests
|
26
|
+
=============
|
27
|
+
|
28
|
+
When appropriate, please add unit tests for features or bugs. Also, please
|
29
|
+
update all documentation related to the changes that you've made, including
|
30
|
+
comments in the source code. If you don't know where all the affected areas
|
31
|
+
might be, don't hesitate to ask.
|
32
|
+
|
33
|
+
For pure documentation-related changes, please put '[ci skip]' somewhere in the
|
34
|
+
commit message so that Travis-ci skips the build.
|
35
|
+
|
36
|
+
Note
|
37
|
+
====
|
38
|
+
|
39
|
+
If you're working on the grammar file, make sure to re-create the parser after
|
40
|
+
each change before testing. This is done by going to the 'lib' directory and
|
41
|
+
typing:
|
42
|
+
|
43
|
+
`racc -o parser.rb grammar.y`
|
44
|
+
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
CHANGED
@@ -94,7 +94,7 @@ Classes
|
|
94
94
|
defm translate let frenchToEnglishTranslationObj = {}
|
95
95
|
if (self.input == "Bonjour!") let translationObj = g:TranslationConstructor(a:input)
|
96
96
|
echo "Hello!" call extend(frenchToEnglishTranslationObj, translationObj)
|
97
|
-
else let
|
97
|
+
else let frenchToEnglishTranslationObj.translate = function('g:FrenchToEnglishTranslation_translate')
|
98
98
|
echo "Sorry, I don't know that word." return frenchToEnglishTranslationObj
|
99
99
|
end endfunction
|
100
100
|
end
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path('../lib/environment', __FILE__)
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
task :default => :test
|
5
|
+
task :test => [:output_test_count]
|
6
|
+
|
7
|
+
desc 'Run all *_tests and *_specs (default)'
|
8
|
+
test = Rake::TestTask.new(:test) do |t|
|
9
|
+
TEST_LIST = FileList['test/**/*_test.rb'].to_a
|
10
|
+
t.test_files = TEST_LIST
|
11
|
+
end
|
12
|
+
|
13
|
+
task :output_test_count do
|
14
|
+
puts "#{TEST_LIST.size} test files to run."
|
15
|
+
end
|
data/bin/riml
CHANGED
@@ -12,25 +12,30 @@ module Riml
|
|
12
12
|
|
13
13
|
class Options
|
14
14
|
def self.parse(argv)
|
15
|
+
ARGV << '--help' if ARGV.size.zero?
|
15
16
|
|
16
17
|
# defaults
|
17
18
|
options = OpenStruct.new
|
18
19
|
options.compile_files = []
|
20
|
+
options.check_syntax_files = []
|
21
|
+
options.repl = false
|
22
|
+
options.vi_readline = false
|
19
23
|
|
20
24
|
OptionParser.new do |opts|
|
21
|
-
opts.banner = "Usage: riml [options]"
|
25
|
+
opts.banner = "Usage: riml [options] [file1][,file2]..."
|
22
26
|
opts.separator ""
|
23
27
|
opts.separator "Specific options:"
|
24
28
|
|
25
|
-
opts.on("-c", "--compile FILES", Array, "
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
opts.on("-c", "--compile FILES", Array, "Compiles riml file(s) to VimL.") do |filenames|
|
30
|
+
append_filenames_to_list_if_all_exist(options.compile_files, *filenames)
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("-s", "--stdio", "Takes riml from stdin and outputs VimL to stdout.") do
|
34
|
+
options.stdio = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("-k", "--check FILES", Array, "Checks syntax of file(s). Because Riml is a superset of VimL, this can be used to check VimL's syntax as well.") do |filenames|
|
38
|
+
append_filenames_to_list_if_all_exist(options.check_syntax_files, *filenames)
|
34
39
|
end
|
35
40
|
|
36
41
|
opts.on("-t", "--source-path PATH", "Path riml uses for `riml_source` to find files. Defaults to pwd.") do |path|
|
@@ -42,8 +47,13 @@ module Riml
|
|
42
47
|
end
|
43
48
|
end
|
44
49
|
|
45
|
-
opts.on("-
|
46
|
-
|
50
|
+
opts.on("-i", "--interactive", "Start an interactive riml session (REPL).") do
|
51
|
+
require 'repl'
|
52
|
+
options.repl = true
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("--vi", "Use vi readline settings during interactive session.") do
|
56
|
+
options.vi_readline = true
|
47
57
|
end
|
48
58
|
|
49
59
|
opts.on_tail("-v", "--version", "Show riml version.") do
|
@@ -59,6 +69,19 @@ module Riml
|
|
59
69
|
|
60
70
|
options
|
61
71
|
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def self.append_filenames_to_list_if_all_exist(list, *filenames)
|
76
|
+
filenames.each do |fname|
|
77
|
+
if File.exists?(fname)
|
78
|
+
list << fname
|
79
|
+
else
|
80
|
+
warn "Couldn't find file #{fname.inspect}."
|
81
|
+
exit 1
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
62
85
|
end
|
63
86
|
|
64
87
|
class Runner
|
@@ -70,6 +93,14 @@ module Riml
|
|
70
93
|
puts Riml.compile($stdin.read)
|
71
94
|
elsif options.compile_files.any?
|
72
95
|
Riml.compile_files(*options.compile_files)
|
96
|
+
elsif options.check_syntax_files.any?
|
97
|
+
files = options.check_syntax_files
|
98
|
+
Riml.check_syntax_files(*files)
|
99
|
+
size = files.size
|
100
|
+
# "ok (1 file)" OR "ok (2 files)"
|
101
|
+
puts "ok (#{size} file#{'s' if size > 1})"
|
102
|
+
elsif options.repl
|
103
|
+
Riml::Repl.new(options.vi_readline).run
|
73
104
|
end
|
74
105
|
end
|
75
106
|
end
|
data/lib/ast_rewriter.rb
CHANGED
data/lib/class_map.rb
CHANGED
data/lib/compiler.rb
CHANGED
@@ -111,8 +111,8 @@ module Riml
|
|
111
111
|
def compile(nodes)
|
112
112
|
nodes.each_with_index do |node, i|
|
113
113
|
visitor = visitor_for_node(node)
|
114
|
-
next_node = nodes.nodes[i+1]
|
115
114
|
node.parent_node = nodes
|
115
|
+
next_node = nodes.nodes[i+1]
|
116
116
|
if ElseNode === next_node
|
117
117
|
node.force_newline = true
|
118
118
|
end
|
@@ -194,11 +194,11 @@ module Riml
|
|
194
194
|
class ReturnNodeVisitor < Visitor
|
195
195
|
def compile(node)
|
196
196
|
node.compiled_output = "return"
|
197
|
-
|
197
|
+
node.force_newline = true
|
198
|
+
return node.compiled_output if node.expression.nil?
|
198
199
|
node.expression.parent_node = node
|
199
200
|
node.compiled_output << " "
|
200
201
|
node.expression.accept(visitor_for_node(node.expression))
|
201
|
-
node.force_newline = true
|
202
202
|
node.compiled_output
|
203
203
|
end
|
204
204
|
end
|
@@ -287,9 +287,8 @@ module Riml
|
|
287
287
|
end
|
288
288
|
|
289
289
|
def compile_parts(parts)
|
290
|
-
|
291
|
-
|
292
|
-
output = if CurlyBraceVariable === part
|
290
|
+
parts.map do |part|
|
291
|
+
if CurlyBraceVariable === part
|
293
292
|
compile_parts(part)
|
294
293
|
elsif part.nested?
|
295
294
|
compile_nested_parts(part.value, part)
|
@@ -298,11 +297,9 @@ module Riml
|
|
298
297
|
part.value.accept(visitor_for_node(part.value))
|
299
298
|
"{#{part.value.compiled_output}}"
|
300
299
|
else
|
301
|
-
|
300
|
+
part.value
|
302
301
|
end
|
303
|
-
|
304
|
-
end
|
305
|
-
compiled
|
302
|
+
end.join
|
306
303
|
end
|
307
304
|
|
308
305
|
def compile_nested_parts(parts, root_part)
|
@@ -319,9 +316,8 @@ module Riml
|
|
319
316
|
next
|
320
317
|
end
|
321
318
|
part.value.accept(visitor_for_node(part.value, :propagate_up_tree => false))
|
322
|
-
root_part.compiled_output << "{#{part.value.compiled_output}}"
|
319
|
+
root_part.compiled_output << "{#{part.value.compiled_output}}#{'}' * nested}"
|
323
320
|
end
|
324
|
-
root_part.compiled_output << ('}' * nested)
|
325
321
|
end
|
326
322
|
end
|
327
323
|
|
@@ -446,7 +442,7 @@ module Riml
|
|
446
442
|
node.name.accept(visitor_for_node(node.name))
|
447
443
|
node.name.compiled_output
|
448
444
|
else
|
449
|
-
|
445
|
+
node.full_name
|
450
446
|
end
|
451
447
|
compile_arguments(node)
|
452
448
|
node.compiled_output
|
@@ -494,6 +490,7 @@ module Riml
|
|
494
490
|
class RimlCommandNodeVisitor < CallNodeVisitor
|
495
491
|
def compile(node)
|
496
492
|
if node.name == 'riml_source'
|
493
|
+
node.name = 'source'
|
497
494
|
node.arguments.map(&:value).each do |file|
|
498
495
|
unless File.exists?(File.join(Riml.source_path, file))
|
499
496
|
raise Riml::FileNotFound, "#{file.inspect} could not be found in " \
|
@@ -502,12 +499,12 @@ module Riml
|
|
502
499
|
|
503
500
|
root_node(node).current_compiler.compile_queue << file
|
504
501
|
end
|
505
|
-
node.compiled_output << 'source'
|
506
|
-
compile_arguments(node)
|
507
|
-
node.compiled_output.gsub!(/['"]/, '')
|
508
|
-
node.compiled_output.sub!('.riml', '.vim')
|
509
|
-
node.compiled_output
|
510
502
|
end
|
503
|
+
node.compiled_output << node.name
|
504
|
+
compile_arguments(node)
|
505
|
+
node.compiled_output.gsub!(/['"]/, '')
|
506
|
+
node.compiled_output.sub!('.riml', '.vim')
|
507
|
+
node.compiled_output
|
511
508
|
end
|
512
509
|
|
513
510
|
def root_node(node)
|
data/lib/constants.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Riml
|
2
2
|
module Constants
|
3
3
|
VIML_KEYWORDS =
|
4
|
-
%w(function function! if else elseif while for in
|
4
|
+
%w(function function! if else elseif while for in
|
5
5
|
return is isnot finish break continue call let unlet unlet! try
|
6
6
|
catch finally)
|
7
7
|
VIML_END_KEYWORDS =
|
@@ -17,6 +17,8 @@ module Riml
|
|
17
17
|
%w(echo echon echohl execute sleep)
|
18
18
|
RIML_COMMANDS =
|
19
19
|
%w(riml_source)
|
20
|
+
VIML_COMMANDS =
|
21
|
+
%w(source source! command! command silent silent!)
|
20
22
|
|
21
23
|
IGNORECASE_CAPABLE_OPERATORS =
|
22
24
|
%w(== != >= > <= < =~ !~)
|
data/lib/errors.rb
CHANGED
data/lib/grammar.y
CHANGED
@@ -72,17 +72,24 @@ rule
|
|
72
72
|
|
73
73
|
# Expressions that evaluate to a value
|
74
74
|
ValueExpression:
|
75
|
+
ValueExpressionWithoutDictLiteral { result = val[0] }
|
76
|
+
| Dictionary { result = val[0] }
|
77
|
+
| Dictionary DictGetWithDotLiteral { result = DictGetDotNode.new(val[0], val[1]) }
|
78
|
+
| BinaryOperator { result = val[0] }
|
79
|
+
| Ternary { result = val[0] }
|
80
|
+
| '(' ValueExpression ')' { result = WrapInParensNode.new(val[1]) }
|
81
|
+
;
|
82
|
+
|
83
|
+
ValueExpressionWithoutDictLiteral:
|
75
84
|
UnaryOperator { result = val[0] }
|
76
85
|
| Assign { result = val[0] }
|
77
86
|
| DictGet { result = val[0] }
|
78
87
|
| ListOrDictGet { result = val[0] }
|
79
88
|
| AllVariableRetrieval { result = val[0] }
|
80
|
-
|
|
89
|
+
| LiteralWithoutDictLiteral { result = val[0] }
|
81
90
|
| Call { result = val[0] }
|
82
|
-
| Ternary { result = val[0] }
|
83
91
|
| ObjectInstantiation { result = val[0] }
|
84
|
-
|
|
85
|
-
| '(' ValueExpression ')' { result = WrapInParensNode.new(val[1]) }
|
92
|
+
| '(' ValueExpressionWithoutDictLiteral ')' { result = WrapInParensNode.new(val[1]) }
|
86
93
|
;
|
87
94
|
|
88
95
|
Terminator:
|
@@ -93,11 +100,15 @@ rule
|
|
93
100
|
|
94
101
|
# All hard-coded values
|
95
102
|
Literal:
|
103
|
+
LiteralWithoutDictLiteral { result = val[0] }
|
104
|
+
| Dictionary { result = val[0] }
|
105
|
+
;
|
106
|
+
|
107
|
+
LiteralWithoutDictLiteral:
|
96
108
|
Number { result = val[0] }
|
97
109
|
| String { result = val[0] }
|
98
110
|
| Regexp { result = val[0] }
|
99
111
|
| List { result = val[0] }
|
100
|
-
| Dictionary { result = val[0] }
|
101
112
|
| ScopeModifierLiteral { result = val[0] }
|
102
113
|
| TRUE { result = TrueNode.new }
|
103
114
|
| FALSE { result = FalseNode.new }
|
@@ -163,13 +174,14 @@ rule
|
|
163
174
|
;
|
164
175
|
|
165
176
|
DictGet:
|
166
|
-
|
167
|
-
| AllVariableRetrieval DictGetWithDot { result = DictGetDotNode.new(val[0], val[1]) }
|
177
|
+
AllVariableRetrieval DictGetWithDot { result = DictGetDotNode.new(val[0], val[1]) }
|
168
178
|
| ListOrDictGet DictGetWithDot { result = DictGetDotNode.new(val[0], val[1]) }
|
179
|
+
| '(' ValueExpression ')' DictGetWithDot { result = DictGetDotNode.new(WrapInParensNode.new(val[1]), val[3]) }
|
169
180
|
;
|
170
181
|
|
171
182
|
ListOrDictGet:
|
172
|
-
|
183
|
+
ValueExpressionWithoutDictLiteral ListOrDictGetWithBrackets { result = ListOrDictGetNode.new(val[0], val[1]) }
|
184
|
+
| '(' ValueExpression ')' ListOrDictGetWithBrackets { result = ListOrDictGetNode.new(WrapInParensNode.new(val[1]), val[3]) }
|
173
185
|
;
|
174
186
|
|
175
187
|
ListOrDictGetWithBrackets:
|
@@ -296,9 +308,9 @@ rule
|
|
296
308
|
|
297
309
|
AssignLHS:
|
298
310
|
AllVariableRetrieval { result = val[0] }
|
299
|
-
| DictGet { result = val[0] }
|
300
311
|
| List { result = val[0] }
|
301
312
|
| ListUnpack { result = val[0] }
|
313
|
+
| DictGet { result = val[0] }
|
302
314
|
| ListOrDictGet { result = val[0] }
|
303
315
|
;
|
304
316
|
|
data/lib/lexer.rb
CHANGED
@@ -10,7 +10,9 @@ module Riml
|
|
10
10
|
INTERPOLATION_REGEX = /\A"(.*?)(\#\{(.*?)\})(.*?)"/m
|
11
11
|
INTERPOLATION_SPLIT_REGEX = /(\#{.*?})/m
|
12
12
|
|
13
|
-
attr_reader :tokens, :prev_token, :lineno, :chunk
|
13
|
+
attr_reader :tokens, :prev_token, :lineno, :chunk, :current_indent
|
14
|
+
# for REPL
|
15
|
+
attr_accessor :ignore_indentation_check
|
14
16
|
|
15
17
|
def initialize(code)
|
16
18
|
@code = code
|
@@ -19,7 +21,10 @@ module Riml
|
|
19
21
|
end
|
20
22
|
|
21
23
|
def set_start_state!
|
22
|
-
|
24
|
+
# number of characters consumed
|
25
|
+
@i = 0
|
26
|
+
# array of doubles and triples: [tokenname, tokenval, lineno_to_add(optional)]
|
27
|
+
# ex: [[:NEWLINE, "\n"]] OR [[:NEWLINE, "\n", 1]]
|
23
28
|
@token_buf = []
|
24
29
|
@tokens = []
|
25
30
|
@prev_token = nil
|
@@ -45,18 +50,22 @@ module Riml
|
|
45
50
|
tokenize_chunk(get_new_chunk)
|
46
51
|
end
|
47
52
|
if !@token_buf.empty?
|
48
|
-
|
53
|
+
token = @token_buf.shift
|
54
|
+
if token.size == 3
|
55
|
+
@lineno += token.pop
|
56
|
+
end
|
57
|
+
return @prev_token = token
|
49
58
|
end
|
50
|
-
check_indentation
|
59
|
+
check_indentation unless ignore_indentation_check
|
51
60
|
nil
|
52
61
|
end
|
53
62
|
|
54
63
|
def tokenize_chunk(chunk)
|
55
64
|
@chunk = chunk
|
56
65
|
# deal with line continuations
|
57
|
-
if cont = chunk[/\A
|
66
|
+
if cont = chunk[/\A\n*\s*\\/m]
|
58
67
|
@i += cont.size
|
59
|
-
@lineno +=
|
68
|
+
@lineno += cont.each_line.to_a.size - 1
|
60
69
|
return
|
61
70
|
end
|
62
71
|
|
@@ -111,6 +120,13 @@ module Riml
|
|
111
120
|
@token_buf << [:BUILTIN_COMMAND, identifier]
|
112
121
|
elsif RIML_COMMANDS.include? identifier
|
113
122
|
@token_buf << [:RIML_COMMAND, identifier]
|
123
|
+
elsif VIML_COMMANDS.include?(identifier) && (prev_token.nil? || prev_token[0] == :NEWLINE)
|
124
|
+
@i += identifier.size
|
125
|
+
new_chunk = get_new_chunk
|
126
|
+
until_eol = new_chunk[/.*$/].to_s
|
127
|
+
@token_buf << [:EX_LITERAL, identifier << until_eol]
|
128
|
+
@i += until_eol.size
|
129
|
+
return
|
114
130
|
# method names and variable names
|
115
131
|
else
|
116
132
|
@token_buf << [:IDENTIFIER, identifier]
|
@@ -145,12 +161,12 @@ module Riml
|
|
145
161
|
parts = interpolation[1...-1].split(INTERPOLATION_SPLIT_REGEX)
|
146
162
|
handle_interpolation(*parts)
|
147
163
|
@i += interpolation.size
|
148
|
-
elsif single_line_comment = chunk[SINGLE_LINE_COMMENT_REGEX] && (prev_token.nil? || prev_token[0] == :NEWLINE)
|
149
|
-
|
150
|
-
@
|
151
|
-
@lineno += 1
|
164
|
+
elsif (single_line_comment = chunk[SINGLE_LINE_COMMENT_REGEX]) && (prev_token.nil? || prev_token[0] == :NEWLINE)
|
165
|
+
@i += single_line_comment.size + 1 # consume next newline character
|
166
|
+
@lineno += single_line_comment.each_line.to_a.size
|
152
167
|
elsif inline_comment = chunk[/\A\s*"[^"]*?$/]
|
153
168
|
@i += inline_comment.size # inline comment, don't consume newline character
|
169
|
+
@lineno += inline_comment.each_line.to_a.size - 1
|
154
170
|
elsif string_double = chunk[/\A"(.*?)(?<!\\)"/, 1]
|
155
171
|
@token_buf << [:STRING_D, string_double]
|
156
172
|
@i += string_double.size + 2
|
@@ -184,12 +200,11 @@ module Riml
|
|
184
200
|
else
|
185
201
|
@token_buf << [:STRING_D, heredoc_string]
|
186
202
|
end
|
187
|
-
@lineno +=
|
203
|
+
@lineno += heredoc_string.each_line.to_a.size
|
188
204
|
# operators of more than 1 char
|
189
205
|
elsif operator = chunk[OPERATOR_REGEX]
|
190
206
|
@token_buf << [operator, operator]
|
191
207
|
@i += operator.size
|
192
|
-
# FIXME: this doesn't work well enough
|
193
208
|
elsif regexp = chunk[%r{\A/.*?[^\\]/}]
|
194
209
|
@token_buf << [:REGEXP, regexp]
|
195
210
|
@i += regexp.size
|
@@ -236,11 +251,11 @@ module Riml
|
|
236
251
|
def parse_dict_vals!
|
237
252
|
# dict.key OR dict.key.other_key
|
238
253
|
new_chunk = get_new_chunk
|
239
|
-
if new_chunk[/\A\.([\w.]+)
|
240
|
-
parts =
|
241
|
-
@i +=
|
254
|
+
if vals = new_chunk[/\A\.([\w.]+)/, 1]
|
255
|
+
parts = vals.split('.')
|
256
|
+
@i += vals.size + 1
|
242
257
|
if @in_function_declaration
|
243
|
-
@token_buf.last[1] << ".#{
|
258
|
+
@token_buf.last[1] << ".#{vals}"
|
244
259
|
else
|
245
260
|
while key = parts.shift
|
246
261
|
@token_buf << [:DICT_VAL, key]
|