class ReVIEW::Compiler

Constants

INLINE
SYNTAX

Attributes

strategy[R]

Public Class Methods

defblock(name, argc, optional = false, &block) click to toggle source
# File lib/review/compiler.rb, line 92
def Compiler.defblock(name, argc, optional = false, &block)
  defsyntax name, (optional ? :optional : :block), argc, &block
end
definline(name) click to toggle source
# File lib/review/compiler.rb, line 122
def Compiler.definline(name)
  INLINE[name] = InlineSyntaxElement.new(name)
end
defsingle(name, argc, &block) click to toggle source
# File lib/review/compiler.rb, line 96
def Compiler.defsingle(name, argc, &block)
  defsyntax name, :line, argc, &block
end
defsyntax(name, type, argc, &block) click to toggle source
# File lib/review/compiler.rb, line 100
def Compiler.defsyntax(name, type, argc, &block)
  SYNTAX[name] = SyntaxElement.new(name, type, argc, &block)
end
new(strategy) click to toggle source
# File lib/review/compiler.rb, line 43
def initialize(strategy)
  @strategy = strategy
end

Public Instance Methods

compile(chap) click to toggle source
# File lib/review/compiler.rb, line 49
def compile(chap)
  @chapter = chap
  do_compile
  @strategy.result
end
inline_defined?(name) click to toggle source
# File lib/review/compiler.rb, line 126
def inline_defined?(name)
  INLINE.key?(name.to_sym)
end
syntax_defined?(name) click to toggle source
# File lib/review/compiler.rb, line 104
def syntax_defined?(name)
  SYNTAX.key?(name.to_sym)
end
syntax_descriptor(name) click to toggle source
# File lib/review/compiler.rb, line 108
def syntax_descriptor(name)
  SYNTAX[name.to_sym]
end
text(str) click to toggle source
# File lib/review/compiler.rb, line 525
def text(str)
  return '' if str.empty?
  words = str.split(/(@<\w+>\{(?:[^\}\]|\.)*?\})/, -1)
  words.each do |w|
    error "`@<xxx>' seen but is not valid inline op: #{w}" if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
  end
  result = @strategy.nofunc_text(words.shift)
  until words.empty?
    result << compile_inline(words.shift.gsub(/\\}/, '}').gsub(/\\/, '\'))
    result << @strategy.nofunc_text(words.shift)
  end
  result
rescue => err
  error err.message
end

Private Instance Methods

block_open?(line) click to toggle source
# File lib/review/compiler.rb, line 442
def block_open?(line)
  line.rstrip[-1,1] == '{'
end
close_all_tagged_section() click to toggle source
# File lib/review/compiler.rb, line 348
def close_all_tagged_section
  until @tagged_section.empty?
    close_tagged_section(* @tagged_section.pop)
  end
end
close_current_tagged_section(level) click to toggle source
# File lib/review/compiler.rb, line 314
def close_current_tagged_section(level)
  while @tagged_section.last and @tagged_section.last[1] >= level
    close_tagged_section(* @tagged_section.pop)
  end
end
close_tagged_section(tag, level) click to toggle source
# File lib/review/compiler.rb, line 339
def close_tagged_section(tag, level)
  mid = "#{tag}_end"
  if @strategy.respond_to?(mid)
    @strategy.__send__ mid, level
  else
    error "strategy does not support block op: #{mid}"
  end
end
compile_block(syntax, args, lines) click to toggle source
# File lib/review/compiler.rb, line 510
def compile_block(syntax, args, lines)
  @strategy.__send__(syntax.name, (lines || default_block(syntax)), *args)
end
compile_command(syntax, args, lines) click to toggle source
# File lib/review/compiler.rb, line 484
def compile_command(syntax, args, lines)
  unless @strategy.respond_to?(syntax.name)
    error "strategy does not support command: //#{syntax.name}"
    compile_unknown_command args, lines
    return
  end
  begin
    syntax.check_args args
  rescue CompileError => err
    error err.message
    args = ['(NoArgument)'] * syntax.min_argc
  end
  if syntax.block_allowed?
    compile_block syntax, args, lines
  else
    if lines
      error "block is not allowed for command //#{syntax.name}; ignore"
    end
    compile_single syntax, args
  end
end
compile_dlist(f) click to toggle source
# File lib/review/compiler.rb, line 413
def compile_dlist(f)
  @strategy.dl_begin
  while /\A\s*:/ =~ f.peek
    @strategy.dt text(f.gets.sub(/\A\s*:/, '').strip)
    @strategy.dd f.break(/\A(\S|\s*:)/).map {|line| text(line.strip) }
    f.skip_blank_lines
    f.skip_comment_lines
  end
  @strategy.dl_end
end
compile_headline(line) click to toggle source
# File lib/review/compiler.rb, line 283
def compile_headline(line)
  @headline_indexs ||= [@chapter.number.to_i - 1]
  m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(line)
  level = m[1].size
  tag = m[2]
  label = m[3]
  caption = m[4].strip
  index = level - 1
  if tag
    if tag !~ /\A\//
      close_current_tagged_section(level)
      open_tagged_section(tag, level, label, caption)
    else
      open_tag = tag[1..-1]
      prev_tag_info = @tagged_section.pop
      unless prev_tag_info.first == open_tag
        raise CompileError, "#{open_tag} is not opened."
      end
      close_tagged_section(*prev_tag_info)
    end
  else
    if @headline_indexs.size > (index + 1)
      @headline_indexs = @headline_indexs[0..index]
    end
    @headline_indexs[index] = 0 if @headline_indexs[index].nil?
    @headline_indexs[index] += 1
    close_current_tagged_section(level)
    @strategy.headline level, label, caption
  end
end
compile_inline(str) click to toggle source
# File lib/review/compiler.rb, line 542
def compile_inline(str)
  op, arg = /\A@<(\w+)>\{(.*?)\}\z/.match(str).captures
  unless inline_defined?(op)
    raise CompileError, "no such inline op: #{op}"
  end
  unless @strategy.respond_to?("inline_#{op}")
    raise "strategy does not support inline op: @<#{op}>"
  end
  @strategy.__send__("inline_#{op}", arg)
rescue => err
  error err.message
  @strategy.nofunc_text(str)
end
compile_olist(f) click to toggle source
# File lib/review/compiler.rb, line 398
def compile_olist(f)
  @strategy.ol_begin
  f.while_match(/\A\s+\d+\.|\A\#@/) do |line|
    next if line =~ /\A\#@/

    num = line.match(/(\d+)\./)[1]
    buf = [text(line.sub(/\d+\./, '').strip)]
    f.while_match(/\A\s+(?!\d+\.)\S/) do |cont|
      buf.push text(cont.strip)
    end
    @strategy.ol_item buf, num
  end
  @strategy.ol_end
end
compile_paragraph(f) click to toggle source
# File lib/review/compiler.rb, line 424
def compile_paragraph(f)
  buf = []
  f.until_match(%r<\A//|\A\#@>) do |line|
    break if line.strip.empty?
    buf.push text(line.sub(/^(\t+)\s*/) {|m| "<!ESCAPETAB!>" * m.size}.strip.gsub(/<!ESCAPETAB!>/, "\t"))
  end
  @strategy.paragraph buf
end
compile_single(syntax, args) click to toggle source
# File lib/review/compiler.rb, line 521
def compile_single(syntax, args)
  @strategy.__send__(syntax.name, *args)
end
compile_ulist(f) click to toggle source
# File lib/review/compiler.rb, line 354
def compile_ulist(f)
  level = 0
  f.while_match(/\A\s+\*|\A\#@/) do |line|
    next if line =~ /\A\#@/

    buf = [text(line.sub(/\*+/, '').strip)]
    f.while_match(/\A\s+(?!\*)\S/) do |cont|
      buf.push text(cont.strip)
    end

    line =~ /\A\s+(\*+)/
    current_level = $1.size
    if level == current_level
      @strategy.ul_item_end
      # body
      @strategy.ul_item_begin buf
    elsif level < current_level # down
      level_diff = current_level - level
      level = current_level
      (1..(level_diff - 1)).to_a.reverse_each do |i|
        @strategy.ul_begin {i}
        @strategy.ul_item_begin []
      end
      @strategy.ul_begin {level}
      @strategy.ul_item_begin buf
    elsif level > current_level # up
      level_diff = level - current_level
      level = current_level
      (1..level_diff).to_a.reverse_each do |i|
        @strategy.ul_item_end
        @strategy.ul_end {level + i}
      end
      @strategy.ul_item_end
      # body
      @strategy.ul_item_begin buf
    end
  end

  (1..level).to_a.reverse_each do |i|
    @strategy.ul_item_end
    @strategy.ul_end {i}
  end
end
compile_unknown_command(args, lines) click to toggle source
# File lib/review/compiler.rb, line 506
def compile_unknown_command(args, lines)
  @strategy.unknown_command args, lines
end
default_block(syntax) click to toggle source
# File lib/review/compiler.rb, line 514
def default_block(syntax)
  if syntax.block_required?
    error "block is required for //#{syntax.name}; use empty block"
  end
  []
end
do_compile() click to toggle source
# File lib/review/compiler.rb, line 237
def do_compile
  f = LineInput.new(StringIO.new(@chapter.content))
  @strategy.bind self, @chapter, Location.new(@chapter.basename, f)
  tagged_section_init
  while f.next?
    case f.peek
    when /\A\#@/
      f.gets # Nothing to do
    when /\A=+[\[\s\{]/
      compile_headline f.gets
    when %r<\A\s+\*>
      compile_ulist f
    when %r<\A\s+\d+\.>
      compile_olist f
    when %r<\A\s*:\s>
      compile_dlist f
    when %r<\A//\}>
      f.gets
      error 'block end seen but not opened'
    when %r<\A//[a-z]+>
      name, args, lines = read_command(f)
      syntax = syntax_descriptor(name)
      unless syntax
        error "unknown command: //#{name}"
        compile_unknown_command args, lines
        next
      end
      compile_command syntax, args, lines
    when %r<\A//>
      line = f.gets
      warn "`//' seen but is not valid command: #{line.strip.inspect}"
      if block_open?(line)
        warn "skipping block..."
        read_block(f, false)
      end
    else
      if f.peek.strip.empty?
        f.gets
        next
      end
      compile_paragraph f
    end
  end
  close_all_tagged_section
end
error(msg) click to toggle source
# File lib/review/compiler.rb, line 560
def error(msg)
  @strategy.error msg
end
headline(level, label, caption) click to toggle source
# File lib/review/compiler.rb, line 320
def headline(level, label, caption)
  @strategy.headline level, label, caption
end
open_tagged_section(tag, level, label, caption) click to toggle source
# File lib/review/compiler.rb, line 328
def open_tagged_section(tag, level, label, caption)
  mid = "#{tag}_begin"
  unless @strategy.respond_to?(mid)
    error "strategy does not support tagged section: #{tag}"
    headline level, label, caption
    return
  end
  @tagged_section.push [tag, level]
  @strategy.__send__ mid, level, label, caption
end
parse_args(str, name=nil) click to toggle source
# File lib/review/compiler.rb, line 466
def parse_args(str, name=nil)
  return [] if str.empty?
  scanner = StringScanner.new(str)
  words = []
  while word = scanner.scan(/(\[\]|\[.*?[^\]\])/)
    w2 = word[1..-2].gsub(/\(.)/){
      ch = $1
      (ch == "]" or ch == "\\") ? ch : "\\" + ch
    }
    words << w2
  end
  if !scanner.eos?
    error "argument syntax error: #{scanner.rest} in #{str.inspect}"
    return []
  end
  return words
end
read_block(f, ignore_inline) click to toggle source
# File lib/review/compiler.rb, line 446
def read_block(f, ignore_inline)
  head = f.lineno
  buf = []
  f.until_match(%r<\A//\}>) do |line|
    if ignore_inline
      buf.push line
    else
      unless line =~ /\A\#@/
        buf.push text(line.rstrip)
      end
    end
  end
  unless %r<\A//\}> =~ f.peek
    error "unexpected EOF (block begins at: #{head})"
    return buf
  end
  f.gets # discard terminator
  buf
end
read_command(f) click to toggle source
# File lib/review/compiler.rb, line 433
def read_command(f)
  line = f.gets
  name = line.slice(/[a-z]+/).to_sym
  ignore_inline = (name == :embed)
  args = parse_args(line.sub(%r<\A//[a-z]+>, '').rstrip.chomp('{'), name)
  lines = block_open?(line) ? read_block(f, ignore_inline) : nil
  return name, args, lines
end
tagged_section_init() click to toggle source
# File lib/review/compiler.rb, line 324
def tagged_section_init
  @tagged_section = []
end
warn(msg) click to toggle source
# File lib/review/compiler.rb, line 556
def warn(msg)
  @strategy.warn msg
end