Class: Riml::AST_Rewriter

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/ast_rewriter.rb

Defined Under Namespace

Classes: CallToExplicitCall, ClassDefinitionToFunctions, DefaultParamToIfNode, DeserializeVarAssignment, ObjectInstantiationToCall, RegisterDefinedClasses, StrictEqualsComparisonOperator, TopLevelDefMethodToDef, VarEqualsComparisonOperator

Constant Summary

Constants included from Constants

Constants::BUILTIN_COMMANDS, Constants::BUILTIN_FUNCTIONS, Constants::COMPARISON_OPERATORS, Constants::DEFINE_KEYWORDS, Constants::END_KEYWORDS, Constants::IGNORECASE_CAPABLE_OPERATORS, Constants::KEYWORDS, Constants::REGISTERS, Constants::RIML_COMMANDS, Constants::RIML_END_KEYWORDS, Constants::RIML_KEYWORDS, Constants::SPECIAL_VARIABLE_PREFIXES, Constants::SPLAT_LITERAL, Constants::VIML_COMMANDS, Constants::VIML_END_KEYWORDS, Constants::VIML_KEYWORDS

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ast = nil, classes = nil) ⇒ AST_Rewriter

Returns a new instance of AST_Rewriter.



12
13
14
15
16
17
18
19
20
21
22
# File 'lib/ast_rewriter.rb', line 12

def initialize(ast = nil, classes = nil)
  @ast = ast
  @classes = classes || ClassMap.new
  # Keeps track of filenames with their rewritten ASTs, to prevent rewriting
  # the same AST more than once.
  @rewritten_included_and_sourced_files = {}
  # Keeps track of which filenames included/sourced which.
  # ex: { nil => ["main.riml"], "main.riml" => ["lib1.riml", "lib2.riml"],
  # "lib1.riml" => [], "lib2.riml" => [] }
  @included_and_sourced_file_refs = Hash.new { |h, k| h[k] = [] }
end

Instance Attribute Details

#astObject

Returns the value of attribute ast.



9
10
11
# File 'lib/ast_rewriter.rb', line 9

def ast
  @ast
end

#classesObject (readonly)

Returns the value of attribute classes.



10
11
12
# File 'lib/ast_rewriter.rb', line 10

def classes
  @classes
end

#rewritten_included_and_sourced_filesObject (readonly)

Returns the value of attribute rewritten_included_and_sourced_files.



10
11
12
# File 'lib/ast_rewriter.rb', line 10

def rewritten_included_and_sourced_files
  @rewritten_included_and_sourced_files
end

Instance Method Details

#add_SID_function!Object

:h <SID>



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ast_rewriter.rb', line 126

def add_SID_function!
  fchild = ast.nodes.first
  return false if DefNode === fchild && fchild.name == 'SID' && fchild.scope_modifier == 's:'
  fn = DefNode.new('!', nil, 's:', 'SID', [], nil, Nodes.new([
      ReturnNode.new(CallNode.new(nil, 'matchstr', [
        CallNode.new(nil, 'expand', [StringNode.new('<sfile>', :s)]),
        StringNode.new('<SNR>\zs\d\+\ze_SID$', :s)
      ]
      ))
    ])
  )
  fn.parent = ast.nodes
  establish_parents(fn)
  ast.nodes.unshift fn
end

#add_SID_function?(filename) ⇒ Boolean

Add SID function if this is the main file and it has defined classes, or if it included other files and any one of those other files defined classes.

Returns:

  • (Boolean)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/ast_rewriter.rb', line 109

def add_SID_function?(filename)
  return true if ast.children.grep(ClassDefinitionNode).any?
  included_files = @included_and_sourced_file_refs[filename]
  while included_files.any?
    incs = []
    included_files.each do |included_file|
      if (ast = rewritten_included_and_sourced_files[included_file])
        return true if ast.children.grep(ClassDefinitionNode).any?
      end
      incs.concat @included_and_sourced_file_refs[included_file]
    end
    included_files = incs
  end
  false
end

#do_establish_parents(node) ⇒ Object



56
57
58
59
60
# File 'lib/ast_rewriter.rb', line 56

def do_establish_parents(node)
  node.children.each do |child|
    child.parent_node = node if child.respond_to?(:parent_node=)
  end if node.respond_to?(:children)
end

#do_rewrite_on_match(node) ⇒ Object



66
67
68
# File 'lib/ast_rewriter.rb', line 66

def do_rewrite_on_match(node)
  replace node if match?(node)
end

#establish_parents(node) ⇒ Object Also known as: reestablish_parents



51
52
53
# File 'lib/ast_rewriter.rb', line 51

def establish_parents(node)
  Walker.walk_node(node, method(:do_establish_parents))
end

#recursive?Boolean

Returns:

  • (Boolean)


103
104
105
# File 'lib/ast_rewriter.rb', line 103

def recursive?
  true
end

#rewrite(filename = nil, included = false) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/ast_rewriter.rb', line 24

def rewrite(filename = nil, included = false)
  if filename && (rewritten_ast = rewritten_included_and_sourced_files[filename])
    return rewritten_ast
  end
  establish_parents(ast)
  class_registry = RegisterDefinedClasses.new(ast, classes)
  class_registry.rewrite_on_match
  rewrite_included_and_sourced_files!(filename)
  if filename && !included && add_SID_function?(filename)
    add_SID_function!
  end
  rewriters = [
    StrictEqualsComparisonOperator.new(ast, classes),
    VarEqualsComparisonOperator.new(ast, classes),
    ClassDefinitionToFunctions.new(ast, classes),
    ObjectInstantiationToCall.new(ast, classes),
    CallToExplicitCall.new(ast, classes),
    DefaultParamToIfNode.new(ast, classes),
    DeserializeVarAssignment.new(ast, classes),
    TopLevelDefMethodToDef.new(ast, classes)
  ]
  rewriters.each do |rewriter|
    rewriter.rewrite_on_match
  end
  ast
end

#rewrite_included_and_sourced_files!(filename) ⇒ Object

We need to rewrite the included/sourced files before anything else. This is in order to keep track of any classes defined in the included and sourced files (and files included/sourced in those, etc…). We keep a cache of rewritten asts because the included/sourced files are parsed more than once. They’re parsed first in this step, plus whenever the compiler visits a ‘riml_include’/‘riml_source’ node in order to compile it on the spot.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/ast_rewriter.rb', line 76

def rewrite_included_and_sourced_files!(filename)
  old_ast = ast
  ast.children.each do |node|
    next unless RimlCommandNode === node
    action = node.name == 'riml_include' ? 'include' : 'source'

    node.each_existing_file! do |file, fullpath|
      if filename && @included_and_sourced_file_refs[file].include?(filename)
        msg = "#{filename.inspect} can't #{action} #{file.inspect}, as " \
              " #{file.inspect} already included/sourced #{filename.inspect}"
        # IncludeFileLoop/SourceFileLoop
        raise Riml.const_get("#{action.capitalize}FileLoop"), msg
      elsif filename == file
        raise UserArgumentError, "#{file.inspect} can't include itself"
      end
      @included_and_sourced_file_refs[filename] << file
      riml_src = File.read(fullpath)
      # recursively parse included files with this ast_rewriter in order
      # to pick up any classes that are defined there
      rewritten_ast = Parser.new.parse(riml_src, self, file, action == 'include')
      rewritten_included_and_sourced_files[file] = rewritten_ast
    end
  end
ensure
  self.ast = old_ast
end

#rewrite_on_match(node = ast) ⇒ Object



62
63
64
# File 'lib/ast_rewriter.rb', line 62

def rewrite_on_match(node = ast)
  Walker.walk_node(node, method(:do_rewrite_on_match), lambda { |_| recursive? })
end