comfpile-old/lib/comfpile/engines/parser_engine.rb

173 lines
No EOL
5.9 KiB
Ruby

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<String>] keys Key, or array of keys, to look for
#
# @return [Array<String>] 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<String>] 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 [<Type>] core <description>
# @param [<Type>] **options <description>
#
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