class Riml::AST_Rewriter
Attributes
ast[RW]
classes[R]
options[RW]
Public Class Methods
new(ast = nil, classes = nil, class_dependency_graph = nil)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 14 def initialize(ast = nil, classes = nil, class_dependency_graph = nil) @ast = ast @classes = classes @class_dependency_graph = class_dependency_graph # only the top-most rewriter has these properties and defaults if self.instance_of?(Riml::AST_Rewriter) @classes ||= ClassMap.new # AST_Rewriter shares options with Parser. Parser set AST_Rewriter's # options before call to `rewrite`. @options = nil # 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] = [] } @class_dependency_graph ||= ClassDependencyGraph.new @resolving_class_dependencies = nil end end
Public Instance Methods
add_SID_function!()
click to toggle source
:h <SID>
# File lib/riml/ast_rewriter.rb, line 289 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([ IfNode.new( CallNode.new(nil, 'exists', [StringNode.new('s:SID_VALUE', :s)]), Nodes.new([ ReturnNode.new(GetVariableNode.new('s:', 'SID_VALUE')) ]) ), AssignNode.new( '=', GetVariableNode.new('s:', 'SID_VALUE'), CallNode.new(nil, 'matchstr', [ CallNode.new(nil, 'expand', [StringNode.new('<sfile>', :s)]), StringNode.new('<SNR>\zs\d\+\ze_SID$', :s) ] )), ReturnNode.new(GetVariableNode.new('s:', 'SID_VALUE')) ]) ) fn.parent = ast.nodes establish_parents(fn) ast.nodes.unshift fn end
add_SID_function?(filename)
click to toggle source
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.
# File lib/riml/ast_rewriter.rb, line 272 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)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 79 def do_establish_parents(node) node.children.each do |child| child.parent_node = node if Visitable === child end if Visitable === node end
do_rewrite_on_match(node)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 89 def do_rewrite_on_match(node) replace node if match?(node) end
establish_parents(node)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 74 def establish_parents(node) Walker.walk_node(node, method(:do_establish_parents)) end
Also aliased as: reestablish_parents
max_recursion_lvl()
click to toggle source
recurse until no more children
# File lib/riml/ast_rewriter.rb, line 266 def max_recursion_lvl -1 end
reorder_includes_based_on_class_dependencies!()
click to toggle source
# File lib/riml/ast_rewriter.rb, line 132 def reorder_includes_based_on_class_dependencies! global_included_filename_order = @class_dependency_graph.filename_order asts = [ast] while (ast = asts.shift) include_nodes = ast.children.grep(RimlFileCommandNode).select do |n| n.name == 'riml_include' end included_filenames = include_nodes.map { |n| n.arguments.map(&:value) }.flatten new_order_filenames = global_included_filename_order & included_filenames add_to_head = included_filenames - new_order_filenames new_order_filenames = add_to_head + new_order_filenames include_nodes.each do |node| node.arguments.each do |arg| if (included_file_ast = @included_ASTs_by_include_file[arg.value]) asts << included_file_ast end if new_order_filenames.first arg.value = new_order_filenames.shift else # for now, just to be cautious raise "Internal error in AST rewriting process. Please report bug!" end end end end end
resolve_class_dependencies!(filename)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 98 def resolve_class_dependencies!(filename) if @resolving_class_dependencies.nil? start_resolving = @resolving_class_dependencies = true @included_ASTs_by_include_file = {} end old_ast = ast RegisterClassDependencies.new(ast, classes, @class_dependency_graph, filename).rewrite_on_match ast.children.grep(RimlFileCommandNode).each do |node| next unless node.name == 'riml_include' node.each_existing_file! do |file, fullpath| if filename && @included_and_sourced_file_refs[file].include?(filename) msg = "#{filename.inspect} can't include #{file.inspect}, as " \ " #{file.inspect} already included #{filename.inspect}" error = IncludeFileLoop.new(msg, node) raise error elsif filename == file error = UserArgumentError.new("#{file.inspect} can't include itself", node) raise error end @included_and_sourced_file_refs[filename] << file riml_src = File.read(fullpath) Parser.new.tap { |p| p.options = @options }.parse(riml_src, self, file, true) @included_ASTs_by_include_file[file] = Parser.ast_cache[file] end end ensure self.ast = old_ast if start_resolving == true @resolving_class_dependencies = false @included_and_sourced_file_refs.clear reorder_includes_based_on_class_dependencies! end end
resolve_class_dependencies?()
click to toggle source
# File lib/riml/ast_rewriter.rb, line 93 def resolve_class_dependencies? @resolving_class_dependencies = false if @options[:include_reordering] != true @resolving_class_dependencies != false end
rewrite(filename = nil, included = false)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 36 def rewrite(filename = nil, included = false) if filename && (rewritten_ast = Riml.rewritten_ast_cache[filename]) return rewritten_ast end establish_parents(ast) if @options && @options[:allow_undefined_global_classes] && !@classes.has_global_import? @classes.globbed_imports.unshift(ImportedClass.new('*')) end class_imports = RegisterImportedClasses.new(ast, classes) class_imports.rewrite_on_match if resolve_class_dependencies? resolve_class_dependencies!(filename) return if @resolving_class_dependencies == true end 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), SplatsToCallFunctionInCallingContext.new(ast, classes) ] rewriters.each do |rewriter| rewriter.rewrite_on_match end ast end
rewrite_included_and_sourced_files!(filename)
click to toggle source
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.
# File lib/riml/ast_rewriter.rb, line 209 def rewrite_included_and_sourced_files!(filename) old_ast = ast ast.children.grep(RimlFileCommandNode).each do |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 error = Riml.const_get("#{action.capitalize}FileLoop").new(msg, node) raise error elsif filename == file error = UserArgumentError.new("#{file.inspect} can't #{action} itself", node) raise error end @included_and_sourced_file_refs[filename] << file # recursively parse included files with this ast_rewriter in order # to pick up any classes that are defined there rewritten_ast = nil watch_for_class_pickup do rewritten_ast = Riml.rewritten_ast_cache.fetch(file) do riml_src = File.read(fullpath) Parser.new.tap { |p| p.options = @options }. parse(riml_src, self, file, action == 'include') end end @rewritten_included_and_sourced_files[file] ||= rewritten_ast end end ensure self.ast = old_ast end
rewrite_on_match(node = ast)
click to toggle source
# File lib/riml/ast_rewriter.rb, line 85 def rewrite_on_match(node = ast) Walker.walk_node(node, method(:do_rewrite_on_match), max_recursion_lvl) end
watch_for_class_pickup() { || ... }
click to toggle source
# File lib/riml/ast_rewriter.rb, line 243 def watch_for_class_pickup before_class_names = @classes.class_names ast = yield after_class_names = @classes.class_names diff_class_names = after_class_names - before_class_names class_diff = diff_class_names.inject({}) do |hash, class_name| hash[class_name] = @classes[class_name] hash end # no classes were picked up, it could be that the cache was hit. Let's # register the cached classes for this ast, if there are any if class_diff.empty? real_diff = Riml.rewritten_ast_cache.fetch_classes_registered(ast) real_diff.each do |k,v| @classes[k] = v unless @classes.safe_fetch(k) end # new classes were picked up, let's save them with this ast as the key else Riml.rewritten_ast_cache.save_classes_registered(ast, class_diff) end end