class DakeAnalyzer

Attributes

file_target_dict[R]
file_template_dict[R]
included_files[R]
method_dict[R]
step_template_dict[R]
tag_target_dict[R]
tag_template_dict[R]
variable_dict[R]
workflow[R]

Public Class Methods

new(workflow, inclusion_stack, env={}) click to toggle source
# File lib/dake/analyzer.rb, line 9
def initialize(workflow, inclusion_stack, env={})
  @workflow = workflow
  @inclusion_stack = inclusion_stack
  @included_files = inclusion_stack.to_set
  @variable_dict = env.dup
  @method_dict = {}
  @tag_target_dict = {}
  @file_target_dict = {}
  @tag_template_dict = {}
  @file_template_dict = {}
  @step_template_dict = {}
end

Public Instance Methods

analyze() click to toggle source
# File lib/dake/analyzer.rb, line 168
def analyze
  @workflow.tasks.each do |task|
    case task
    when VariableDef
      var_name = task.var_name.to_s
      var_value = text_eval(task.var_value)
      if task.type == :assign
        @variable_dict[var_name] = var_value
      elsif not @variable_dict[var_name]
        @variable_dict[var_name] = var_value
      end
    when Step
      analyze_step(task)
    when StepMethod
      analyze_method(task)
    when Workflow, Inclusion, Condition
      case task
      when Inclusion
        file_names = text_eval(task.files).split("\n")
        file_names.each do |file_name|
          src_dirname = File.dirname(@inclusion_stack[-1])
          path = file_name.start_with?('/') ? file_name : File.absolute_path(file_name, src_dirname)
          if @included_files.include? path
            line, column = text_line_and_column task.files
            raise "Cyclical inclusion detected in #{task.src_file} at #{line}:#{column}"
          else
            @inclusion_stack.push path
          end
          begin
            tree = DakeParser.new.parse(File.read(path))
          rescue Parslet::ParseFailed => failure
            line, column = text_line_and_column task.files
            STDERR.puts "Failed parsing #{path} included from #{task.src_file} at #{line}:#{column}"
            raise failure.message
          end
          workflow = DakeTransform.new.apply(tree, src_file: path)
          if task.type == :include or task.type == :call
            sub_workflow = DakeAnalyzer.new(workflow, @inclusion_stack, @variable_dict).analyze
          else
            sub_workflow = DakeAnalyzer.new(workflow, @inclusion_stack, 'BASE' => File.dirname(path)).analyze
          end
          @inclusion_stack.pop
        end
      when Condition
        body = analyze_condition(task)
        next unless body
        sub_workflow = DakeAnalyzer.new(body, @inclusion_stack, @variable_dict).analyze
      when Workflow # for scope
        sub_workflow = DakeAnalyzer.new(task, @inclusion_stack, @variable_dict).analyze
      end
      @tag_target_dict.merge! sub_workflow.tag_target_dict do |tag, step_list1, step_list2|
        step_list1 + step_list2
      end
      @file_target_dict.merge! sub_workflow.file_target_dict do |file, step1, step2|
          file1 = step1.targets.find { |target| target.scheme.path == file }
          file2 = step2.targets.find { |target| target.scheme.path == file }
          line1, column1 = text_line_and_column file1.name
          line2, column2 = text_line_and_column file2.name
          file2_name = file2.scheme.path
          raise "Output file `#{file2_name}' defined in #{step2.src_file} at #{line2}:#{column2} " +
                "was previously defined in #{step1.src_file} at #{line1}:#{column1}."
        end
      @file_template_dict.merge! sub_workflow.file_template_dict do |file, (scheme1, step1), (scheme2, step2)|
        file1 = step1.targets.find { |target| target.scheme.path == file }
        file2 = step2.targets.find { |target| target.scheme.path == file }
        line1, column1 = text_line_and_column file1.name
        line2, column2 = text_line_and_column file2.name
        file2_name = file2.scheme.path
        raise "Output pattern `#{file2_name}' defined in #{step2.src_file} at #{line2}:#{column2} " +
                  "was previously defined in #{step1.src_file} at #{line1}:#{column1}."
      end
      if task.is_a? Condition or (task.is_a? Inclusion and task.type == :include)
        @variable_dict.merge! sub_workflow.variable_dict
      end
    end
  end
  self
end
analyze_condition(condition) click to toggle source
# File lib/dake/analyzer.rb, line 147
def analyze_condition(condition)
  case condition.cond
  when EqCond
    lhs = text_eval condition.cond.eq_lhs
    rhs = text_eval condition.cond.eq_rhs
    if condition.not
      truth = lhs != rhs
    else
      truth = lhs == rhs
    end
  when DefCond
    var_name = condition.cond.var_name.to_s
    if condition.not
      truth = (not @variable_dict.has_key? var_name)
    else
      truth = @variable_dict.has_key? var_name
    end
  end
  truth ? condition.if_body : condition.else_body
end
analyze_file(file, type, step) click to toggle source
# File lib/dake/analyzer.rb, line 72
def analyze_file(file, type, step)
  line, column = text_line_and_column(file.name)
  if file.flag == '?' and type == :targets
    file_name = text_eval(file.name)
    raise "Output file `#{file_name}' in #{step.src_file} at #{line}:#{column} should not be optional."
  end
  if file.tag
    tag_name = text_eval(file.name, step.src_file, step.context)
    if file.regex
      if type == :prerequisites
        raise "Pattern `#{file_name}' in #{step.src_file} at #{line}:#{column} cannot be used in input file list."
      end
      file.scheme = DakeScheme::Regex.new('^', tag_name, step)
      if type == :targets
        @tag_template_dict[file.scheme.path] ||= []
        @tag_template_dict[file.scheme.path] << step
      end
    else
      file.scheme = DakeScheme::Tag.new('@', tag_name, step)
      if type == :targets
        @tag_target_dict[file.scheme.path] ||= []
        @tag_target_dict[file.scheme.path] << step
      end
    end
    return [file]
  else
    # there maybe more than one file if a file list is generated in command substitution
    file_names = text_eval(file.name, step.src_file, step.context).split("\n")
    files = file_names.map do |file_name|
      if file.regex
        scheme = DakeScheme::Regex.new('^', file_name, step)
      else
        scheme = analyze_scheme(file_name, step, line, column)
      end
      if file_names.length == 1
        file_path = scheme.path
        if type == :targets
          if file.regex
            if @file_template_dict[file_path]
              raise "Output pattern `#{file_name}' in #{step.src_file} at #{line}:#{column} appears in more than one step."
            else
              @file_template_dict[file_path] = step
            end
          else
            if @file_target_dict[file_path]
              raise "Output file `#{file_name}' in #{step.src_file} at #{line}:#{column} appears in more than one step."
            else
              @file_target_dict[file_path] = step
            end
          end
        else
          if file.regex
            raise "Pattern `#{file_name}' in #{step.src_file} at #{line}:#{column} cannot be used in input file list."
          end
        end
      else
        # Generated file list should not be used in targets
        # if type == :targets
        #   raise "File list `#{file_name}' in #{step.src_file} at #{line}:#{column} cannot be used as targets."
        # end
      end
      newfile = file.dup
      newfile.scheme = scheme
      newfile
    end
    return files
  end
end
analyze_method(meth) click to toggle source
# File lib/dake/analyzer.rb, line 48
def analyze_method(meth)
  if @method_dict[meth.name.to_s]
    line, column = @method_dict[meth.name.to_s].name.line_and_column
    raise "Method `#{meth.name.to_s}' has already beed defined in " +
          "#{@method_dict[meth.name.to_s].src_file} at #{line}:#{column}"
  end
  meth.context = @variable_dict.dup
  analyze_option(meth)
  @method_dict[meth.name.to_s] = meth
end
analyze_option(step) click to toggle source
# File lib/dake/analyzer.rb, line 22
def analyze_option(step) # method is the same as step
  step.option_dict = {}
  step.options.each do |option|
    name = option.name.to_s
    if option.value.is_a? Parslet::Slice
      value = option.value.to_s
      value = 'true' if value == '+'
      value = 'false' if value == '-'
    else
      value = text_eval(option.value, step.src_file, step.context)
    end
    if step.option_dict[name]
      line, column = option.name.line_and_column
      raise "Option `#{name}' in #{step.src_file} at #{line}:#{column} has already been set."
    else
      if name == 'protocol' and not DakeProtocol::ProtocolDict.keys.include? value
        line, column = option.value.line_and_column
        raise "Protocol `#{value}' in #{step.src_file} at #{line}:#{column} is not supported."
      end
      # TODO: should have more option check
      step.option_dict[name] = value
    end
  end
  step.option_dict['protocol'] = 'shell' unless step.option_dict['protocol']
end
analyze_scheme(name, step, line, column) click to toggle source
# File lib/dake/analyzer.rb, line 59
def analyze_scheme(name, step, line, column)
  scheme_part, path_part = name.match(/(\w+:)?(.*)/).to_a[1, 2]
  scheme_cls = DakeScheme::SchemeDict[scheme_part ? scheme_part : 'local:']
  if step
    raise "Scheme `#{scheme_part}' in #{step.src_file} at #{line}:#{column} is not supported." unless scheme_cls
    scheme_cls.new(scheme_part, path_part, step)
  else # for user input target name
    raise "Scheme `#{scheme_part}' in #{name} is not supported." unless scheme_cls
    dummy_step = Step.new([], [], [], {}, nil, nil, @variable_dict, nil, nil)
    scheme_cls.new(scheme_part, path_part, dummy_step)
  end
end
analyze_step(step) click to toggle source
# File lib/dake/analyzer.rb, line 141
def analyze_step(step)
  step.context = @variable_dict.dup
  # the analysis of prerequisites is deferred to the resolve phase
  step.targets.map! { |file| analyze_file(file, :targets, step) }.flatten!
end
option_line_and_column(options, option_name) click to toggle source
# File lib/dake/analyzer.rb, line 257
def option_line_and_column(options, option_name)
  options.each do |option|
    next unless option.name.to_s == option_name
    if option.value.is_a? Parslet::Slice
      return option.value.line_and_column
    else
      return text_line_and_column option.value
    end
  end
  nil
end
print_target(target, tag, target_step, dep_graph = nil, pad_stack = []) click to toggle source
step_line_and_column(step) click to toggle source
# File lib/dake/analyzer.rb, line 247
def step_line_and_column(step)
  return step.name.line_and_column if step.is_a? StepMethod
  first_target = step.targets[0]
  if first_target.name.is_a? Parslet::Slice
      first_target.name.line_and_column
  else
          text_line_and_column(first_target.name)
        end
end
text_eval(text, src_file=@inclusion_stack[-1], context=@variable_dict, skip=0) click to toggle source

src_file is needed by prepare_command in execution phase because @inclusion_stack will have only root workflow by then

# File lib/dake/analyzer.rb, line 345
def text_eval(text, src_file=@inclusion_stack[-1], context=@variable_dict, skip=0)
  text.items[skip..-1].reduce('') do |string, item|
    case item
    when Chars
      string.concat item.string.to_s
    when VariableSub
      var_name = item.var_name.to_s
      var_value = context[var_name]
      if var_value
        string.concat var_value
      else
        line, column = item.var_name.line_and_column
        raise "Variable `#{var_name}' in #{src_file} at #{line}:#{column} is not defined."
      end
    when CommandSub
      command = text_eval(item.cmd_text, src_file, context)
      stdout, stderr, status = Open3.capture3(command, :chdir=>@variable_dict['BASE'])
      if status.success?
        string.concat stdout.chomp
      else
        line, column = text_line_and_column item.cmd_text
        raise "Command `#{command}' in #{src_file} at #{line}:#{column} failed " +
               "with EXIT_STATUS:#{status.exitstatus} and STDERR:\n#{stderr}"
      end
    else # For Escaped Char
      string.concat item.to_s
    end
    string
  end
end
text_line_and_column(text) click to toggle source
# File lib/dake/analyzer.rb, line 269
def text_line_and_column(text)
  first_item = text.items[0]
  case first_item
  when Chars then first_item.string.line_and_column
  when VariableSub
    line, column = first_item.var_name.line_and_column
    [line, column - 2]
  when CommandSub
    line, column = text_line_and_column first_item.cmd_text
    [line, column - 2]
  end
end