Class: Ibex::Frontend::Resolver
- Inherits:
-
Object
- Object
- Ibex::Frontend::Resolver
- Defined in:
- lib/ibex/frontend/resolver.rb,
sig/ibex/frontend/resolver.rbs
Overview
Loads an explicit root grammar and resolves its extended-mode fragment graph.
Constant Summary collapse
- GLOB_CHARACTERS =
/[*?\[\]{}]/- WINDOWS_ABSOLUTE =
%r{\A(?:[A-Za-z]:[\\/]|\\\\)}
Instance Attribute Summary collapse
- #attempted_paths ⇒ Array[String] readonly
Instance Method Summary collapse
- #canonical_include(include_node) ⇒ String
- #canonical_root ⇒ String
- #dependencies ⇒ Array[String]
- #expand_include(include_node, chain) ⇒ [ Array[AST::declaration], Array[AST::Rule] ]
- #expand_node(node, chain) ⇒ [ Array[AST::declaration], Array[AST::Rule] ]
- #fail_include(include_node, message) ⇒ bot
-
#initialize(path, mode: :default, loader: SourceLoader.new) ⇒ Resolver
constructor
A new instance of Resolver.
- #inside_root?(path) ⇒ Boolean
- #parse_fragment(path) ⇒ AST::Fragment
- #parse_root(path) ⇒ AST::Root
- #prepare_resolution ⇒ void
- #reject_cycle(include_node, target) ⇒ void
- #resolve ⇒ Resolution
- #source_provenance(file) ⇒ IR::source_provenance
-
#source_records ⇒ Array[GenerationInput]
Source bytes actually consumed by the parser, in resolution order.
- #validate_include_path(include_node) ⇒ void
Constructor Details
#initialize(path, mode: :default, loader: SourceLoader.new) ⇒ Resolver
Returns a new instance of Resolver.
27 28 29 30 31 32 33 34 |
# File 'lib/ibex/frontend/resolver.rb', line 27 def initialize(path, mode: :default, loader: SourceLoader.new) raise ArgumentError, "mode must be :default or :extended" unless %i[default extended].include?(mode) @input_path = path @mode = mode @loader = loader @attempted_paths = [File.(path)] end |
Instance Attribute Details
#attempted_paths ⇒ Array[String] (readonly)
24 25 26 |
# File 'lib/ibex/frontend/resolver.rb', line 24 def attempted_paths @attempted_paths end |
Instance Method Details
#canonical_include(include_node) ⇒ String
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
# File 'lib/ibex/frontend/resolver.rb', line 146 def canonical_include(include_node) validate_include_path(include_node) candidate = File.(include_node.path, File.dirname(include_node.loc.file)) @attempted_paths << candidate unless @attempted_paths.include?(candidate) canonical = @loader.canonical_path(candidate) unless @loader.file?(canonical) fail_include(include_node, "include path is not a file: #{include_node.path.inspect}") end unless inside_root?(canonical) = "include resolves outside the root grammar directory: #{include_node.path.inspect}" fail_include(include_node, ) end @loader.record_access(canonical, candidate) canonical rescue Errno::ENOENT, Errno::ENOTDIR fail_include(include_node, "include file does not exist: #{include_node.path.inspect}") rescue SystemCallError => e = "#{include_node.loc}: cannot read include #{include_node.path.inspect}: #{e.}" raise ResolutionIOError, end |
#canonical_root ⇒ String
83 84 85 86 87 88 89 90 91 |
# File 'lib/ibex/frontend/resolver.rb', line 83 def canonical_root path = @loader.canonical_path(@input_path) raise Ibex::Error, "#{@input_path}:1:1: root grammar must be a file" unless @loader.file?(path) @loader.record_access(path, @input_path) path rescue SystemCallError => e raise ResolutionIOError, "#{@input_path}:1:1: cannot read root grammar: #{e.}" end |
#dependencies ⇒ Array[String]
58 59 60 |
# File 'lib/ibex/frontend/resolver.rb', line 58 def dependencies resolve.files end |
#expand_include(include_node, chain) ⇒ [ Array[AST::declaration], Array[AST::Rule] ]
130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/ibex/frontend/resolver.rb', line 130 def (include_node, chain) target = canonical_include(include_node) reject_cycle(include_node, target) return [[], []] if @loaded[target] @files << target @visiting << target fragment = parse_fragment(target) next_chain = chain + [source_provenance(target)] declarations, rules = (fragment, next_chain) @visiting.pop @loaded[target] = true [declarations, rules] end |
#expand_node(node, chain) ⇒ [ Array[AST::declaration], Array[AST::Rule] ]
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
# File 'lib/ibex/frontend/resolver.rb', line 109 def (node, chain) declarations = [] #: Array[AST::declaration] rules = [] #: Array[AST::Rule] node.declarations.each do |declaration| if declaration.is_a?(AST::Include) included_declarations, included_rules = (declaration, chain) declarations.concat(included_declarations) rules.concat(included_rules) else declarations << declaration end end node.rules.each do |rule| @include_chains[rule] = chain rules << rule end [declarations, rules] end |
#fail_include(include_node, message) ⇒ bot
212 213 214 |
# File 'lib/ibex/frontend/resolver.rb', line 212 def fail_include(include_node, ) raise Ibex::Error, "#{include_node.loc}: #{}" end |
#inside_root?(path) ⇒ Boolean
185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/ibex/frontend/resolver.rb', line 185 def inside_root?(path) directory = File.dirname(path) loop do return true if directory == @root_directory parent = File.dirname(directory) return false if parent == directory directory = parent end end |
#parse_fragment(path) ⇒ AST::Fragment
101 102 103 104 105 |
# File 'lib/ibex/frontend/resolver.rb', line 101 def parse_fragment(path) Parser.new(@loader.read(path), file: path, mode: :extended).parse_fragment rescue SystemCallError => e raise ResolutionIOError, "#{path}:1:1: cannot read fragment: #{e.}" end |
#parse_root(path) ⇒ AST::Root
94 95 96 97 98 |
# File 'lib/ibex/frontend/resolver.rb', line 94 def parse_root(path) Parser.new(@loader.read(path), file: path, mode: @mode).parse rescue SystemCallError => e raise ResolutionIOError, "#{path}:1:1: cannot read grammar: #{e.}" end |
#prepare_resolution ⇒ void
This method returns an undefined value.
71 72 73 74 75 76 77 78 79 80 |
# File 'lib/ibex/frontend/resolver.rb', line 71 def prepare_resolution @root_path = canonical_root @root_directory = File.dirname(@root_path) @files = [@root_path] #: Array[String] @visiting = [@root_path] #: Array[String] @loaded = {} #: Hash[String, bool] chains = {} #: Hash[AST::Rule, Array[IR::source_provenance]] @include_chains = chains.compare_by_identity @attempted_paths = [File.(@input_path)] end |
#reject_cycle(include_node, target) ⇒ void
This method returns an undefined value.
198 199 200 201 202 203 204 |
# File 'lib/ibex/frontend/resolver.rb', line 198 def reject_cycle(include_node, target) start = @visiting.index(target) return unless start cycle = @visiting.drop(start) + [target] fail_include(include_node, "include cycle: #{cycle.join(' -> ')}") end |
#resolve ⇒ Resolution
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/ibex/frontend/resolver.rb', line 37 def resolve cached = @resolution return cached if cached prepare_resolution parsed_root = parse_root(@root_path) declarations, rules = (parsed_root, []) root = AST::Root.new( class_name: parsed_root.class_name, superclass: parsed_root.superclass, declarations: declarations, rules: rules, user_code: parsed_root.user_code, loc: parsed_root.loc, extended: parsed_root.extended, cst: parsed_root.cst ) @loaded[@root_path] = true @visiting.pop @resolution = Resolution.new( root: root, root_path: @root_path, root_directory: @root_directory, files: @files, include_chains: @include_chains ) end |
#source_provenance(file) ⇒ IR::source_provenance
207 208 209 |
# File 'lib/ibex/frontend/resolver.rb', line 207 def source_provenance(file) { file: file, root: @root_directory, byte_span: nil } end |
#source_records ⇒ Array[GenerationInput]
Source bytes actually consumed by the parser, in resolution order.
64 65 66 |
# File 'lib/ibex/frontend/resolver.rb', line 64 def source_records @loader.read_records end |
#validate_include_path(include_node) ⇒ void
This method returns an undefined value.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/ibex/frontend/resolver.rb', line 169 def validate_include_path(include_node) path = include_node.path fail_include(include_node, "include path must not be empty") if path.empty? fail_include(include_node, "include path must not contain NUL") if path.include?("\0") if path.start_with?("/") || path.match?(WINDOWS_ABSOLUTE) fail_include(include_node, "include path must be relative") end if path.split(%r{[\\/]}).include?("..") fail_include(include_node, "include path must not contain parent traversal") end return unless path.match?(GLOB_CHARACTERS) fail_include(include_node, "include path must not contain glob metacharacters") end |