riml 0.3.3 → 0.3.4

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.
@@ -1,4 +1,5 @@
1
1
  require File.expand_path("../errors", __FILE__)
2
+ require File.expand_path("../imported_class", __FILE__)
2
3
 
3
4
  module Riml
4
5
  # Map of {"ClassName" => ClassDefinitionNode}
@@ -6,13 +7,26 @@ module Riml
6
7
  #
7
8
  # Ex : class_map.superclass("g:SomeClass") => "g:SomeClassBase"
8
9
  class ClassMap
10
+ attr_reader :globbed_imports
11
+
9
12
  def initialize
10
13
  @map = {}
14
+ # list of ImportedClass objects that are #globbed?
15
+ @globbed_imports = []
11
16
  end
12
17
 
13
18
  def [](key)
14
19
  ensure_key_is_string!(key)
15
- @map[key] or raise ClassNotFound, "class #{key.inspect} not found."
20
+ klass = @map[key]
21
+ return klass if klass
22
+ if key[0..1] == 'g:'
23
+ globbed_imports.each do |imported_class|
24
+ if imported_class.match?(key)
25
+ return @map[key] = ImportedClass.new(key)
26
+ end
27
+ end
28
+ end
29
+ raise ClassNotFound, "class #{key.inspect} not found."
16
30
  end
17
31
 
18
32
  def []=(key, val)
@@ -39,7 +53,16 @@ module Riml
39
53
  @map.keys
40
54
  end
41
55
 
56
+ def safe_fetch(key)
57
+ @map[key]
58
+ end
59
+
60
+ def has_global_import?
61
+ @globbed_imports.any? { |import| import.global_import? }
62
+ end
63
+
42
64
  protected
65
+
43
66
  def ensure_key_is_string!(key)
44
67
  unless key.is_a?(String)
45
68
  raise ArgumentError, "key must be name of class (String)"
@@ -596,7 +596,7 @@ module Riml
596
596
  end
597
597
  end
598
598
 
599
- class RimlCommandNodeVisitor < CallNodeVisitor
599
+ class RimlFileCommandNodeVisitor < CallNodeVisitor
600
600
  def compile(node)
601
601
  if node.name == 'riml_source'
602
602
  node.name = 'source'
@@ -610,8 +610,7 @@ module Riml
610
610
  raise IncludeNotTopLevel, error_msg
611
611
  end
612
612
  node.each_existing_file! do |basename, full_path|
613
- riml_src = File.read(full_path)
614
- output = current_compiler(node).compile_include(riml_src, basename)
613
+ output = current_compiler(node).compile_include(basename, full_path)
615
614
  node.compiled_output << output if output
616
615
  end
617
616
  return node.compiled_output
@@ -781,13 +780,14 @@ module Riml
781
780
  @included_files_compiled ||= []
782
781
  end
783
782
 
784
- def compile_include(source, filename)
785
- return if included_files_compiled.include?(filename)
786
- Riml.include_cache.fetch(filename) do
787
- root_node = parser.parse(source, parser.ast_rewriter, filename, true)
783
+ def compile_include(file_basepath, file_fullpath)
784
+ return if included_files_compiled.include?(file_basepath)
785
+ Riml.include_cache.fetch(file_basepath) do
786
+ source = File.read(file_fullpath)
787
+ root_node = parser.parse(source, parser.ast_rewriter, file_basepath, true)
788
+ included_files_compiled << file_basepath
788
789
  output = compile(root_node)
789
- included_files_compiled << filename
790
- (Riml::INCLUDE_COMMENT_FMT % filename) + output
790
+ (Riml::INCLUDE_COMMENT_FMT % file_basepath) + output
791
791
  end
792
792
  end
793
793
 
@@ -20,8 +20,10 @@ module Riml
20
20
  %w(& @ $)
21
21
  BUILTIN_COMMANDS =
22
22
  %w(echo echon echomsg echoerr echohl execute exec sleep throw)
23
- RIML_COMMANDS =
23
+ RIML_FILE_COMMANDS =
24
24
  %w(riml_source riml_include)
25
+ RIML_CLASS_COMMANDS = %w(riml_import)
26
+ RIML_COMMANDS = RIML_FILE_COMMANDS + RIML_CLASS_COMMANDS
25
27
  VIML_COMMANDS =
26
28
  %w(source source! command! command silent silent!)
27
29
 
@@ -1,6 +1,6 @@
1
1
  module Riml
2
2
  module Environment
3
- ROOTDIR = File.expand_path('../../', __FILE__)
3
+ ROOTDIR = File.expand_path('../../..', __FILE__)
4
4
  require File.join(ROOTDIR, 'version')
5
5
 
6
6
  LIBDIR = File.join(ROOTDIR, 'lib')
@@ -1,5 +1,7 @@
1
1
  module Riml
2
- RimlError = Class.new(StandardError)
2
+ RimlError = Class.new(StandardError) do
3
+ attr_accessor :node
4
+ end
3
5
 
4
6
  SyntaxError = Class.new(RimlError)
5
7
  ParseError = Class.new(RimlError)
@@ -4,8 +4,9 @@ token IF ELSE ELSEIF THEN UNLESS END
4
4
  token WHILE UNTIL BREAK CONTINUE
5
5
  token TRY CATCH FINALLY
6
6
  token FOR IN
7
- token DEF DEF_BANG SPLAT CALL BUILTIN_COMMAND # such as echo "hi"
8
- token CLASS NEW DEFM DEFM_BANG SUPER RIML_COMMAND
7
+ token DEF DEF_BANG SPLAT_PARAM SPLAT_ARG CALL BUILTIN_COMMAND # such as echo "hi"
8
+ token CLASS NEW DEFM DEFM_BANG SUPER
9
+ token RIML_FILE_COMMAND RIML_CLASS_COMMAND
9
10
  token RETURN
10
11
  token NEWLINE
11
12
  token NUMBER
@@ -64,7 +65,8 @@ rule
64
65
  | ClassDefinition { result = val[0] }
65
66
  | LoopKeyword { result = val[0] }
66
67
  | EndScript { result = val[0] }
67
- | RimlCommand { result = val[0] }
68
+ | RimlFileCommand { result = val[0] }
69
+ | RimlClassCommand { result = val[0] }
68
70
  | MultiAssign { result = val[0] }
69
71
  | If { result = val[0] }
70
72
  | Unless { result = val[0] }
@@ -233,9 +235,20 @@ rule
233
235
  | Scope DefCallIdentifier { result = make_node(val) { |v| Riml::CallNode.new(v[0], v[1], []) } }
234
236
  ;
235
237
 
236
- RimlCommand:
237
- RIML_COMMAND '(' ArgList ')' { result = make_node(val) { |v| Riml::RimlCommandNode.new(nil, v[0], v[2]) } }
238
- | RIML_COMMAND ArgList { result = make_node(val) { |v| Riml::RimlCommandNode.new(nil, v[0], v[1]) } }
238
+ RimlFileCommand:
239
+ RIML_FILE_COMMAND '(' ArgList ')' { result = make_node(val) { |v| Riml::RimlFileCommandNode.new(nil, v[0], v[2]) } }
240
+ | RIML_FILE_COMMAND ArgList { result = make_node(val) { |v| Riml::RimlFileCommandNode.new(nil, v[0], v[1]) } }
241
+ ;
242
+
243
+ RimlClassCommand:
244
+ RIML_CLASS_COMMAND '(' ClassArgList ')' { result = make_node(val) { |v| Riml::RimlClassCommandNode.new(nil, v[0], v[2]) } }
245
+ | RIML_CLASS_COMMAND ClassArgList { result = make_node(val) { |v| Riml::RimlClassCommandNode.new(nil, v[0], v[1]) } }
246
+ ;
247
+
248
+ ClassArgList:
249
+ Scope IDENTIFIER { result = ["#{val[0]}#{val[1]}"] }
250
+ | String { result = val }
251
+ | ClassArgList ',' Scope IDENTIFIER { result = val[0].concat ["#{val[2]}#{val[3]}"] }
239
252
  ;
240
253
 
241
254
  ExplicitCall:
@@ -259,6 +272,18 @@ rule
259
272
  | ArgListWithoutNothing { result = val[0] }
260
273
  ;
261
274
 
275
+ ArgListWithSplat:
276
+ /* nothing */ { result = [] }
277
+ | ArgListWithoutNothingWithSplat { result = val[0] }
278
+ ;
279
+
280
+ ArgListWithoutNothingWithSplat:
281
+ Expression { result = val }
282
+ | SPLAT_ARG { result = [ make_node(val) { |v| Riml::SplatNode.new(v[0]) } ] }
283
+ | ArgListWithoutNothingWithSplat "," Expression { result = val[0] << val[2] }
284
+ | ArgListWithoutNothingWithSplat "," SPLAT_ARG { result = val[0] << make_node(val) { |v| Riml::SplatNode.new(v[2]) } }
285
+ ;
286
+
262
287
  ArgListWithoutNothing:
263
288
  Expression { result = val }
264
289
  | ArgListWithoutNothing "," Expression { result = val[0] << val[2] }
@@ -381,10 +406,10 @@ rule
381
406
  # Method definition
382
407
  # [SID, scope_modifier, name, parameters, keyword, expressions]
383
408
  Def:
384
- FunctionType SIDAndScope DefCallIdentifier DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], [], v[3], v[4]) } }
385
- | FunctionType SIDAndScope DefCallIdentifier '(' ParamList ')' DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], v[4], v[6], v[7]) } }
386
- | FunctionType SIDAndScope DefCallIdentifier '(' SPLAT ')' DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], [v[4]], v[6], v[7]) } }
387
- | FunctionType SIDAndScope DefCallIdentifier '(' ParamList ',' SPLAT ')' DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], v[4] << v[6], v[8], v[9]) } }
409
+ FunctionType SIDAndScope DefCallIdentifier DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], [], v[3], v[4]) } }
410
+ | FunctionType SIDAndScope DefCallIdentifier '(' ParamList ')' DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], v[4], v[6], v[7]) } }
411
+ | FunctionType SIDAndScope DefCallIdentifier '(' SPLAT_PARAM ')' DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], [v[4]], v[6], v[7]) } }
412
+ | FunctionType SIDAndScope DefCallIdentifier '(' ParamList ',' SPLAT_PARAM ')' DefKeywords Block END { result = make_node(val) { |v| Riml.const_get(val[0]).new('!', v[1][0], v[1][1], v[2], v[4] << v[6], v[8], v[9]) } }
388
413
  ;
389
414
 
390
415
  FunctionType:
@@ -517,8 +542,8 @@ rule
517
542
  ;
518
543
 
519
544
  Super:
520
- SUPER '(' ArgList ')' { result = make_node(val) { |v| Riml::SuperNode.new(v[2], true) } }
521
- | SUPER { result = make_node(val) { |_| Riml::SuperNode.new([], false) } }
545
+ SUPER '(' ArgListWithSplat ')' { result = make_node(val) { |v| Riml::SuperNode.new(v[2], true) } }
546
+ | SUPER { result = make_node(val) { |_| Riml::SuperNode.new([], false) } }
522
547
  ;
523
548
 
524
549
  ExLiteral:
@@ -535,6 +560,12 @@ end
535
560
  # This code will be put as-is in the parser class
536
561
 
537
562
  attr_accessor :ast_rewriter
563
+ attr_writer :options
564
+
565
+ # The Parser and AST_Rewriter share this same hash of options
566
+ def options
567
+ @options ||= {}
568
+ end
538
569
 
539
570
  # parses tokens or code into output nodes
540
571
  def parse(object, ast_rewriter = Riml::AST_Rewriter.new, filename = nil, included = false)
@@ -560,6 +591,7 @@ end
560
591
  @ast_rewriter ||= ast_rewriter
561
592
  return ast unless @ast_rewriter
562
593
  @ast_rewriter.ast = ast
594
+ @ast_rewriter.options ||= options
563
595
  @ast_rewriter.rewrite(filename, included)
564
596
  end
565
597
 
File without changes
@@ -0,0 +1,67 @@
1
+ require File.expand_path('../nodes', __FILE__)
2
+
3
+ module Riml
4
+ class ImportedClass
5
+
6
+ ANCHOR_BEGIN = '\A'
7
+ ANCHOR_END = '\Z'
8
+
9
+ attr_reader :name
10
+ def initialize(name)
11
+ @name = rm_modifier(name)
12
+ end
13
+
14
+ def imported?
15
+ true
16
+ end
17
+
18
+ # an ImportedClass is #globbed? if its name contains 1 or more '*'
19
+ # characters.
20
+ def globbed?
21
+ not @name.index('*').nil?
22
+ end
23
+
24
+ # returns MatchData or `nil`
25
+ def match?(class_name)
26
+ match_regexp.match(rm_modifier(class_name))
27
+ end
28
+
29
+ # returns Regexp
30
+ def match_regexp
31
+ @match_regexp ||= begin
32
+ normalized_glob = @name.gsub(/\*/, '.*?')
33
+ Regexp.new(ANCHOR_BEGIN + normalized_glob + ANCHOR_END)
34
+ end
35
+ end
36
+
37
+ def global_import?
38
+ @name == '*'
39
+ end
40
+
41
+ def scope_modifier
42
+ 'g:'
43
+ end
44
+
45
+ # stubbed out constructor function
46
+ def constructor
47
+ @contructor ||= begin
48
+ DefNode.new('!', nil, scope_modifier, constructor_name, ['...'], [], Nodes.new([]))
49
+ end
50
+ end
51
+
52
+ def constructor_name
53
+ "#{name}Constructor"
54
+ end
55
+
56
+ def constructor_obj_name
57
+ @name[0].downcase + @name[1..-1] + "Obj"
58
+ end
59
+
60
+ private
61
+
62
+ def rm_modifier(class_name)
63
+ class_name.sub(/g:/, '')
64
+ end
65
+
66
+ end
67
+ end
@@ -9,8 +9,8 @@ module Riml
9
9
  end
10
10
 
11
11
  # `fetch` can be called recursively in the `yield`ed block, so must
12
- # make sure not to try to lock a Mutex if it's already locked, as this
13
- # would result in a deadlock.
12
+ # make sure not to try to lock the Mutex if it's already locked by the
13
+ # current thread, as this would result in an error.
14
14
  def fetch(included_filename)
15
15
  if source = @cache[included_filename]
16
16
  return source
@@ -32,6 +32,7 @@ module Riml
32
32
  end
33
33
  end
34
34
 
35
+ # Not used internally but might be useful as an API
35
36
  def [](included_filename)
36
37
  @m.synchronize { @cache[included_filename] }
37
38
  end
File without changes
@@ -141,8 +141,10 @@ module Riml
141
141
 
142
142
  elsif BUILTIN_COMMANDS.include?(identifier) && !chunk[/\A#{Regexp.escape(identifier)}\(/]
143
143
  @token_buf << [:BUILTIN_COMMAND, identifier]
144
- elsif RIML_COMMANDS.include? identifier
145
- @token_buf << [:RIML_COMMAND, identifier]
144
+ elsif RIML_FILE_COMMANDS.include? identifier
145
+ @token_buf << [:RIML_FILE_COMMAND, identifier]
146
+ elsif RIML_CLASS_COMMANDS.include? identifier
147
+ @token_buf << [:RIML_CLASS_COMMAND, identifier]
146
148
  elsif VIML_COMMANDS.include?(identifier) && (prev_token.nil? || prev_token[0] == :NEWLINE)
147
149
  @i += identifier.size
148
150
  new_chunk = get_new_chunk
@@ -159,12 +161,12 @@ module Riml
159
161
 
160
162
  parse_dict_vals!
161
163
 
162
- if @in_function_declaration
163
- @in_function_declaration = false unless DEFINE_KEYWORDS.include?(identifier) && @token_buf.size == 1
164
- end
165
- elsif splat = chunk[/\A(\.{3}|\*[a-zA-Z_]\w*)/]
166
- @token_buf << [:SPLAT, splat]
167
- @i += splat.size
164
+ elsif @in_function_declaration && (splat_param = chunk[/\A(\.{3}|\*[a-zA-Z_]\w*)/])
165
+ @token_buf << [:SPLAT_PARAM, splat_param]
166
+ @i += splat_param.size
167
+ elsif !@in_function_declaration && (splat_arg = chunk[/\A\*([bwtglsavn]:)?([a-zA-Z_]\w*|\d+)/])
168
+ @token_buf << [:SPLAT_ARG, splat_arg]
169
+ @i += splat_arg.size
168
170
  # integer (octal)
169
171
  elsif octal = chunk[/\A0[0-7]+/]
170
172
  @token_buf << [:NUMBER, octal]
@@ -204,6 +206,9 @@ module Riml
204
206
  elsif @dedent_pending
205
207
  @dedent_pending = false
206
208
  end
209
+ if @in_function_declaration
210
+ @in_function_declaration = false
211
+ end
207
212
 
208
213
  @i += newlines.size
209
214
  @lineno += newlines.size
@@ -284,6 +284,12 @@ module Riml
284
284
  end
285
285
  end
286
286
 
287
+ # right now just used in splats in a calling context with super,
288
+ # such as `super(*args)` or `super(*a:000)`
289
+ class SplatNode < LiteralNode
290
+ include Walkable
291
+ end
292
+
287
293
  class SIDNode < LiteralNode
288
294
  def initialize(ident = 'SID')
289
295
  Riml.warn("expected #{ident} to be SID") unless ident == 'SID'
@@ -396,8 +402,12 @@ module Riml
396
402
  # call s:Method(argument1, argument2)
397
403
  class ExplicitCallNode < CallNode; end
398
404
 
399
- # riml_include and riml_source
400
- class RimlCommandNode < CallNode
405
+ # riml_include, riml_source, riml_import
406
+ class RimlCommandNode < CallNode
407
+ end
408
+
409
+ # riml_include, riml_source
410
+ class RimlFileCommandNode < RimlCommandNode
401
411
 
402
412
  def initialize(*)
403
413
  super
@@ -410,22 +420,16 @@ module Riml
410
420
  # Riml.source_path or Riml.include_path
411
421
  def each_existing_file!
412
422
  files = {}
423
+ path_dirs
413
424
  file_variants.each do |(fname_given, fname_ext_added)|
414
- fname = nil
415
- if base_path = paths.detect do |path|
416
- full_given = File.join(path, fname_given)
417
- full_ext_added = File.join(path, fname_ext_added)
418
- fname = if File.exists?(full_given)
419
- fname_given
420
- elsif File.exists?(full_ext_added)
421
- add_ext_to_filename(fname_given)
422
- fname_ext_added
423
- end
424
- end
425
- files[fname] = File.join(base_path, fname)
425
+ if (full_path = Riml.path_cache.file(path_dirs, fname_given))
426
+ files[fname_given] = full_path
427
+ elsif (full_path = Riml.path_cache.file(path_dirs, fname_ext_added))
428
+ add_ext_to_filename(fname_given)
429
+ files[fname_ext_added] = full_path
426
430
  else
427
431
  raise Riml::FileNotFound, "#{fname_given.inspect} could not be found in " \
428
- "Riml.#{name.sub('riml_', '')}_path (#{paths.join(':').inspect})"
432
+ "Riml.#{name.sub('riml_', '')}_path (#{path_dirs.join(':').inspect})"
429
433
  end
430
434
  end
431
435
  return files unless block_given?
@@ -439,7 +443,9 @@ module Riml
439
443
  end
440
444
  end
441
445
 
442
- def paths
446
+ private
447
+
448
+ def path_dirs
443
449
  if name == 'riml_include'
444
450
  Riml.include_path
445
451
  else
@@ -447,8 +453,6 @@ module Riml
447
453
  end
448
454
  end
449
455
 
450
- private
451
-
452
456
  def file_variants
453
457
  arguments.map { |arg| file_variants_for_arg(arg) }
454
458
  end
@@ -464,6 +468,34 @@ module Riml
464
468
  end
465
469
  end
466
470
 
471
+ class RimlClassCommandNode < RimlCommandNode
472
+ def initialize(*args)
473
+ super
474
+ string_node_arguments.each do |arg|
475
+ class_name = arg.value
476
+ # if '*' isn't a char in `class_name`, raise error
477
+ if class_name.index('*').nil?
478
+ msg = "* must be a character in class name '#{class_name}' if riml_import " \
479
+ "is given a string. Try\n`riml_import` #{class_name}` instead."
480
+ error = UserArgumentError.new(msg)
481
+ error.node = self
482
+ raise error
483
+ end
484
+ end
485
+ end
486
+
487
+ def class_names_without_modifiers
488
+ arguments.map do |full_name|
489
+ full_name = full_name.value if full_name.respond_to?(:value)
490
+ full_name.sub(/\A\w:/, '')
491
+ end
492
+ end
493
+
494
+ def string_node_arguments
495
+ arguments.select { |arg| StringNode === arg }
496
+ end
497
+ end
498
+
467
499
  class OperatorNode < Struct.new(:operator, :operands)
468
500
  include Visitable
469
501
  include Walkable
@@ -639,6 +671,7 @@ module Riml
639
671
  include Walkable
640
672
 
641
673
  attr_accessor :private_function
674
+ alias private_function? private_function
642
675
 
643
676
  def initialize(*args)
644
677
  super
@@ -991,6 +1024,12 @@ module Riml
991
1024
  not superclass_name.nil?
992
1025
  end
993
1026
 
1027
+ # This if for the AST_Rewriter, checking if a class is an `ImportedClass`
1028
+ # or not without resorting to type checking.
1029
+ def imported?
1030
+ false
1031
+ end
1032
+
994
1033
  def full_name
995
1034
  scope_modifier + name
996
1035
  end