module Strgen
  PAIRS=[
    ['<','>'],
    ['(',')'],
    ['[',']'],
    ['{','}']
  ]
  ALLOWED_UNNESTING_FANCY=/[^<>\[\]{}()a-z0-9_]/i
  FANCY_TYPES=%w[q Q r s x w W]<<''
  SIMPLE_QUOTES=%w[" ' / `]
  SIMPLE_ESCAPES=%w[s n r t v f a b e]
  MULTI_ESCAPES=%w[x c C M 0 1 2 3 4 5 6 7]
  NON_ESCAPES=/[^#{SIMPLE_ESCAPES+MULTI_ESCAPES}]/

  def Strgen.rand_char_including(allow,disallow='')
    q=nil
    q=rand(255).chr until ((allow===q) and not (disallow[q]))
    q
  end

  def Strgen.rand_esc_seq(disallow,bsonly)
    limit=4
    bsonly[/\\/] and disallow+='\\'
    (disallow['#'] or bsonly['#']) and limit=3
    choice=rand limit
    choice=3 if disallow[/\\/] or bsonly[/\#/]
    case choice
    when 0: "\\"+rand_char_including(NON_ESCAPES,disallow)
    when 1: "\\"+SIMPLE_ESCAPES[rand(SIMPLE_ESCAPES.size)]
    when 2:
      "\\"+
      case ch=MULTI_ESCAPES[rand(MULTI_ESCAPES.size)]
      when "x": "x"+rand(256).to_s(16)
      when "0".."7": rand(256).to_s(8)
      when "c": 
        "c"+
        if rand(2).zero?
          rand_char_including(/[^\\]/,disallow+bsonly)
        else
          rand_esc_seq disallow+"#",bsonly
        end
      when "C","M":
        return rand_esc_seq(disallow,bsonly) if disallow['-'] or bsonly['-']
        ch+"-"+
        if rand(2).zero?
          rand_char_including(/[^\\]/,disallow+bsonly)
        else
          rand_esc_seq disallow+"#",bsonly
        end
      end
    when 3:
      '#{'+rand(9999999999).to_s+'}'
    end
  end
  
  CACHE={}

  def Strgen.strgen
    must_be_escaped="#\\"
    case rand(3)
    when 0
      starter=ender=SIMPLE_QUOTES[rand(SIMPLE_QUOTES.size)]
      must_be_escaped<<starter
    when 1
      type=FANCY_TYPES[rand(FANCY_TYPES.size)]
      pair=PAIRS[rand(PAIRS.size)]
      starter= "%"+type+pair[0]
      ender= pair[1]
      must_be_escaped<<pair.to_s
    when 2
      type=FANCY_TYPES[rand(FANCY_TYPES.size)]
      q=rand_char_including ALLOWED_UNNESTING_FANCY
      /w/i===type and /\s|\v/===q and q='"'
      starter= "%"+type+q
      ender=q
      must_be_escaped<<q
    end
  
    if starter=="/" or type=='r'
      must_be_escaped+="[]{}()?+*"
    end
    must_be_escaped+="\0" if type=='s'
    ckey=must_be_escaped
    ordinary=
      CACHE[ckey]||=
        /[^#{must_be_escaped.gsub(/./){"\\"+$&}}]/

    interior=(1..rand(40)).map{|x|
      rand_char_including ordinary
    }.to_s

    interior["\\"] and fail

    disallow=''
    bsonly=starter[-1,1]+ender
#    disallow+='#' if /[\#\\\-]/===starter[-1,1]
#    disallow+=starter[-1,1]+ender if type=='r' or starter=='/'
    disallow+="\0" if type=='s'
    disallow+=must_be_escaped.gsub('\\','') if type=='r' or starter=='/'

    poslimit=interior.size+1
    rand(5).times{
      pos=rand poslimit
      interior[pos,0]=rand_esc_seq disallow,bsonly
      poslimit=pos
    } unless starter[-1]==?\\

    interior.gsub!(/\\[a-z]/i,'') if type=='r' or starter=='/'

    starter[-1]==?\r and interior.gsub!(/\A\n+/,'')

    starter[1]==?s and interior=='' and interior="x"

    result= starter+interior+ender

    begin
      begin eval "BEGIN{break};proc() do #{result} end" end while false
    rescue Exception
      #puts %<failing string: eval "#{result.gsub(/./){'\\x'+$&[0].to_s(16)}}">
      return strgen
    end

    return result
  end
end

if __FILE__==$0
start=Time.now
i=0
10_000_000.times{|i|
  begin
    ss=Strgen.strgen
    RubyLexerVsRuby.rubylexervsruby "-e#{i}", ss
  rescue Exception
    puts %<failing string: eval "#{ss.gsub(/./){'\\x'+$&[0].to_s(16)}}">
  end
}
end