Class: RedParse::StringNode

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

Direct Known Subclasses

HereDocNode

Constant Summary collapse

ESCAPABLES =
{}
EVEN_NUM_BSLASHES =
/(^|[^\\])((?:\\\\)*)/
EVEN_BSS =
/(?:[^\\\s\v]|\G)(?:\\\\)*/
DQ_ESC =
/(?>\\(?>[CM]-|c)?)/
DQ_EVEN =
%r[
     (?:
      \G |
      [^\\c-] |
      (?>\G|[^\\])c |
      (?> [^CM] | (?>\G|[^\\])[CM] )-
     )              #not esc
     #{DQ_ESC}{2}*  #an even number of esc
]omx
DQ_ODD =
/#{DQ_EVEN}#{DQ_ESC}/omx
SQ_ESC =
/\\/
SQ_EVEN =
%r[
     (?:  \G | [^\\]  )  #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, #reducer_method

Methods inherited from Node

#+, #+@, #==, [], #[]=, #add_parent_links!, #args_rip, #begin_parsetree, #classic_inspect, create, #data, #deep_copy, #delete_extraneous_ivars!, #delete_linenums!, #depthwalk_nodes, #error?, #evalable_inspect, #fixup_multiple_assignments!, #fixup_rescue_assignments!, #force_stmt_list_rip, 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, #rfind, #rfind_all, #rgrep, #rip_and_rescues, #rip_explode!, #short_inspect, #stmts_rip, #to_parsetree, #to_parsetree_and_warnings, #to_ruby, #to_s, #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.



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
# File 'lib/redparse/node.rb', line 3531

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



3682
3683
3684
# File 'lib/redparse/node.rb', line 3682

def char
  @char
end

#modifiersObject (readonly)

,:data



3682
3683
3684
# File 'lib/redparse/node.rb', line 3682

def modifiers
  @modifiers
end

Instance Method Details

#depthwalk(*args, &callback) ⇒ Object



3673
3674
3675
3676
# File 'lib/redparse/node.rb', line 3673

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

#endline=(endline) ⇒ Object



3740
3741
3742
3743
3744
3745
3746
# File 'lib/redparse/node.rb', line 3740

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

  super
end

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



3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
# File 'lib/redparse/node.rb', line 3632

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
    quotes=open[-1,1]
    quotes+=close unless quotes==close
    escapable=ESCAPABLES[open]=
      /[#{Regexp.quote(quotes)}#{maybe_crunch}]/
  end
  escapable             
end

#imageObject



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

def image; '(#@char)' end

#initialize_ivarsObject



3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
# File 'lib/redparse/node.rb', line 3570

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

#old_cat_initialize(*tokens) ⇒ Object

not needed anymore?



3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
# File 'lib/redparse/node.rb', line 3598

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



3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
# File 'lib/redparse/node.rb', line 3859

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

#reducer_identObject



17620
17621
17622
# File 'lib/redparse/ReduceWithsFor_RedParse_1_9.rb', line 17620

def reducer_ident
  :StringNode
end

#Regexp_new(src, opts, lang) ⇒ Object



4015
4016
4017
4018
# File 'lib/redparse/node.rb', line 4015

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

#rip(p) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/redparse/ripper.rb', line 221

def rip p
  list=self.dup
  list.shift if String===list.first and list.first.empty?
  p.on_string_literal \
    list.inject(p.on_string_content){|sum,chunk|
      p.on_string_add sum,
        if String===chunk
          p.on_tstring_content chunk
        else
          p.on_string_embexpr chunk.rip(p)
        end
    }
end

#special_conditions!Object



3678
3679
3680
# File 'lib/redparse/node.rb', line 3678

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

#split_into_words(strtok) ⇒ Object



3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
# File 'lib/redparse/node.rb', line 3772

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



3748
3749
3750
3751
# File 'lib/redparse/node.rb', line 3748

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

#translate_escapes(str) ⇒ Object



3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
# File 'lib/redparse/node.rb', line 3581

def translate_escapes(str)
  rl=RubyLexer.new("(string escape translation hack...)",'')
  result=str.dup
  seq=result.to_sequence
  rl.instance_eval{@file=seq}
  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



3624
3625
3626
3627
3628
3629
3630
# File 'lib/redparse/node.rb', line 3624

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

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



3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
# File 'lib/redparse/node.rb', line 3644

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

      #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
    when NopNode
      '#{}'
    else
      ['#{',substr.unparse(o),'}']
    end
  }
  result
end

#walk(*args, &callback) ⇒ Object



3668
3669
3670
3671
# File 'lib/redparse/node.rb', line 3668

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

#with_string_data(token) ⇒ Object



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
# File 'lib/redparse/node.rb', line 3685

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

    #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