riml 0.3.3 → 0.3.4

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