riml 0.2.0 → 0.2.1

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