riml 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]