riml 0.2.0 → 0.2.1

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 by Luke Gruber
1
+ Copyright (c) 2012-2013 by Luke Gruber
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/Rakefile CHANGED
@@ -4,12 +4,19 @@ require 'rake/testtask'
4
4
  task :default => :test
5
5
  task :test => [:output_test_count]
6
6
 
7
- desc 'Run all *_tests and *_specs (default)'
7
+ desc 'Run all tests (default)'
8
8
  Rake::TestTask.new(:test) do |t|
9
9
  TEST_LIST = FileList['test/**/*_test.rb'].to_a
10
10
  t.test_files = TEST_LIST
11
11
  end
12
12
 
13
+ desc 'recreate lib/parser.rb from lib/grammar.y using racc'
14
+ task :parser do
15
+ Dir.chdir(File.expand_path("../lib", __FILE__)) do
16
+ sh 'racc -o parser.rb grammar.y'
17
+ end
18
+ end
19
+
13
20
  task :output_test_count do
14
21
  puts "#{TEST_LIST.size} test files to run."
15
22
  end
data/bin/riml CHANGED
@@ -99,6 +99,9 @@ module Riml
99
99
  elsif options.repl
100
100
  require 'repl'
101
101
  Riml::Repl.new(options.vi_readline).run
102
+ else
103
+ ARGV << '--help'
104
+ start
102
105
  end
103
106
  end
104
107
  end
data/lib/ast_rewriter.rb CHANGED
@@ -13,20 +13,25 @@ module Riml
13
13
  @ast = ast
14
14
  @classes = classes || ClassMap.new
15
15
  @rewritten_include_files = {}
16
+ # keeps track of which files included which, to prevent infinite loops
17
+ @included_file_refs = {}
16
18
  end
17
19
 
18
- def rewrite(file = nil)
19
- if rewritten_ast = rewritten_include_files[file]
20
- return rewritten_ast
20
+ def rewrite(from_file = nil)
21
+ if from_file
22
+ if rewritten_ast = rewritten_include_files[from_file]
23
+ return rewritten_ast
24
+ end
25
+ rewrite_included_files!(from_file)
21
26
  end
22
- rewrite_included_files!
23
27
  establish_parents(ast)
24
28
  rewriters = [
25
29
  StrictEqualsComparisonOperator.new(ast, classes),
26
30
  VarEqualsComparisonOperator.new(ast, classes),
27
31
  ClassDefinitionToFunctions.new(ast, classes),
28
32
  ObjectInstantiationToCall.new(ast, classes),
29
- CallToExplicitCall.new(ast, classes)
33
+ CallToExplicitCall.new(ast, classes),
34
+ DefaultParamToIfNode.new(ast, classes)
30
35
  ]
31
36
  rewriters.each do |rewriter|
32
37
  rewriter.rewrite_on_match
@@ -53,14 +58,30 @@ module Riml
53
58
  replace node if match?(node)
54
59
  end
55
60
 
56
- def rewrite_included_files!
61
+ # We need to rewrite the included files before anything else. This is in
62
+ # order to keep track of any classes defined in the included files (and
63
+ # files included in those, etc...). We keep a cache of rewritten asts
64
+ # because the 'riml_include'd files are parsed more than once. They're
65
+ # parsed first before anything else, plus whenever the compiler visits a
66
+ # 'compile_include' node in order to compile it on the spot.
67
+ def rewrite_included_files!(from_file)
57
68
  old_ast = ast
58
69
  ast.children.each do |node|
59
70
  next unless RimlCommandNode === node && node.name == 'riml_include'
60
71
  node.each_existing_file! do |file|
72
+ if from_file && @included_file_refs[file] == from_file
73
+ msg = "#{from_file.inspect} can't include #{file.inspect}, as " \
74
+ " #{file.inspect} already included #{from_file.inspect}"
75
+ raise IncludeFileLoop, msg
76
+ elsif from_file == file
77
+ raise UserArgumentError, "#{file.inspect} can't include itself"
78
+ end
79
+ @included_file_refs[from_file] = file
61
80
  full_path = File.join(Riml.source_path, file)
62
81
  riml_src = File.read(full_path)
63
- rewritten_ast = Parser.new.parse(riml_src, self)
82
+ # recursively parse included files with this ast_rewriter in order
83
+ # to pick up any classes that are defined there
84
+ rewritten_ast = Parser.new.parse(riml_src, self, file)
64
85
  rewritten_include_files[file] = rewritten_ast
65
86
  end
66
87
  end
@@ -297,5 +318,50 @@ module Riml
297
318
  end
298
319
  end
299
320
 
321
+ class DefaultParamToIfNode < AST_Rewriter
322
+ def match?(node)
323
+ DefaultParamNode === node
324
+ end
325
+
326
+ def replace(node)
327
+ def_node = node.parent
328
+ param_idx = def_node.parameters.index(node)
329
+ first_default_param = def_node.parameters.detect(&DefNode::DEFAULT_PARAMS)
330
+ first_default_param_idx = def_node.parameters.index(first_default_param)
331
+
332
+ last_default_param = def_node.parameters.reverse.detect(&DefNode::DEFAULT_PARAMS)
333
+ insert_idx = param_idx - first_default_param_idx
334
+
335
+ while param = def_node.parameters[param_idx += 1]
336
+ unless param == def_node.splat || DefaultParamNode === param
337
+ raise UserArgumentError, "can't have regular parameter after default parameter in function #{def_node.name.inspect}"
338
+ end
339
+ end
340
+
341
+ if_expression = construct_if_expression(node)
342
+
343
+ if last_default_param == node
344
+ def_node.parameters.delete_if(&DefNode::DEFAULT_PARAMS)
345
+ def_node.parameters << SPLAT_LITERAL unless def_node.splat
346
+ end
347
+ def_node.expressions.insert(insert_idx, if_expression)
348
+ reestablish_parents(def_node)
349
+ end
350
+
351
+ def construct_if_expression(node)
352
+ get_splat_node = CallNode.new(nil, 'get', [ GetVariableNode.new('a:', '000'), NumberNode.new(0), StringNode.new('rimldefault', :s) ])
353
+ condition_node = BinaryOperatorNode.new('!=#', [ get_splat_node, StringNode.new('rimldefault', :s) ])
354
+ remove_from_splat_node = CallNode.new(nil, 'remove', [ GetVariableNode.new('a:', '000'), NumberNode.new(0) ])
355
+ IfNode.new(condition_node,
356
+ Nodes.new([
357
+ AssignNode.new('=', GetVariableNode.new(nil, node.parameter), remove_from_splat_node),
358
+ ElseNode.new(Nodes.new([
359
+ AssignNode.new('=', GetVariableNode.new(nil, node.parameter), node.expression)
360
+ ]))
361
+ ])
362
+ )
363
+ end
364
+ end
365
+
300
366
  end
301
367
  end
data/lib/compiler.rb CHANGED
@@ -185,6 +185,19 @@ module Riml
185
185
  ContinueNodeVisitor = LiteralNodeVisitor
186
186
  BreakNodeVisitor = LiteralNodeVisitor
187
187
 
188
+ class StringLiteralConcatNodeVisitor < Visitor
189
+ def compile(nodes)
190
+ nodes.each_with_index do |node, i|
191
+ visitor = visitor_for_node(node)
192
+ node.parent_node = nodes
193
+ next_node = nodes.nodes[i+1]
194
+ node.accept(visitor)
195
+ nodes.compiled_output << ' ' if next_node
196
+ end
197
+ nodes.compiled_output
198
+ end
199
+ end
200
+
188
201
  class ListUnpackNodeVisitor < ListNodeVisitor
189
202
  def compile(node)
190
203
  node.compiled_output = super.reverse.sub(',', ';').reverse
@@ -219,7 +232,9 @@ module Riml
219
232
  def set_modifier(node)
220
233
  # Ex: n:myVariable = "override riml default scoping" compiles into:
221
234
  # myVariable = "override riml default scoping"
222
- node.scope_modifier = "" if node.scope_modifier == "n:"
235
+ if node.scope_modifier == "n:"
236
+ node.scope_modifier = ""
237
+ end
223
238
  return node.scope_modifier if node.scope_modifier
224
239
  node.scope_modifier = scope_modifier_for_node(node)
225
240
  end
@@ -495,6 +510,11 @@ module Riml
495
510
  root_node(node).current_compiler.compile_queue << file
496
511
  end
497
512
  elsif node.name == 'riml_include'
513
+ # riml_include has to be top-level
514
+ unless node.parent == root_node(node)
515
+ error_msg = %Q(riml_include error, has to be called at top-level)
516
+ raise IncludeNotTopLevel, error_msg
517
+ end
498
518
  node.each_existing_file! do |file|
499
519
  full_path = File.join(Riml.source_path, file)
500
520
  riml_src = File.read(full_path)
@@ -510,8 +530,10 @@ module Riml
510
530
  end
511
531
 
512
532
  def root_node(node)
513
- node = node.parent until node.parent.nil?
514
- node
533
+ @root_node ||= begin
534
+ node = node.parent until node.parent.nil?
535
+ node
536
+ end
515
537
  end
516
538
  end
517
539
 
@@ -618,6 +640,20 @@ module Riml
618
640
  end
619
641
  end
620
642
 
643
+ class GetVariableByScopeAndDictNameNodeVisitor < Visitor
644
+ def compile(node)
645
+ node.scope_modifier.parent = node
646
+ node.scope_modifier.accept(visitor_for_node(node.scope_modifier))
647
+ node.keys.each do |key|
648
+ key.parent = node
649
+ node.compiled_output << '['
650
+ key.accept(visitor_for_node(key))
651
+ node.compiled_output << ']'
652
+ end
653
+ node.compiled_output
654
+ end
655
+ end
656
+
621
657
  class ClassDefinitionNodeVisitor < Visitor
622
658
  def compile(node)
623
659
  node.expressions.parent_node = node
@@ -645,10 +681,13 @@ module Riml
645
681
  @compile_queue ||= []
646
682
  end
647
683
 
648
- def compile_include(source, from_file = nil)
684
+ def sourced_files_compiled
685
+ @sourced_files_compiled ||= []
686
+ end
687
+
688
+ def compile_include(source, from_file)
649
689
  root_node = parser.parse(source, parser.ast_rewriter, from_file)
650
690
  output = compile(root_node)
651
- return output unless from_file
652
691
  (Riml::INCLUDE_COMMENT_FMT % from_file) + output
653
692
  end
654
693
 
data/lib/constants.rb CHANGED
@@ -30,7 +30,24 @@ module Riml
30
30
  COMPARISON_OPERATORS = IGNORECASE_CAPABLE_OPERATORS.map do |o|
31
31
  [o + '#', o + '?', o]
32
32
  end.flatten
33
- # :h function-list
33
+
34
+ SPLAT_LITERAL = '...'
35
+
36
+ # :help registers
37
+ REGISTERS = [
38
+ '"',
39
+ (0..9).to_a.map(&:to_s),
40
+ '-',
41
+ ('a'..'z').to_a.map(&:to_s),
42
+ ('A'..'Z').to_a.map(&:to_s),
43
+ ':', '.', '%', '#',
44
+ '=',
45
+ '*', '+', '~',
46
+ '_',
47
+ '/'
48
+ ].flatten
49
+
50
+ # :help function-list
34
51
  BUILTIN_FUNCTIONS =
35
52
  %w(
36
53
  abs
data/lib/errors.rb CHANGED
@@ -6,6 +6,10 @@ module Riml
6
6
  CompileError = Class.new(RimlError)
7
7
 
8
8
  FileNotFound = Class.new(RimlError)
9
+ IncludeFileLoop = Class.new(RimlError)
10
+ IncludeNotTopLevel = Class.new(RimlError)
11
+ # bad user arguments to Riml functions
12
+ UserArgumentError = Class.new(RimlError)
9
13
 
10
14
  ClassNotFound = Class.new(RimlError)
11
15
  ClassRedefinitionError = Class.new(RimlError)
data/lib/grammar.y CHANGED
@@ -122,6 +122,8 @@ rule
122
122
  String:
123
123
  STRING_S { result = StringNode.new(val[0], :s) }
124
124
  | STRING_D { result = StringNode.new(val[0], :d) }
125
+ | String STRING_S { result = StringLiteralConcatNode.new(val[0], StringNode.new(val[1], :s)) }
126
+ | String STRING_D { result = StringLiteralConcatNode.new(val[0], StringNode.new(val[1], :d)) }
125
127
  ;
126
128
 
127
129
  Regexp:
@@ -318,6 +320,7 @@ rule
318
320
  VariableRetrieval:
319
321
  Scope IDENTIFIER { result = GetVariableNode.new(val[0], val[1]) }
320
322
  | SPECIAL_VAR_PREFIX IDENTIFIER { result = GetSpecialVariableNode.new(val[0], val[1]) }
323
+ | ScopeModifierLiteral ListOrDictGetWithBrackets { result = GetVariableByScopeAndDictNameNode.new(val[0], val[1]) }
321
324
  ;
322
325
 
323
326
  AllVariableRetrieval:
@@ -374,7 +377,13 @@ rule
374
377
  ParamList:
375
378
  /* nothing */ { result = [] }
376
379
  | IDENTIFIER { result = val }
380
+ | DefaultParam { result = val }
377
381
  | ParamList ',' IDENTIFIER { result = val[0] << val[2] }
382
+ | ParamList ',' DefaultParam { result = val[0] << val[2] }
383
+ ;
384
+
385
+ DefaultParam:
386
+ IDENTIFIER '=' ValueExpression { result = DefaultParamNode.new(val[0], val[2]) }
378
387
  ;
379
388
 
380
389
  Return:
@@ -419,6 +428,7 @@ rule
419
428
  For:
420
429
  FOR IDENTIFIER IN ValueExpression Block END { result = ForNode.new(val[1], val[3], val[4]) }
421
430
  | FOR List IN ValueExpression Block END { result = ForNode.new(val[1], val[3], val[4]) }
431
+ | FOR ListUnpack IN ValueExpression Block END { result = ForNode.new(val[1], val[3], val[4]) }
422
432
  ;
423
433
 
424
434
  Try:
data/lib/lexer.rb CHANGED
@@ -88,8 +88,17 @@ module Riml
88
88
  @token_buf << [:SCOPE_MODIFIER_LITERAL, scope_modifier_literal]
89
89
  elsif special_var_prefix = chunk[/\A(&(\w:)?(?!&)|\$|@)/]
90
90
  @token_buf << [:SPECIAL_VAR_PREFIX, special_var_prefix.strip]
91
- @expecting_identifier = true
92
91
  @i += special_var_prefix.size
92
+ if special_var_prefix == '@'
93
+ new_chunk = get_new_chunk
94
+ next_char = new_chunk[0]
95
+ if REGISTERS.include?(next_char)
96
+ @token_buf << [:IDENTIFIER, next_char]
97
+ @i += 1
98
+ end
99
+ else
100
+ @expecting_identifier = true
101
+ end
93
102
  elsif function_method = chunk[/\A(function)\(/, 1]
94
103
  @token_buf << [:IDENTIFIER, function_method]
95
104
  @i += function_method.size
@@ -153,7 +162,7 @@ module Riml
153
162
  @token_buf << [:NUMBER, hex]
154
163
  @i += hex.size
155
164
  # integer or float (decimal)
156
- elsif decimal = chunk[/\A[0-9]+(\.[0-9]+)?/]
165
+ elsif decimal = chunk[/\A[0-9]+(\.[0-9]+([eE][+-]?[0-9]+)?)?/]
157
166
  @token_buf << [:NUMBER, decimal]
158
167
  @i += decimal.size
159
168
  elsif interpolation = chunk[INTERPOLATION_REGEX]
data/lib/nodes.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require File.expand_path('../constants', __FILE__)
2
+ require File.expand_path('../errors', __FILE__)
2
3
  require 'set'
3
4
 
4
5
  module Visitable
@@ -29,9 +30,8 @@ module Visitable
29
30
  super
30
31
  end
31
32
  end
32
- def respond_to_missing?(method, include_private = false)
33
- return true if method =~ DESCENDANT_OF_REGEX
34
- super
33
+ def respond_to?(method, include_private = false)
34
+ super || method =~ DESCENDANT_OF_REGEX
35
35
  end
36
36
  end
37
37
 
@@ -127,9 +127,8 @@ class Nodes < Struct.new(:nodes)
127
127
  end
128
128
  end
129
129
 
130
- def respond_to_missing?(method, include_private = false)
131
- return true if nodes.respond_to?(method)
132
- super
130
+ def respond_to?(method, include_private = false)
131
+ super || nodes.respond_to?(method, include_private)
133
132
  end
134
133
 
135
134
  def children
@@ -155,6 +154,20 @@ class StringNode < Struct.new(:value, :type) # type: :d or :s for double- or sin
155
154
  include Visitable
156
155
  end
157
156
 
157
+ class StringLiteralConcatNode < Struct.new(:string_nodes)
158
+ include Visitable
159
+ include Walkable
160
+
161
+ def initialize(*string_nodes)
162
+ super(string_nodes)
163
+ end
164
+ alias nodes string_nodes
165
+
166
+ def children
167
+ string_nodes
168
+ end
169
+ end
170
+
158
171
  class RegexpNode < LiteralNode; end
159
172
 
160
173
  class ListNode < LiteralNode
@@ -306,6 +319,14 @@ end
306
319
  # call s:Method(argument1, argument2)
307
320
  class ExplicitCallNode < CallNode; end
308
321
  class RimlCommandNode < CallNode
322
+
323
+ def initialize(*)
324
+ super
325
+ if arguments.empty? || !arguments.all? { |arg| arg.is_a?(StringNode) }
326
+ raise Riml::UserArgumentError, "#{name.inspect} error: must pass string (name of file)"
327
+ end
328
+ end
329
+
309
330
  def each_existing_file!
310
331
  files = []
311
332
  arguments.map(&:value).each do |file|
@@ -316,8 +337,15 @@ class RimlCommandNode < CallNode
316
337
  "source path (#{Riml.source_path.inspect})"
317
338
  end
318
339
  end
340
+ return unless block_given?
319
341
  # all files exist
320
- files.each {|f| yield f} if block_given?
342
+ files.each do |f|
343
+ begin
344
+ yield f
345
+ rescue Riml::IncludeFileLoop
346
+ arguments.delete_if { |arg| arg.value == f }
347
+ end
348
+ end
321
349
  end
322
350
  end
323
351
 
@@ -477,28 +505,30 @@ class DefNode < Struct.new(:bang, :scope_modifier, :name, :parameters, :keyword,
477
505
  def initialize(*args)
478
506
  super
479
507
  # max number of arguments in viml
480
- if parameters.size > 20
481
- raise ArgumentError, "can't have more than 20 parameters for #{full_name}"
508
+ if parameters.reject(&DEFAULT_PARAMS).size > 20
509
+ raise Riml::UserArgumentError, "can't have more than 20 parameters for #{full_name}"
482
510
  end
483
511
  end
484
512
 
485
- SPLAT = lambda {|arg| arg == '...' || arg[0] == "*"}
513
+ SPLAT = lambda {|arg| arg == Riml::Constants::SPLAT_LITERAL || arg[0] == "*"}
514
+ DEFAULT_PARAMS = lambda {|p| DefaultParamNode === p}
486
515
 
487
516
  # ["arg1", "arg2"}
488
517
  def argument_variable_names
489
- @argument_variable_names ||= parameters.reject(&SPLAT)
518
+ parameters.reject(&SPLAT)
490
519
  end
491
520
 
492
521
  # returns the splat argument or nil
493
522
  def splat
494
- @splat ||= begin
495
- parameters.select(&SPLAT).first
496
- end
523
+ parameters.detect(&SPLAT)
497
524
  end
498
525
 
499
526
  def keyword
500
- return super unless name.include?(".")
501
- "dict"
527
+ if name.include?('.')
528
+ 'dict'
529
+ else
530
+ super
531
+ end
502
532
  end
503
533
 
504
534
  def autoload?
@@ -516,8 +546,13 @@ class DefNode < Struct.new(:bang, :scope_modifier, :name, :parameters, :keyword,
516
546
  end
517
547
  end
518
548
 
549
+ def default_param_nodes
550
+ parameters.select(&DEFAULT_PARAMS)
551
+ end
552
+
519
553
  def children
520
- [expressions]
554
+ children = [expressions]
555
+ children.concat(default_param_nodes)
521
556
  end
522
557
 
523
558
  def method_missing(method, *args, &blk)
@@ -529,6 +564,15 @@ class DefNode < Struct.new(:bang, :scope_modifier, :name, :parameters, :keyword,
529
564
  end
530
565
  end
531
566
 
567
+ class DefaultParamNode < Struct.new(:parameter, :expression)
568
+ include Visitable
569
+ include Walkable
570
+
571
+ def children
572
+ [parameter, expression]
573
+ end
574
+ end
575
+
532
576
  class ScopeNode
533
577
  attr_writer :for_node_variable_names, :argument_variable_names
534
578
  attr_accessor :function
@@ -721,6 +765,15 @@ class ListOrDictGetNode < Struct.new(:list_or_dict, :keys)
721
765
  end
722
766
  end
723
767
 
768
+ class GetVariableByScopeAndDictNameNode < Struct.new(:scope_modifier, :keys)
769
+ include Visitable
770
+ include Walkable
771
+
772
+ def children
773
+ [scope_modifier] + keys
774
+ end
775
+ end
776
+
724
777
  class TryNode < Struct.new(:try_block, :catch_nodes, :finally_block)
725
778
  include Visitable
726
779
  include Indentable
@@ -750,7 +803,14 @@ class ClassDefinitionNode < Struct.new(:name, :superclass_name, :expressions)
750
803
 
751
804
  def constructor
752
805
  expressions.detect do |n|
753
- DefNode === n && (n.name == 'initialize' || n.name.match(/Constructor\Z/))
806
+ next(false) unless DefNode === n && (n.name == 'initialize' || n.name.match(/Constructor\Z/))
807
+ if n.instance_of?(DefMethodNode)
808
+ Riml.warn("class #{name.inspect} has an initialize function declared with 'defm'. Please use 'def'.")
809
+ new_node = n.to_def_node
810
+ new_node.keyword = nil
811
+ n.replace_with(new_node)
812
+ end
813
+ true
754
814
  end
755
815
  end
756
816
  alias constructor? constructor