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.
@@ -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
@@ -0,0 +1,13 @@
1
+ source :rubygems
2
+
3
+ group :development do
4
+ # create the parser by going to the lib directory
5
+ # and executing the following:
6
+ #
7
+ # $ racc -o parser.rb grammar.y
8
+ gem 'racc'
9
+ end
10
+
11
+ group :test do
12
+ gem 'rake' # for travis-ci
13
+ end
@@ -0,0 +1,12 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ racc (1.4.9)
5
+ rake (10.0.3)
6
+
7
+ PLATFORMS
8
+ ruby
9
+
10
+ DEPENDENCIES
11
+ racc
12
+ rake
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 translationObj.translate = function('g:FrenchToEnglishTranslation_translate')
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
@@ -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, "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
33
- end
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("-s", "--stdio", "Pipe in riml to STDIN and get back VimL on STDOUT.") do
46
- options.stdio = true
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
@@ -98,6 +98,9 @@ module Riml
98
98
  end
99
99
 
100
100
  def replace(node)
101
+ if classes[node.name]
102
+ raise ClassRedefinitionError, "can't redefine class #{node.name.inspect}"
103
+ end
101
104
  classes[node.name] = node
102
105
 
103
106
  name, expressions = node.name, node.expressions
@@ -35,10 +35,6 @@ module Riml
35
35
  @map.keys
36
36
  end
37
37
 
38
- def clear
39
- @map.clear
40
- end
41
-
42
38
  protected
43
39
  def ensure_key_is_string!(key)
44
40
  unless key.is_a?(String)
@@ -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
- return (node.compiled_output << "\n") if node.expression.nil?
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
- compiled = ""
291
- parts.each do |part|
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
- "#{part.value}"
300
+ part.value
302
301
  end
303
- compiled << output
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
- "#{node.full_name}"
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)
@@ -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 command command!
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(== != >= > <= < =~ !~)
@@ -6,4 +6,6 @@ module Riml
6
6
  CompileError = Class.new(RimlError)
7
7
 
8
8
  FileNotFound = Class.new(RimlError)
9
+
10
+ ClassRedefinitionError = Class.new(RimlError)
9
11
  end
@@ -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
- | Literal { result = val[0] }
89
+ | LiteralWithoutDictLiteral { result = val[0] }
81
90
  | Call { result = val[0] }
82
- | Ternary { result = val[0] }
83
91
  | ObjectInstantiation { result = val[0] }
84
- | BinaryOperator { result = val[0] }
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
- Dictionary DictGetWithDotLiteral { result = DictGetDotNode.new(val[0], val[1]) }
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
- ValueExpression ListOrDictGetWithBrackets { result = ListOrDictGetNode.new(val[0], val[1]) }
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
 
@@ -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
- @i = 0 # number of characters consumed
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
- return @prev_token = @token_buf.shift
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(\n*)\s*\\/]
66
+ if cont = chunk[/\A\n*\s*\\/m]
58
67
  @i += cont.size
59
- @lineno += $1.size
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
- comment = chunk[SINGLE_LINE_COMMENT_REGEX]
150
- @i += comment.size + 1 # consume next newline character
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 += (1 + heredoc_string.each_line.to_a.size)
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 = $1.split('.')
241
- @i += $1.size + 1
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] << ".#{$1}"
258
+ @token_buf.last[1] << ".#{vals}"
244
259
  else
245
260
  while key = parts.shift
246
261
  @token_buf << [:DICT_VAL, key]