riml 0.1.7 → 0.1.8
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.
- 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]
|