class ReVIEW::Preprocessor

Constants

INF_INDENT
KNOWN_DIRECTIVES
TYPES

Public Class Methods

new(param) click to toggle source
# File lib/review/preprocessor.rb, line 26
def initialize(param)
  @repository = ReVIEW::Preprocessor::Repository.new(param)
  @config = param ## do not use params in this class; only used in Repository
  @logger = ReVIEW.logger
  @leave_content = nil
end

Public Instance Methods

process(path) click to toggle source
# File lib/review/preprocessor.rb, line 33
def process(path)
  File.open(path) do |inf|
    @inf = inf
    @f = StringIO.new
    begin
      preproc(@inf)
    rescue Errno::ENOENT => e
      error! e.message
    end
    @f.string
  end
end

Private Instance Methods

defvar(name, value) click to toggle source
# File lib/review/preprocessor.rb, line 206
def defvar(name, value)
  @vartable[name] = value
end
evaluate(path, chunk) click to toggle source
# File lib/review/preprocessor.rb, line 228
def evaluate(path, chunk)
  outputs = get_output("ruby #{path}", false).split("\n").map(&:strip)
  chunk.map do |line|
    if /\# \$\d+/ =~ line.string
      # map result into source.
      line.edit { |s| s.sub(/\$(\d+)/) { outputs[$1.to_i - 1] } }
    else
      line
    end
  end
end
expand(str) click to toggle source
# File lib/review/preprocessor.rb, line 210
def expand(str)
  str.gsub(/\$\w+/) do |name|
    s = @vartable[name.sub('$', '')]
    s ? expand(s) : name
  end
end
filename() click to toggle source
# File lib/review/preprocessor.rb, line 263
def filename
  @inf.path
end
get_output(cmd, use_stderr) click to toggle source
# File lib/review/preprocessor.rb, line 240
def get_output(cmd, use_stderr)
  out = err = nil
  Open3.popen3(cmd) do |_stdin, stdout, stderr|
    out = stdout.readlines
    if use_stderr
      out.concat(stderr.readlines)
    else
      err = stderr.readlines
    end
  end
  if err && !err.empty?
    $stderr.puts '[unexpected stderr message]'
    err.each { |line| $stderr.print line }
    app_error 'get_output: got unexpected output'
  end
  num = 0
  out.map { |line| Line.new(num += 1, line) }
end
known_directive?(op) click to toggle source
# File lib/review/preprocessor.rb, line 116
def known_directive?(op)
  KNOWN_DIRECTIVES.index(op)
end
lineno() click to toggle source
# File lib/review/preprocessor.rb, line 267
def lineno
  @inf.lineno
end
location() click to toggle source
# File lib/review/preprocessor.rb, line 259
def location
  "#{filename}:#{lineno}"
end
minimum_indent(chunk) click to toggle source
# File lib/review/preprocessor.rb, line 223
def minimum_indent(chunk)
  n = chunk.map { |line| line.empty? ? INF_INDENT : line.num_indent }.min
  n == INF_INDENT ? 0 : n
end
optarg_value(spec) click to toggle source
# File lib/review/preprocessor.rb, line 191
def optarg_value(spec)
  case spec
  when 'true' # [name=true], [name]
    true
  when 'false' # [name=false]
    false
  when 'nil' # [name=nil]
    nil
  when /^\d+$/ # [name=8]
    $&.to_i
  else # [name=val]
    spec
  end
end
parse_directive(line, argc, *optdecl) click to toggle source
# File lib/review/preprocessor.rb, line 158
def parse_directive(line, argc, *optdecl)
  m = /\A\#@(\w+)\((.*?)\)(?:\[(.*?)\])?\z/.match(line.strip) or
    app_error "wrong directive: #{line.strip}"
  op = m[1]
  args = m[2].split(/,\s*/)
  opts = parse_optargs(m[3])
  return if (argc == 0) && args.empty?

  if argc == -1
    # Any number of arguments are allowed.
  elsif args.size != argc
    app_error 'wrong arg size'
  end
  if opts
    wrong_opts = opts.keys - optdecl
    unless wrong_opts.empty?
      app_error "wrong option: #{wrong_opts.keys.join(' ')}"
    end
  end
  Directive.new(op, args, opts || {})
end
parse_optargs(str) click to toggle source
# File lib/review/preprocessor.rb, line 180
def parse_optargs(str)
  return nil unless str

  table = {}
  str.split(/,\s*/).each do |a|
    name, spec = a.split('=', 2)
    table[name] = optarg_value(spec)
  end
  table
end
preproc(f) click to toggle source
# File lib/review/preprocessor.rb, line 48
def preproc(f)
  @vartable = {}
  @has_errors = false

  f.each_line do |line|
    begin
      case line
      when /\A\#@\#/, /\A\#\#\#\#/
        @f.print line

      when /\A\#@defvar/
        @f.print line
        direc = parse_directive(line, 2)
        defvar(*direc.args)

      when /\A\#@mapoutput/
        direc = parse_directive(line, 1, 'stderr')
        @f.print line
        get_output(expand(direc.arg), direc['stderr']).each { |out| @f.print out.string }
        skip_list(f)

      when /\A\#@mapfile/
        direc = parse_directive(line, 1, 'eval')
        path = expand(direc.arg)
        @leave_content = File.extname(path) == '.re'
        ent = if direc['eval']
                evaluate(path, ent)
              else
                @repository.fetch_file(path)
              end
        replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp

      when /\A\#@map(?:range)?/
        direc = parse_directive(line, 2, 'unindent')
        path = expand(direc.args[0])
        @leave_content = File.extname(path) == '.re'
        ent = @repository.fetch_range(path, direc.args[1]) or
          app_error "unknown range: #{path}: #{direc.args[1]}"
        ent = (direc['unindent'] ? unindent(ent, direc['unindent']) : ent)
        replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp

      when /\A\#@end/
        app_error 'unbaranced #@end'

      when /\A\#@/
        op = line.slice(/@(\w+)/, 1)
        warn "unknown directive: #{line.strip}", location: location unless known_directive?(op)
        if op == 'warn'
          warn line.strip.sub(/\#@warn\((.+)\)/, '\1'), location: location
        end
        @f.print line

      when /\A\s*\z/ # empty line
        @f.puts
      else # rubocop:disable Lint/DuplicateBranch
        @f.print line
      end
    rescue ApplicationError => e
      @has_errors = true
      error e.message, location: location
    end
  end

  if @has_erros
    error! 'preprocessor failed.'
  end
end
print_number(num) click to toggle source
replace_block(f, directive_line, newlines, with_lineno) click to toggle source
# File lib/review/preprocessor.rb, line 120
def replace_block(f, directive_line, newlines, with_lineno)
  @f.print directive_line
  newlines.each do |line|
    if with_lineno
      print_number(line.number)
    end
    @f.print line.string
  end
  skip_list(f)
end
skip_list(f) click to toggle source
# File lib/review/preprocessor.rb, line 135
def skip_list(f)
  begline = f.lineno
  f.each_line do |line|
    case line
    when /\A\#@end/
      @f.print line
      return nil
    when %r{\A//\}}
      unless @leave_content
        warn '//} seen in list', location: location
        @f.print line
        return nil
      end
    when /\A\#@\w/
      warn "#{line.slice(/\A\#@\w+/)} seen in list", location: location
      @f.print line
    when /\A\#@/
      @f.print line
    end
  end
  app_error "list reached end of file (beginning line = #{begline})"
end
unindent(chunk, n) click to toggle source
# File lib/review/preprocessor.rb, line 217
def unindent(chunk, n)
  n = minimum_indent(chunk) unless n.is_a?(Integer)
  re = /\A#{' ' * n}/
  chunk.map { |line| line.edit { |s| s.sub(re, '') } }
end