Class: Riml::AST_Rewriter
- Inherits:
-
Object
- Object
- Riml::AST_Rewriter
- Includes:
- Constants
- Defined in:
- lib/riml/ast_rewriter.rb
Direct Known Subclasses
CallToExplicitCall, ClassDefinitionToFunctions, ClassDefinitionToFunctions::DefNodeToPrivateFunction, ClassDefinitionToFunctions::ExtendObjectWithMethods, ClassDefinitionToFunctions::InitializeSuperToObjectExtension, ClassDefinitionToFunctions::InsertInitializeMethod, ClassDefinitionToFunctions::PrivateFunctionCallToPassObjExplicitly, ClassDefinitionToFunctions::RegisterPrivateFunctions, ClassDefinitionToFunctions::SelfToDictName, ClassDefinitionToFunctions::SelfToDictNameInAssignments, ClassDefinitionToFunctions::SelfToObjArgumentInPrivateFunction, ClassDefinitionToFunctions::SuperToSuperclassFunction, DefaultParamToIfNode, DefaultParamToIfNode::SplatVarToCopiedSplatVar, DeserializeVarAssignment, ObjectInstantiationToCall, RegisterClassDependencies, RegisterDefinedClasses, RegisterImportedClasses, SplatsToCallFunctionInCallingContext, StrictEqualsComparisonOperator, TopLevelDefMethodToDef, VarEqualsComparisonOperator
Defined Under Namespace
Classes: CallToExplicitCall, ClassDefinitionToFunctions, DefaultParamToIfNode, DeserializeVarAssignment, ObjectInstantiationToCall, RegisterClassDependencies, RegisterDefinedClasses, RegisterImportedClasses, SplatsToCallFunctionInCallingContext, StrictEqualsComparisonOperator, TopLevelDefMethodToDef, VarEqualsComparisonOperator
Constant Summary
Constants included from Constants
Constants::BUILTIN_COMMANDS, Constants::BUILTIN_FUNCTIONS, Constants::COMPARISON_OPERATORS, Constants::COMPILED_STRING_LOCATION, Constants::DEFINE_KEYWORDS, Constants::END_KEYWORDS, Constants::IGNORECASE_CAPABLE_OPERATORS, Constants::KEYWORDS, Constants::REGISTERS, Constants::RIML_CLASS_COMMANDS, Constants::RIML_COMMANDS, Constants::RIML_END_KEYWORDS, Constants::RIML_FILE_COMMANDS, Constants::RIML_KEYWORDS, Constants::SPECIAL_VARIABLE_PREFIXES, Constants::SPLAT_LITERAL, Constants::UNKNOWN_LOCATION_INFO, Constants::VIML_COMMANDS, Constants::VIML_END_KEYWORDS, Constants::VIML_KEYWORDS
Instance Attribute Summary collapse
-
#ast ⇒ Object
Returns the value of attribute ast.
-
#classes ⇒ Object
readonly
Returns the value of attribute classes.
-
#options ⇒ Object
Returns the value of attribute options.
Instance Method Summary collapse
-
#add_SID_function! ⇒ Object
:h <SID>.
-
#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.
- #do_establish_parents(node) ⇒ Object
- #do_rewrite_on_match(node) ⇒ Object
- #establish_parents(node) ⇒ Object (also: #reestablish_parents)
-
#initialize(ast = nil, classes = nil, class_dependency_graph = nil) ⇒ AST_Rewriter
constructor
A new instance of AST_Rewriter.
-
#max_recursion_lvl ⇒ Object
recurse until no more children.
- #reorder_includes_based_on_class_dependencies! ⇒ Object
- #resolve_class_dependencies!(filename) ⇒ Object
- #resolve_class_dependencies? ⇒ Boolean
- #rewrite(filename = nil, included = false) ⇒ Object
-
#rewrite_included_and_sourced_files!(filename) ⇒ Object
We need to rewrite the included/sourced files before anything else.
- #rewrite_on_match(node = ast) ⇒ Object
- #watch_for_class_pickup ⇒ Object
Constructor Details
#initialize(ast = nil, classes = nil, class_dependency_graph = nil) ⇒ AST_Rewriter
Returns a new instance of AST_Rewriter.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# 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 |
Instance Attribute Details
#ast ⇒ Object
Returns the value of attribute ast.
11 12 13 |
# File 'lib/riml/ast_rewriter.rb', line 11 def ast @ast end |
#classes ⇒ Object (readonly)
Returns the value of attribute classes.
12 13 14 |
# File 'lib/riml/ast_rewriter.rb', line 12 def classes @classes end |
#options ⇒ Object
Returns the value of attribute options.
11 12 13 |
# File 'lib/riml/ast_rewriter.rb', line 11 def @options end |
Instance Method Details
#add_SID_function! ⇒ Object
:h <SID>
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# 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) ⇒ 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.
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# 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) ⇒ Object
79 80 81 82 83 |
# 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) ⇒ Object
89 90 91 |
# File 'lib/riml/ast_rewriter.rb', line 89 def do_rewrite_on_match(node) replace node if match?(node) end |
#establish_parents(node) ⇒ Object Also known as: reestablish_parents
74 75 76 |
# File 'lib/riml/ast_rewriter.rb', line 74 def establish_parents(node) Walker.walk_node(node, method(:do_establish_parents)) end |
#max_recursion_lvl ⇒ Object
recurse until no more children
266 267 268 |
# File 'lib/riml/ast_rewriter.rb', line 266 def max_recursion_lvl -1 end |
#reorder_includes_based_on_class_dependencies! ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# 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) ⇒ Object
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
# 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 }.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? ⇒ Boolean
93 94 95 96 |
# 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) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# 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) ⇒ 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.
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 |
# 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 }. 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) ⇒ Object
85 86 87 |
# 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 ⇒ Object
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# 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 |