Class FileProcessor
In: fileproc.rb
Parent: Object

Transform a template file into a new file by processing the %-directives and replacing symbol references <?FOO> with values.

Supported directives: %#, %if, %elif, %else, %endif, %define, %undef, and %include.

Methods
<<    cons_line    do_define    do_elif    do_else    do_endif    do_eof    do_if    do_include    do_license    do_line    do_meta    do_metapop    do_metapush    do_metastr    do_text    do_token    do_undef    error    findfile    first_word    new    process    readfile    unquote   
Attributes
:errlist  [R] 
:fileFinder  [RW] 
:outtext  [R] 
:sytab  [RW] 
Public Class methods
new()

Setup symbol table, conditional evaluation stack, and map of token bit values to handler Procs

# File fileproc.rb, line 201
  def initialize()
    @abend = false
    @goteof = false
    @sytab = SyTab.new(VarDelims)
    @allowed = Array.new
    @allowed.push(NonCond | CondBegin)
    @condstk = Array.new
    @condstk.push(true)
    @filestk = Array.new
    @filestk.push(nil)
    @metastk = Array.new
    @metastk.push(DefMetaStr)
    @metastr = @metastk.last
    @metalen = @metastk.last.size
    @outtext = Array.new
    @errlist = Array.new
    @fileFinder = nil
    @linebuf = "" 
    # Map from token bit values to Proc objects which invoke the handler
    # in the context of the current object.
    @handlers = {
      Comment =>	proc { |ln| true },
      IfTok =>		proc { |ln| do_if(ln) },
      ElifTok =>	proc { |ln| do_elif(ln) },
      ElseTok =>	proc { |ln| do_else(ln) },
      EndifTok =>	proc { |ln| do_endif(ln) },
      EofTok =>		proc { |ln| do_eof(ln) if @condstk.last },
      DefTok =>		proc { |ln| do_define(ln) if @condstk.last },
      UndefTok =>	proc { |ln| do_undef(ln) if @condstk.last },
      InclTok =>	proc { |ln| do_include(ln) if @condstk.last },
      LicenseTok=>	proc { |ln| do_license(ln) if @condstk.last },
      MetaStrTok =>	proc { |ln| do_metastr(ln) if @condstk.last },
      MetaPushTok =>	proc { |ln| do_metapush(ln) if @condstk.last },
      MetaPopTok =>	proc { |ln| do_metapop(ln) if @condstk.last },
    }
  end
Public Instance methods
process(fn)

Process file, returning array of output text or nil if errors occurred

fn
path to file to read
# File fileproc.rb, line 538
  def process(fn)
    readfile(fn)
    @errlist.size == 0 ? @outtext : nil
  end
<<(fn)

Shorthand for process(fn)

# File fileproc.rb, line 545
  def <<(fn)
    process(fn)
  end
Private Instance methods
error(msg)

Report an error

msg
Text describing error condition
# File fileproc.rb, line 246
  def error(msg)
    trace = []
    @filestk.each {
      |io|
      trace.push(io ? [ io.path, io.lineno ] : [ "<<start>>", 0 ])
    }
    @errlist.push(ParseError.new(msg, trace))
    if @errlist.size > MaxErrors
      @abend = true
      @errlist.push(ParseError.new("Bailing out: too many errors", trace))
    end
  end
do_if(ln)

Process %if

ln
Remainder of line
# File fileproc.rb, line 262
  def do_if(ln)
    @condstk.push(@condstk.last && Cond.new(ln, @sytab).value)
    @allowed.push(NonCond | CondBegin | ElifTok | ElseTok | EndifTok)
  end
do_else(ln)

Process %else

ln
Remainder of line (ignored)
# File fileproc.rb, line 270
  def do_else(ln)
    @condstk[-1] = !@condstk[-1]
    @condstk[-1] &&= @condstk[-2]
    @allowed[-1] = NonCond | CondBegin | EndifTok
  end
do_elif(ln)

Process %elif

ln
Remainder of line
# File fileproc.rb, line 279
  def do_elif(ln)
    do_else(ln)
    do_if(ln)
    @condstk[-1] &&= @condstk[-2]
    @condstk.delete_at(-2)
    @allowed.delete_at(-2)
  end
do_endif(ln)

Process %endif

ln
Remainder of line (ignored)
# File fileproc.rb, line 290
  def do_endif(ln)
    @condstk.pop
    @allowed.pop
  end
do_eof(ln)

Process %end

ln
Remainder of line (ignored)
# File fileproc.rb, line 298
  def do_eof(ln)
    @goteof = true
  end
do_undef(ln)

Process %undef

ln
Remainder of line (all but first word ignored)
# File fileproc.rb, line 305
  def do_undef(ln)
    @sytab.delete(first_word(ln))
  end
unquote(s)

Remove quotes from string

s
String to be unquoted
# File fileproc.rb, line 312
  def unquote(s)
    if /^\\["']/ =~ s
      s = s[1..-1]
    elsif /^(["']).*\1$/ =~ s
      s = s[1..-2]
    end
    s
  end
do_define(ln)

Define/redefine a variable

ln
Remainder of line: first word is the variable name; rest, without leading or trailing spaces, is value. One layer of quotes is removed unless preceded by \.
# File fileproc.rb, line 327
  def do_define(ln)
    args = ln.strip.split(>\s+>,2)
    @sytab[ args[0] ] = unquote(args[1])
  end
findfile(fn)

Find a file specified to %include in search path; returns full path to file or nil.

fn
Name of file to find
# File fileproc.rb, line 336
  def findfile(fn)
    if fn == nil || fn == ""
      "/dev/null"
    elsif fn[0] == ?/
      File.file?(fn) ? fn : nil
    else
      @fileFinder ? @fileFinder.findFile(fn) : nil
    end
  end
do_include(ln)

Process %include

ln
Remainder of line, used as filename to include; may be enclosed in <\> brackets, as in C.
# File fileproc.rb, line 351
  def do_include(ln)
    ln = ln.strip
    ln = ln[1..-2] if /^<.*>$/ =~ ln
    ln = '"' + ln + '"' if ln !~ /^["<'"]/
    ln = Cond.new(ln, @sytab).value()
    fn = findfile(ln)
    if (fn)
      readfile(fn) 
    else 
      error("can't find include file: \"#{ln}\"")
    end
  end
do_license(ln)

Process %license

ln
Remainder of line, ignored.
# File fileproc.rb, line 368
  def do_license(ln)
    fn = @sytab["LICENSE"] + "@license.inc"
    path = findfile(fn)
    if (path)
      readfile(path) 
    else 
      error("can't find include file: \"#{fn}\"")
    end
  end
do_metastr(ln)

Process %metastr

ln
Remainder of line (all but first word ignored)
# File fileproc.rb, line 381
  def do_metastr(ln)
    do_metapush(ln)
  end
do_metapush(ln)

Process %metapush

ln
Remainder of line (all but first word ignored)
# File fileproc.rb, line 388
  def do_metapush(ln)
    @metastk.push(first_word(ln))
    @metastr = @metastk.last
    @metalen = @metastk.last.size
  end
do_metapop(ln)

Process %metapop

ln
Remainder of line (ignored)
# File fileproc.rb, line 397
  def do_metapop(ln)
    @metastk.pop
    @metastr = @metastk.last
    @metalen = @metastk.last.size
  end
do_token(tok, ln)

Handle a line beginning with @metastr by calling a proc

tok
Number representing token
ln
Remainder of line after token and whitespace
# File fileproc.rb, line 407
  def do_token(tok, ln)
    @handlers[ tok ].call(ln)
  end
first_word(ln)

Get the first word of a line

ln
Line of text
# File fileproc.rb, line 414
  def first_word(ln)
    ln.sub(/\s.*/,"")
  end
do_meta(ln)

Handle a line beginning with @metastr by validating and either calling #do_token or #error

ln
Line of text with leading % removed
# File fileproc.rb, line 422
  def do_meta(ln)
    word = first_word(ln)
    token = TOKENS[ word ]
    if token != nil
      do_token(token, @sytab << ln.sub(/^[a-z]+\s*/,""))
    else
      error("unknown directive: %#{word}")
    end
  end
do_text(ln)

Process input text: replace symbols and copy to output if top of condition stack is not false

ln
Line of input text
# File fileproc.rb, line 436
  def do_text(ln)
    @outtext.push(@sytab << ln) if @condstk.last
  end
do_line(ln)

Decide what to do with a line and call appropriate method

ln
Line of input text
# File fileproc.rb, line 443
  def do_line(ln)
    i = ln.index(@metastr)
    if i != 0
      if i == 1 && ln[0,1] = "\\"
	ln = ln[ 1, ln.size - 1 ]
      end
      do_text(ln)
    else
      do_meta(ln[ @metalen, ln.size - @metalen ])
    end      
  end
cons_line(ln)

Build logical lines: if a physical line ends with "%+\n", then the next line is a continuation, and the "%+\n" is replaced by a single space. Calls do_line once a logical line is assembled.

ln
partial or complete logical line read from input
# File fileproc.rb, line 461
  def cons_line(ln)
    if @linebuf.size == 0
      @linebuf = ln
    else
      @linebuf += " "
      if }^\s*(\S+.*)} =~ ln
	@linebuf += $1
      end
    end
    if @linebuf.size < 2 || @linebuf[0,1] != "%" || @linebuf[-2,2] != "%+"
      tmp = @linebuf
      @linebuf = ""
      do_line(tmp)
    else
      @linebuf.slice!(-2,2)
    end
  end
readfile(fn)

Process an input file (does not search for file)

fn
path to file to read
# File fileproc.rb, line 482
  def readfile(fn) 
    if File::file?(fn) && File::readable?(fn)
      File.open(fn) {
	|io|
	@filestk.push(io)
	@metastk.push(@metastr)
	ccond = @condstk.size
	cmeta = @metastk.size
	io.each_line { |ln| 
	  if @abend
	    break
	  else
	    cons_line(ln.chomp)
	    if @goteof
	      @goteof = false; break
	    end
	  end # if abend
	}
	if @linebuf.size > 0
	  error("File ends with line continuation")
	  cons_line("") # force the partial line out
	end
	if @condstk.size != ccond
	  error("Mismatched conditionals in file")
	  if @condstk.size > ccond
	    error("Unclosed %if(s): recovering ...")
	    while @condtstk.size > ccond
	      @condstk.pop
	    end
	  else
	    @abend = true
	    error("Too many %endifs (corrupted): bailing out");
	  end
	end
	if @metastk.size < cmeta
	    @abend = true
	    error("Too many %metapops (corrupted): bailing out");
	end
	while @metastk.size > cmeta
	  @metastk.pop
	end
	@metastr = @metastk.last
	@metalen = @metastr.size
	@filestk.pop
      }
    else
      error("File '#{fn}' not found/readable")
    end
  end