# /* lib/flex2.rb 
#  * utility for the Ruby extension flex.so
#  * formerly flexruby.h
#  * by pts@fazekas.hu at Sun Dec 10 02:33:06 CET 2000
#  * 4 hours of work + 3 hours of organisation
#  * -- Sun Dec 10 10:57:22 CET 2000
#  * finding trap-but-accept states -- Sun Dec 17 11:41:35 CET 2000
#  * multiple start conditions -- Sun Dec 17 11:41:48 CET 2000
#  */

require "flex"

class Flex
  attr_reader :ar
  attr_reader :s # trace string
  # attr_reader :t # transition table
  # attr_reader :c # character class table
  # attr_reader :extra # type: Flex.Printbuf; extra chars after last (non-complete) match
private # private methods are available from rb_funcall(), but not from Ruby
  def get_ct(ar)
    # Imp: check for array arg
    warnin=''
    @ar=ar.clone
    # old:
    #   @ar[0]=nil # no match yet
    #   @ar[1]=nil # no match, and there never will
    # new:
    #   @ar[0]: extra (command line) options to Flex
    #   @ar[1]: extra text to be put before the 1st '%%'
    raise FlexRuntimeError, "no regexps given" if @ar.length<3

    # hsq=Hash[" ","\\ ","\t","\\t","\n","\\n","\r","\\r","\b","\\b"];
    # @ar.collect! {|s| s.gsub!(/([ \n\t\r\b])/) { hsq[$1] }  }
    grammar="%option noyywrap 8bit nodefault noalign nobackup caseful nodebug batch\n"
    grammar << ar[1] if ar[1]
    grammar << "\n%%\n"
    # nowarn
    (2..@ar.length-1). each {|i|
      # grammar+=@ar[i].gsub(/([ \n\t\r\b])/) { hsq[$1] }+"\n";
      grammar+=rq(@ar[i])+"\n";
      # ^^^ OK: gsub only unquoted whitespace
    }
    @s=Flex.trace grammar, @ar[0]
    # self.ct!
    # print grammar
    # print @s
    # end
    # def ct!()
    # ct!(): calc @c and @t tables from @s
    # Dat: Array[], [] and Array.new(0) are all empty array constructors
    r_nextt=[]; accept=[]; clas=[]
    scond=Hash.new # [name:number]; number: INITIAL==1..
    # print @s
    @s.each_line { |l|
      if l=~/^> (\d+) (\d+) (\d+)/;
        # p l
        #a=$1.to_i; b=$2.to_i;
        #nextt[a]=[] unless nextt[a]
        #nextt[a][b]=$3.to_i
        r_nextt.push [$1.to_i,$2.to_i,$3.to_i]
      elsif l=~/^= (\d+) (\d+|-1)/;
        b=$2.to_i; b=1 if b==-1
        a=$1.to_i
        raise FlexInternalError, "bad/dup char" if clas[a] or b>256
        clas[a]=b
      elsif l=~/^@ (\d+) (\d+)/;
        a=$1.to_i; b=1+$2.to_i
        if b>@ar.length+1
          raise FlexInternalError, "accept rule too high"
        #elsif b==@ar.length+1
          # ignore `accept' for implicit default rule
        #elsif b==@ar.length
          # ignore `accept' for End Marker rule
        elsif b<@ar.length
          # remember that `accept'
          raise FlexInternalError, "dup accept #{$1}" if accept[a]
          accept[a]=b
        # else
          # p l
        end
      elsif l=~/^! /;
        # Dat: rb_warn() and rb_warning() is not accessible from Ruby
        # `Rule cannot be matched' warnings are important!
        # STDERR.print l
        warnin << l
      # No `else'. We silently ignore unknown lines
      elsif l=~/^< (\d+) (.*)/;
        scond[$2.freeze]=$1.to_i*2-1
      else
        raise FlexInternalError, "unknown tracline #{l.inspect}"
      end#if
      #  p l
    }
    raise FlexWarning, warnin unless warnin.empty?
    # p scond

    clasok=clas.length==256
    kh=[] # [old_classid:new_classid]
    ki=[nil] # [new_classid:old_classid]
    clas.each { |t|
      clasok=false unless t>=1 and t<=257
      unless kh[t]; kh[t]=ki.length; ki.push t; end
    } if clasok
    raise FlexInternalError, "bad chartab" unless clasok
    # p clas
    @c=clas.pack("I*").freeze

    nextt=[[]]
    r_nextt.each { |t|
      #a=$1.to_i; b=$2.to_i;
      nextt[t[0]]=[0] unless nextt[t[0]]
      nextt[t[0]][kh[t[1]]]=t[2]
    }
    nextt.collect! { |v| (v)?(v):[] } # nil -> []
    # p nextt, nextt.length
    nextt.each {|t| t.collect! { |v|
      # nextt[v]=[] if v and v>=nextt.length
      (nextt.length..v).each {|i| nextt[i]=[]}  if v
      (v)?(v):0
    } } # nil -> 0
    # Dat: RULEZ: push is allowed while each-ing though the array
    
    # p r_nextt
    # print @s
    # p nextt.length, nextt
    # exit
    
    # vvv find TRAPs (trap=0)
    trapp=[]
    nextt.each_index { |t|
      reached=[]; reached[t]=true
      (go=[t]).each { |i|
        nextt[i].each { |j|
          if !reached[j]; go.push j; reached[j]=1; end
        }
      }
      trapp.push !((0..nextt.length-1).find { |i| reached[i] and accept[i] } )
    }
    # trapp.each_index {|i| print "TRAP: #{i}\n" if trapp[i] }
    # vvv change TRAPs to 0 everywhere
    nextt.each { |t| t.collect! { |v| trapp[v]?0:v } }
    # trapp=nil # let gc reclaim the memory
    
    # vvv find unreachables
    unreached=Array.new(nextt.length,true); unreached[0]=false
    # vvv unreached[1]=false
    go=[];
    scond.each { |k,v| if trapp[v]; scond[k]=0 else unreached[v]=false; go.push v end }
    raise FlexInternalError if unreached[1]
    go.each { |i|
      nextt[i].each { |j| if unreached[j]; go.push j; unreached[j]=false; end }
    }
    # unreached.each_index {|i| print "UNREACHED: #{i}\n" if unreached[i] }
    # p unreached
    
    # vvv determine placement
    accept[0]=1
    place=2
    base=[]
    
    #nextt.each_index { |i|
    #  base[i]=place; place+=ki.length if !unreached[i]
    #}
    #base[0],base[1]=base[1],base[0]

    # Placement:
    #   initial state first,
    #   trap state,
    #   trap-but-accept states (>=2 is possible: they can accept different rules)
    #   any other state
    # print "\n\n"
    # [1].each {|i| base[i]=place; place+=ki.length }
    trt=[] # [state_id:boolean]; boolean: whether states is trap[-but-accept]
    (0..nextt.length-1).each { |i|
      # if i==1;
      #  trt.push false
      # els
      if unreached[i];
        trt.push false
        base[i]=place
      else
        t=0
        nextt[i].each {|v| t+=1 if v!=0 }
        trt.push t!=0
        if t==0;
          # p i
          # p nextt[i]
          base[i]=place; place+=ki.length
        end#if
      end#if
    }
    trt[0]=false
    # p trt, nextt
    # Now: if `place-1' is the highest trap-but-accept index
    # p ((place-1)/ki.length)
    placesave=place
    # p "ZZZZZZZZZZZZ\n"
    # p trt
    nextt.each_index { |i|
      if trt[i]; base[i]=place; place+=ki.length; end
    }

    # p base
    #nextt.each_index { |i|
    #  if !unreached[i];
    #    t=0
    #    nextt[i].each {|v| t+=1 if nextt[i] }
    #    print "#{i}: #{t}\n" if t==0
    #    p nextt[i]
    #  end
    #}

    @t=Array.new place, base[0]
    @t[0]=placesave
    @t[1]=base[1]
    nextt.each_index { |i| if !unreached[i];
      # p nextt[i]
      @t[base[i],nextt[i].length]=nextt[i].collect { |v| base[v] }
      @t[base[i]]=accept[i]?accept[i]:0
    end }
    @begin=Hash.new; scond.each {|k,v| @begin[k]=base[v] }
    @regin=Hash.new; scond.each {|k,v| @regin[base[v]]=k }
    # p @begin
    # p @t
    @t=@t.pack("L*").freeze
    #raise SyntaxError
    #return [@c,@t]
    # @extra=Flex::Printbuf.new
  end # ct
  # so far...: Perl: 9167 bytes, Ruby: 4211 bytes. Ruby :-). ...so good.
end # class extension Flex
