lib/review/preprocessor.rb in review-5.1.1 vs lib/review/preprocessor.rb in review-5.2.0
- old
+ new
@@ -7,124 +7,114 @@
# For details of the GNU LGPL, see the file "COPYING".
#
require 'review/textutils'
require 'review/exception'
-require 'nkf'
+require 'review/preprocessor/directive'
+require 'review/preprocessor/line'
+require 'review/preprocessor/repository'
+require 'review/loggable'
+require 'open3'
module ReVIEW
- module ErrorUtils
- def init_errorutils(f)
- @errutils_file = f
- @errutils_err = false
- end
-
- def warn(msg)
- @logger.warn "#{location}: #{msg}"
- end
-
- def error(msg)
- @errutils_err = true
- raise ApplicationError, "#{location}: #{msg}"
- end
-
- def location
- "#{filename}:#{lineno}"
- end
-
- def filename
- @errutils_file.path
- end
-
- def lineno
- @errutils_file.lineno
- end
- end
-
class Preprocessor
- include ErrorUtils
+ include Loggable
- def initialize(repo, param)
- @repository = repo
- @config = param
+ TYPES = %w[file range].freeze
+ KNOWN_DIRECTIVES = %w[require provide warn ok].freeze
+ INF_INDENT = 9999
+
+ 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
- def process(inf, outf)
- init_errorutils(inf)
- @f = outf
- begin
- preproc(inf)
- rescue Errno::ENOENT => e
- error e.message
+ 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
- TYPES = %w[file range].freeze
-
def preproc(f)
- init_vars
+ @vartable = {}
+ @has_errors = false
+
f.each_line do |line|
- case line
- when /\A\#@\#/, /\A\#\#\#\#/
- @f.print 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\#@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\#@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'
- if direc['eval']
- ent = evaluate(path, ent)
- else
- ent = @repository.fetch_file(path)
- end
- replace_block(f, line, ent, false) # FIXME: turn off lineno: tmp
+ when /\A\#@mapfile/
+ direc = parse_directive(line, 1, 'eval')
+ path = expand(direc.arg)
+ @leave_content = File.extname(path) == '.re'
+ if direc['eval']
+ ent = evaluate(path, ent)
+ else
+ ent = @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
- 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\#@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/
- error 'unbaranced #@end'
+ when /\A\#@end/
+ app_error 'unbaranced #@end'
- when /\A\#@/
- op = line.slice(/@(\w+)/, 1)
- warn "unknown directive: #{line.strip}" unless known_directive?(op)
- if op == 'warn'
- warn line.strip.sub(/\#@warn\((.+)\)/, '\1')
- end
- @f.print line
+ 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
+ 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
- KNOWN_DIRECTIVES = %w[require provide warn ok].freeze
-
def known_directive?(op)
KNOWN_DIRECTIVES.index(op)
end
def replace_block(f, directive_line, newlines, with_lineno)
@@ -149,65 +139,41 @@
when /\A\#@end/
@f.print line
return nil
when %r{\A//\}}
unless @leave_content
- warn '//} seen in list'
+ warn '//} seen in list', location: location
@f.print line
return nil
end
when /\A\#@\w/
- warn "#{line.slice(/\A\#@\w+/)} seen in list"
+ warn "#{line.slice(/\A\#@\w+/)} seen in list", location: location
@f.print line
when /\A\#@/
@f.print line
end
end
- error "list reached end of file (beginning line = #{begline})"
+ app_error "list reached end of file (beginning line = #{begline})"
end
- class Directive
- def initialize(op, args, opts)
- @op = op
- @args = args
- @opts = opts
- end
-
- attr_reader :op
- attr_reader :args
- attr_reader :opts
-
- def arg
- @args.first
- end
-
- def opt
- @opts.first
- end
-
- def [](key)
- @opts[key]
- end
- end
-
def parse_directive(line, argc, *optdecl)
m = /\A\#@(\w+)\((.*?)\)(?:\[(.*?)\])?\z/.match(line.strip) or
- error "wrong directive: #{line.strip}"
+ 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
- error 'wrong arg size'
+ app_error 'wrong arg size'
end
if opts
wrong_opts = opts.keys - optdecl
unless wrong_opts.empty?
- error "wrong option: #{wrong_opts.keys.join(' ')}"
+ app_error "wrong option: #{wrong_opts.keys.join(' ')}"
end
end
Directive.new(op, args, opts || {})
end
@@ -235,14 +201,10 @@
else # [name=val]
spec
end
end
- def init_vars
- @vartable = {}
- end
-
def defvar(name, value)
@vartable[name] = value
end
def expand(str)
@@ -256,12 +218,10 @@
n = minimum_indent(chunk) unless n.is_a?(Integer)
re = /\A#{' ' * n}/
chunk.map { |line| line.edit { |s| s.sub(re, '') } }
end
- INF_INDENT = 9999
-
def minimum_indent(chunk)
n = chunk.map { |line| line.empty? ? INF_INDENT : line.num_indent }.min
n == INF_INDENT ? 0 : n
end
@@ -275,12 +235,10 @@
line
end
end
end
- require 'open3'
-
def get_output(cmd, use_stderr)
out = err = nil
Open3.popen3(cmd) do |_stdin, stdout, stderr|
out = stdout.readlines
if use_stderr
@@ -290,184 +248,24 @@
end
end
if err && !err.empty?
$stderr.puts '[unexpected stderr message]'
err.each { |line| $stderr.print line }
- error 'get_output: got unexpected output'
+ app_error 'get_output: got unexpected output'
end
num = 0
out.map { |line| Line.new(num += 1, line) }
end
- end
- class Line
- def initialize(number, string)
- @number = number
- @string = string
+ def location
+ "#{filename}:#{lineno}"
end
- attr_reader :number
- attr_reader :string
- alias_method :to_s, :string
-
- def edit
- self.class.new(@number, yield(@string))
+ def filename
+ @inf.path
end
- def empty?
- @string.strip.empty?
- end
-
- def num_indent
- @string.slice(/\A\s*/).size
- end
- end
-
- class Repository
- include TextUtils
- include ErrorUtils
-
- def initialize(param)
- @repository = {}
- @config = param
- @logger = ReVIEW.logger
- end
-
- def fetch_file(file)
- file_descripter(file)['file']
- end
-
- def fetch_range(file, name)
- fetch(file, 'range', name)
- end
-
- def fetch(file, type, name)
- table = file_descripter(file)[type] or return nil
- table[name]
- end
-
- private
-
- def file_descripter(fname)
- @leave_content = File.extname(fname) == '.re'
- return @repository[fname] if @repository[fname]
-
- @repository[fname] = git?(fname) ? parse_git_blob(fname) : parse_file(fname)
- end
-
- def git?(fname)
- fname.start_with?('git|')
- end
-
- def parse_git_blob(g_obj)
- IO.popen('git show ' + g_obj.sub(/\Agit\|/, ''), 'r') do |f|
- init_errorutils(f)
- return _parse_file(f)
- end
- end
-
- def parse_file(fname)
- File.open(fname, 'rt:BOM|utf-8') do |f|
- init_errorutils(f)
- return _parse_file(f)
- end
- end
-
- def _parse_file(f)
- whole = []
- repo = { 'file' => whole }
- curr = { 'WHOLE' => whole }
- lineno = 1
- yacchack = false # remove ';'-only lines.
- opened = [['(not opened)', '(not opened)']] * 3
-
- f.each do |line|
- case line
- when /(?:\A\#@|\#@@)([a-z]+)_(begin|end)\((.*)\)/
- type = check_type($1)
- direction = $2
- spec = check_spec($3)
- case direction
- when 'begin'
- key = "#{type}/#{spec}"
- if curr[key]
- error "begin x2: #{key}"
- end
- (repo[type] ||= {})[spec] = curr[key] = []
- when 'end'
- curr.delete("#{type}/#{spec}") or
- error "end before begin: #{type}/#{spec}"
- else
- raise 'must not happen'
- end
-
- when %r{(?:\A\#@|\#@@)([a-z]+)/(\w+)\{}
- type = check_type($1)
- spec = check_spec($2)
- key = "#{type}/#{spec}"
- if curr[key]
- error "begin x2: #{key}"
- end
- (repo[type] ||= {})[spec] = curr[key] = []
- opened.push([type, spec])
-
- when %r{(?:\A\#@|\#@@)([a-z]+)/(\w+)\}}
- type = check_type($1)
- spec = check_spec($2)
- curr.delete("#{type}/#{spec}") or
- error "end before begin: #{type}/#{spec}"
- opened.delete("#{type}/#{spec}")
-
- when /(?:\A\#@|\#@@)\}/
- type, spec = opened.last
- curr.delete("#{type}/#{spec}") or
- error "closed before open: #{type}/#{spec}"
- opened.pop
-
- when /(?:\A\#@|\#@@)yacchack/
- yacchack = true
-
- when /\A\#@-/ # does not increment line number.
- line = canonical($')
- curr.each_value { |list| list.push(Line.new(nil, line)) }
-
- else
- next if yacchack && (line.strip == ';')
-
- line = canonical(line)
- curr.each_value { |list| list.push(Line.new(lineno, line)) }
- lineno += 1
- end
- end
- if curr.size > 1
- curr.delete('WHOLE')
- curr.each { |range, lines| @logger.warn "#{filename}: unclosed range: #{range} (begin @#{lines.first.number})" }
- raise ApplicationError, 'ERROR'
- end
-
- repo
- end
-
- def canonical(line)
- if @leave_content
- return line
- end
-
- tabwidth = @config['tabwidth'] || 8
- if tabwidth > 0
- detab(line, tabwidth).rstrip + "\n"
- else
- line
- end
- end
-
- def check_type(type)
- error "wrong type: #{type.inspect}" unless Preprocessor::TYPES.index(type)
- type
- end
-
- def check_spec(spec)
- error "wrong spec: #{spec.inspect}" unless /\A\w+\z/ =~ spec
- spec
+ def lineno
+ @inf.lineno
end
end
end