require_relative '../artefact.rb' require_relative '../artefact_engine.rb' module Comfpile class ParserArtefact < Comfpile::Artefact attr_reader :parsed_parameters attr_reader :parsed_dependencies def initialize(*args, search_regexes: nil, **opts) super(*args, **opts) @search_regexes = search_regexes @parsed_parameters = {} @parsed_dependencies = {} parent_artefact :sourcefile, @target add_step do sourcefile_parse_step sourcefile_dependency_step end end # Attempts to find the set of values for a given # parameter key. # # @param [String, Array] keys Key, or array of keys, to look for # # @return [Array] Array of values found for this key # def find_parsed_parameters(keys) if keys.is_a? Array output = [] keys.each do |key| output += @parsed_parameters[key] || [] end output else @parsed_parameters[keys] || [] end end # Try to find a relative or absolute source file # artefact. # Used to resolve the include, require and reference # statements and find relative and/or absolute source # files with matching name. Similar to how C++'s # include statement it will check relative, then # globally. # # @param [String, Array] name Path of the sourcefile to look for # @param [Boolean] optional If true, will also check for absolute # source file, and will discard the file if it can't be found. # Useful for e.g. C/C++ header files that may not be in the search # path of comfpile. # # @return [String] Resolved path of the sourcefile. Relative # if a relative file is found, else absolute. # def resolve_sourcefile(name, optional: false) if(name.is_a? Array) name.map do |n| resolve_sourcefile(n, optional: optional) end.compact else own_dir = File.dirname(@target) relative_file = File.join(own_dir, name).gsub(/^\.\//, '') if not craft_artefact(:sourcefile, relative_file).nil? relative_file elsif not craft_artefact(:sourcefile, name).nil? name elsif not optional relative_file else nil end end end def sourcefile_parse_step log "Parsing file #{@target}..." param_count = 0 File.readlines(@parent_artefact[:file]).each do |line| @search_regexes.each do |r| next if r[:regex].nil? match = r[:regex].match line next if match.nil? match = match.named_captures key = match['key'] || r[:key] value = match['value'] || r[:value] next if key.nil? log "Found k/v pair: '#{key}' '#{value}'", :debug param_count += 1 @parsed_parameters[key] ||= [] @parsed_parameters[key] << value end end log "Parsing completed, found #{param_count} parameters!" end def sourcefile_dependency_step ['include', 'require', 'reference'].map do |key| key_artefacts = resolve_sourcefile(find_parsed_parameters(key), optional: key == 'include') @parsed_dependencies[key] = key_artefacts.map { |key| craft_artefact(:parsed, key) } end log "Generated dependencies. #{@parsed_dependencies.map { |k, v| "#{k}: #{v.size}" }.join(', ')}." end end included_artefacts.each { |a| include_artefact :parsed, a, wait_if_exists: false } required_artefacts.each { |a| require_artefact :parsed, a, wait_if_exists: false } referenced_artefacts.each { |a| reference_artefact :parsed, a, wait_if_exists: false, required: true } log "Generated dependencies. Included #{included_artefacts.size}, required #{required_artefacts.size} and referenced #{referenced_artefacts.size}." end end class ParserEngine < ArtefactEngine # # Initialize a new ParserEngine # # This engine has the task of generating single-file parser artefacts, # which check for comfpile parameters that will be extracted from the sourcefile. # Each file will recursively craft other files it requires and add them, either as # fully included, required, or referenced file. # # @param [] core # @param [] **options # def initialize(core, **options) super(core, **options) @input_file_regex = options[:file_regex] @search_regexes = options[:search_regexes] raise ArgumentError, "Missing input file regex!" unless @input_file_regex.is_a? Regexp raise ArgumentError, "Missing regex parsing array!" unless @search_regexes.is_a? Array end def generate_parser_artefact(stage, target) match = @input_file_regex.match target return nil if match.nil? ParserArtefact.new(@core, self, stage, target, search_regexes: @search_regexes) end def craft(stage, target) case stage when :parsed generate_parser_artefact(stage, target) when :dependency_list nil else nil end end end end