Class: RedParse::StringNode

Inherits:
ValueNode show all
Defined in:
lib/redparse/node.rb,
lib/redparse/node.rb

Direct Known Subclasses

HereDocNode

Constant Summary collapse

ESCAPABLES =
{}
EVEN_NUM_BSLASHES =
/(^|[^\\])((?:\\\\)*)/
EVEN_BSS =
/(?:[^\\\s\v]|\G)(?:\\\\)*/
DQ_ESC =
/(?>\\(?>[CM]-|c)?)/
DQ_EVEN =
%r[
     (?:
      \A |
      [^\\c-] |
      (?>\A|[^\\])c |
      (?> [^CM] | (?>\A|[^\\])[CM] )-
     )              #not esc
     #{DQ_ESC}{2}*  #an even number of esc
]omx
DQ_ODD =
/#{DQ_EVEN}#{DQ_ESC}/omx
SQ_ESC =
/\\/
SQ_EVEN =
%r[
     (?:  \A | [^\\]  )  #not esc
     #{SQ_ESC}{2}*       #an even number of esc
]omx
SQ_ODD =
/#{SQ_EVEN}#{SQ_ESC}/omx
CHAROPT2NUM =
{
  ?x=>Regexp::EXTENDED,
  ?m=>Regexp::MULTILINE,
  ?i=>Regexp::IGNORECASE,
  ?o=>8,
}
CHARSETFLAG2NUM =
{
  ?n=>0x10,
  ?e=>0x20,
  ?s=>0x30,
  ?u=>0x40
}
DOWNSHIFT_STRING_TYPE =
{
  :dregx=>:lit,
  :dregx_once=>:lit,
  :dstr=>:str,
  :dxstr=>:xstr,
}
LETTER2ENCODING =
{
  ?n => Encoding::ASCII,
  ?u => Encoding::UTF_8,
  ?e => Encoding::EUC_JP,
  ?s => Encoding::SJIS,
  "" => Encoding::ASCII
}

Constants included from FlattenedIvars

FlattenedIvars::EXCLUDED_IVARS

Instance Attribute Summary collapse

Attributes inherited from Node

#endline, #errors, #offset, #parent, #startline

Attributes included from RedParse::Stackable::Meta

#boolean_identity_params, #identity_params

Instance Method Summary collapse

Methods inherited from ValueNode

#lvalue

Methods inherited from Node

#+, #+@, #==, [], #[]=, #add_parent_links!, #begin_parsetree, create, #data, #deep_copy, #delete_extraneous_ivars!, #delete_linenums!, #depthwalk_nodes, #error?, #evalable_inspect, #fixup_multiple_assignments!, #fixup_rescue_assignments!, inline_symbols, #inspect, #lhs_unparse, #linerange, #lvalue, #lvars_defined_in, #merge_replacement_session, namelist, #negate, #original_brackets_assign, param_names, #parsetrees, #pretty_print, #prohibit_fixup, #replace_ivars_and_self, #replace_value, #rescue_parsetree, #to_parsetree, #to_parsetree_and_warnings, #unary, #xform_tree!

Methods included from RedParse::Stackable::Meta

#build_exemplars, #enumerate_exemplars, #identity_param

Methods included from FlattenedIvars

#flattened_ivars, #flattened_ivars_equal?

Methods included from Stackable

#identity_name

Constructor Details

#initialize(token) ⇒ StringNode

Returns a new instance of StringNode.



3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
# File 'lib/redparse/node.rb', line 3277

def initialize(token)
  if HerePlaceholderToken===token 
    str=token.string
    @char=token.quote
  else
    str=token
    @char=str.char
  end
  @modifiers=str.modifiers #if str.modifiers
  super( *with_string_data(str) )

  @open=token.open
  @close=token.close
  @offset=token.offset
  @bs_handler=str.bs_handler

  if /[\[{]/===@char
    @parses_like=split_into_words(str)
  end

  return

=begin
#this should have been taken care of by with_string_data        
  first=shift
  delete_if{|x| ''==x }
  unshift(first)

#escape translation now done later on
  map!{|strfrag|
    if String===strfrag
      str.translate_escapes strfrag
    else
      strfrag
    end
  }
=end

end

Instance Attribute Details

#charObject (readonly) Also known as: type

,:data



3422
3423
3424
# File 'lib/redparse/node.rb', line 3422

def char
  @char
end

#modifiersObject (readonly)

,:data



3422
3423
3424
# File 'lib/redparse/node.rb', line 3422

def modifiers
  @modifiers
end

Instance Method Details

#depthwalk(*args, &callback) ⇒ Object



3413
3414
3415
3416
# File 'lib/redparse/node.rb', line 3413

def depthwalk(*args,&callback)
  return @parses_like.depthwalk(*args,&callback) if defined? @parses_like
  super
end

#endline=(endline) ⇒ Object



3481
3482
3483
3484
3485
3486
3487
# File 'lib/redparse/node.rb', line 3481

def endline= endline
  each{|frag| 
    frag.endline||=endline if frag.respond_to? :endline
  }

  super
end

#escapable(open = @open, close = @close) ⇒ Object



3377
3378
3379
3380
3381
3382
3383
3384
3385
# File 'lib/redparse/node.rb', line 3377

def escapable open=@open,close=@close
  unless escapable=ESCAPABLES[open]
    maybe_crunch='\\#' if %r{\A["`/\{]\Z} === @char and open[1] != ?q and open != "'" #"
    #crunch (#) might need to be escaped too, depending on what @char is
    escapable=ESCAPABLES[open]=
      /[#{Regexp.quote open[-1,1]+close}#{maybe_crunch}]/
  end
  escapable             
end

#imageObject



3406
# File 'lib/redparse/node.rb', line 3406

def image; '(#@char)' end

#initialize_ivarsObject



3316
3317
3318
3319
3320
3321
3322
3323
3324
# File 'lib/redparse/node.rb', line 3316

def initialize_ivars
  @char||='"' 
  @open||='"' 
  @close||='"' 
  @bs_handler||=:dquote_esc_seq
  if /[\[{]/===@char
    @parses_like||=split_into_words(str)
  end
end

#old_cat_initialize(*tokens) ⇒ Object

not needed anymore?



3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
# File 'lib/redparse/node.rb', line 3344

def old_cat_initialize(*tokens) #not needed anymore?
  token=tokens.shift
  
  tokens.size==1 or fail "string node must be made from a single string token"

  newdata=with_string_data(*tokens)

  case token
  when HereDocNode
    token.list_to_append=newdata
  when StringNode #do nothing
  else fail "non-string token class used to construct string node"
  end
  replace token.data

#        size%2==1 and last<<newdata.shift
  if size==1 and String===first and String===newdata.first
    first << newdata.shift
  end
  concat newdata
  
  @implicit_match=false
end

#parsetree(o) ⇒ Object



3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
# File 'lib/redparse/node.rb', line 3600

def parsetree(o)
  if size==1
    val=translate_escapes first
    type=case @char
         when '"',"'"; :str
         when '/'
           numopts=0
           charset=0
           RubyLexer::CharHandler.each_char(@modifiers){|ch| 
             if ch==?o
               type=:dregx_once
             elsif numopt=CHAROPT2NUM[ch].nonzero?
               numopts|=numopt
             elsif set=CHARSETFLAG2NUM[ch].nonzero?
               charset=set
             else fail
             end
           }
           val=Regexp.new val,numopts|charset
           :lit
         when '[','{'
           return @parses_like.parsetree(o)
=begin
           double_chunks=val.split(/([^\\]|\A)(?:\s|\v)/,-1)
           chunks=[]
           (0..double_chunks.size).step(2){|i| 
             chunks << double_chunks[i,2].to_s.gsub(/\\(\s|\v)/){$1}
           }
#                 last=chunks
#                 last.last.empty? and last.pop if last and !last.empty?

           words=chunks#.flatten
           words.shift if words.first.empty? unless words.empty?
           words.pop if words.last.empty? unless words.empty?
           return [:zarray] if words.empty? 
           return words.map{|word| [:str,word]}.unshift(:array)
=end

         when '`'; :xstr
         else raise "dunno what to do with #@char<StringToken"
         end
    result=[type,val]
  else
    saw_string=false
    vals=[]
    each{|elem| 
      case elem
      when String
        was_esc_nl= (elem=="\\\n") #ick
        elem=translate_escapes elem
        if saw_string
          vals.push [:str, elem] if !elem.empty? or was_esc_nl
        else
          saw_string=true
          vals.push elem
        end
      when NopNode
        vals.push [:evstr]
      when Node #,VarNameToken
        res=elem.parsetree(o)
        if res.first==:str and @char != '{'
          vals.push res
        elsif res.first==:dstr and @char != '{'
          vals.push [:str, res[1]], *res[2..-1]
        else
          vals.push [:evstr, res]
        end
      else fail "#{elem.class} not expected here"
      end
    }
    while vals.size>1 and vals[1].first==:str
      vals[0]+=vals.delete_at(1).last
    end
    #vals.pop if vals.last==[:str, ""]

    type=case @char
         when '"'; :dstr
         when '/'
           type=:dregx
           numopts=charset=0
           RubyLexer::CharHandler.each_char(@modifiers){|ch| 
             if ch==?o
               type=:dregx_once
             elsif numopt=CHAROPT2NUM[ch].nonzero?
               numopts|=numopt
             elsif set=CHARSETFLAG2NUM[ch].nonzero?
               charset=set
             end
           }
           regex_options= numopts|charset unless numopts|charset==0
           val=/#{val}/
           type
         when '{'
           return @parses_like.parsetree(o)
=begin
           vals[0]=vals[0].sub(/\A(\s|\v)+/,'') if /\A(\s|\v)/===vals.first
           merged=Array.new(vals)
           result=[]
           merged.each{|i|
             if String===i
               next if /\A(?:\s|\v)+\Z/===i 
               double_chunks=i.split(/([^\\]|\A)(?:\s|\v)/,-1)
               chunks=[]
               (0..double_chunks.size).step(2){|ii| 
                 chunks << double_chunks[ii,2].to_s.gsub(/\\(\s|\v)/){$1}
               }
               words=chunks.map{|word| [:str,word]}
               if !result.empty? and frag=words.shift and !frag.last.empty?
                 result[-1]+=frag
               end
               result.push( *words )
             else
               result.push [:str,""] if result.empty?
               if i.first==:evstr and i.size>1 and i.last.first==:str
                 if String===result.last[-1]
                   result.last[-1]+=i.last.last
                 else
                   result.last[0]=:dstr
                   result.last.push(i.last)
                 end
               else
                 result.last[0]=:dstr
                 result.last.push(i)
               end
             end
           }
           return result.unshift(:array)
=end

         when '`'; :dxstr
         else raise "dunno what to do with #@char<StringToken"
         end

    if vals.size==1
      if :dregx==type or :dregx_once==type
        lang=@modifiers.tr_s("^nesuNESU","")
        lang=lang[-1,1] unless lang.empty?
        lang.downcase!
        regex_options=nil
        vals=[Regexp_new( vals.first,numopts,lang )]
      end
      type=DOWNSHIFT_STRING_TYPE[type]
    end
    result= vals.unshift(type)
    result.push regex_options if regex_options
  end
  result=[:match, result] if defined? @implicit_match and @implicit_match
  return result
end

#Regexp_new(src, opts, lang) ⇒ Object



3756
3757
3758
3759
# File 'lib/redparse/node.rb', line 3756

def Regexp_new(src,opts,lang)
  src.encode!(LETTER2ENCODING[lang])
  Regexp.new(src,opts)
end

#special_conditions!Object



3418
3419
3420
# File 'lib/redparse/node.rb', line 3418

def special_conditions!
  @implicit_match= @char=="/"
end

#split_into_words(strtok) ⇒ Object



3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
# File 'lib/redparse/node.rb', line 3513

def split_into_words strtok
  @offset=strtok.offset
  return unless /[{\[]/===@char
  result=ArrayLiteralNode[]
  result << StringNode['',{:@char=>'"',:@open=>@open,:@close=>@close,:@bs_handler=>@bs_handler}]
  proxy=dup
  proxy[0]=proxy[0][/\A(?:\s|\v)+(.*)\Z/m,1] if /\A(?:\s|\v)/===proxy[0]
#        first[/\A(?:\s|\v)+/]='' if /\A(?:\s|\v)/===first #uh-oh, changes first
  proxy.each{|x|
    if String===x
#            x=x[/\A(?:\s|\v)+(.*)\Z/,1] if /\A[\s\v]/===x
if false
      #split on ws preceded by an even # of backslashes or a non-backslash, non-ws char
      #this ignores backslashed ws
      #save the thing that preceded the ws, it goes back on the token preceding split
      double_chunks=x.split(/( #{EVEN_BSS} | (?:[^\\\s\v]|\A|#{EVEN_BSS}\\[\s\v]) )(?:\s|\v)+/xo,-1)
      chunks=[]
      (0..double_chunks.size).step(2){|i| 
        chunks << #strtok.translate_escapes            double_chunks[i,2].to_s #.gsub(/\\([\s\v\\])/){$1}
      }
else
      #split on ws, then ignore ws preceded by an odd number of esc's
      #esc is \ in squote word array, \ or \c or \C- or \M- in dquote
      chunks_and_ws=x.split(/([\s\v]+)/,-1)
      start=chunks_and_ws.size; start-=1 if start&1==1
      chunks=[]
      i=start+2; 
      while (i-=2)>=0 
        ch=chunks_and_ws[i]||""
        if i<chunks_and_ws.size and ch.match(@char=="[" ? /#{SQ_ODD}\Z/omx : /#{DQ_ODD}\Z/omx)
          ch<< chunks_and_ws[i+1][0,1]
          if chunks_and_ws[i+1].size==1
            ch<< chunks.shift
          end
        end
        chunks.unshift ch
      end
end

      chunk1= chunks.shift          
      if chunk1.empty?
        #do nothing more
      elsif String===result.last.last
        result.last.last << chunk1
      else
        result.last.push chunk1
      end
#            result.last.last.empty? and result.last.pop
      result.concat chunks.map{|chunk| 
        StringNode[chunk,{:@char=>'"',:@open=>@open,:@close=>@close,:@bs_handler=>@bs_handler}]
      }
    else
      #result.last << x
      unless String===result.last.last
        result.push StringNode["",{:@char=>'"',:@open=>@open,:@close=>@close,:@bs_handler=>@bs_handler}]
      end
      result.last.push x
#            result.push StringNode["",x,{:@char=>'"',:@open=>@open,:@close=>@close,:@bs_handler=>@bs_handler}]
    end
  } 
  result.shift if StringNode&-{:size=>1, :first=>''}===result.first
  result.pop if StringNode&-{:size=>1, :first=>''}===result.last

  return result
end

#to_lispObject



3489
3490
3491
3492
# File 'lib/redparse/node.rb', line 3489

def to_lisp
  return %{"#{first}"} if size<=1 and @char=='"'
  huh
end

#translate_escapes(str) ⇒ Object



3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
# File 'lib/redparse/node.rb', line 3326

def translate_escapes(str)
  rl=RubyLexer.new("(string escape translation hack...)",'')
  result=str.dup
  seq=result.to_sequence
  rl.instance_eval{@file=seq}
  repls=[]
  i=0
  #ugly ugly ugly... all so I can call @bs_handler
  while i<result.size and bs_at=result.index(/\\./m,i)
    seq.pos=$~.end(0)-1
    ch=rl.send(@bs_handler,"\\",@open[-1,1],@close)
    result[bs_at...seq.pos]=ch
    i=bs_at+ch.size
  end

  return  result
end

#unparse(o = default_unparse_options) ⇒ Object



3370
3371
3372
3373
3374
3375
# File 'lib/redparse/node.rb', line 3370

def unparse o=default_unparse_options
  o[:linenum]+=@open.count("\n")
  result=[@open,unparse_interior(o),@close,@modifiers].join
  o[:linenum]+=@close.count("\n")
  return result
end

#unparse_interior(o, open = @open, close = @close, escape = nil) ⇒ Object



3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
# File 'lib/redparse/node.rb', line 3387

def unparse_interior o,open=@open,close=@close,escape=nil
  escapable=escapable(open,close)
  result=map{|substr|
    if String===substr

      #hack: this is needed for here documents only, because their
      #delimiter is changing.
      substr.gsub!(escape){|ch| ch[0...-1]+"\\"+ch[-1,1]} if escape

      o[:linenum]+=substr.count("\n") if o[:linenum]

      substr
    else
      ['#{',substr.unparse(o),'}']
    end
  }
  result
end

#walk(*args, &callback) ⇒ Object



3408
3409
3410
3411
# File 'lib/redparse/node.rb', line 3408

def walk(*args,&callback)
  @parses_like.walk(*args,&callback) if defined? @parses_like
  super
end

#with_string_data(token) ⇒ Object



3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
# File 'lib/redparse/node.rb', line 3425

def with_string_data(token)
#        token=tokens.first

#        data=tokens.inject([]){|sum,token|
#          data=elems=token.string.elems
    data=elems=
      case token
      when StringToken; token.elems
      when HerePlaceholderToken; token.string.elems
      else raise "unknown string token type: #{token}:#{token.class}"
      end
#          sum.size%2==1 and sum.last<<elems.shift
#          sum+elems
#        } 
#        endline=@endline
  1.step(data.length-1,2){|i|
    tokens=data[i].ident.dup
    line=data[i].linenum

    #replace trailing } with EoiToken
    (tokens.size-1).downto(0){|j| 
       tok=tokens[j]
       break(tokens[j..-1]=[EoiToken.new('',nil,tokens[j].offset)]) if tok.ident=='}' 
    }
    #remove leading {
    tokens.each_with_index{|tok,j| break(tokens.delete_at j) if tok.ident=='{' }

    if tokens.size==1 and VarNameToken===tokens.first
      data[i]=VarNode.new tokens.first
      data[i].startline=data[i].endline=token.endline
      data[i].offset=tokens.first.offset
    else
      #parse the token list in the string inclusion
      parser=Thread.current[:$RedParse_parser]
      klass=parser.class
      data[i]=klass.new(tokens, "(string inclusion)",1,[],:rubyversion=>parser.rubyversion,:cache_mode=>:none).parse
    end
  } #if data
#        was_nul_header= (String===data.first and data.first.empty?) #and o[:quirks]
  last=data.size-1

  #remove (most) empty string fragments
  last.downto(1){|frag_i| 
    frag=data[frag_i]
    String===frag or next
    next unless frag.empty? 
    next if frag_i==last #and o[:quirks]
    next if data[frag_i-1].endline != data[frag_i+1].endline #and o[:quirks]
            #prev and next inclusions on different lines
    data.slice!(frag_i)
  }
#        data.unshift '' if was_nul_header

  return data
end