#! /usr/local/bin/ruby # This is a snapshot of an old perl6-on-ruby implementation. # See README. # Uses emacs ruby and outline mode. setq at bottom colorizes heredoc code. # Ruby 1.9.0 (or higher?) required (for real lambdas). # Disclaimer - most of this code was intended to be thown away on a # timescale of a couple of weeks. But the project terminated first. ###* RedSix ###** Dual boot, ruby/perl6. #( $ruby_init_code = <<'_CODE' def caller_file_line(n=1) file,line = caller[n].split(/:/); line = line.to_i [file,line+1] end def source_ruby(s) file,line = caller_file_line s_len = s.split(/\n/).size startline = line # - s_len eval(s,nil,file,startline) end # def source_perl(*a) end # def source_perl_finish(*a) end _CODE eval($ruby_init_code) #) source_ruby(<<'_END' require 'digest/md5' module Kernel; def chksum(s) Digest::MD5.hexdigest(s) end end $perlcode_to_eval = [] def source_perl_compiled(sum,rubycode,perlcode) file,line = *caller_file_line eval(rubycode,$P.P6_binding,file,line) #File.open("deleteme_02","w"){|f|f.write perlcode.split(//).join("\n")} if chksum(perlcode) != sum print "# Perl source #{caller_file_line.join(':')} changed. I will eval() it later.\n" #p sum; p perlcode.length,chksum(perlcode) linepl = line + rubycode.split(/\n/).size $perlcode_to_eval.push([perlcode,[file,linepl]]) end end def source_perl(perlcode) fileline = caller_file_line perl_eval(perlcode,fileline) end def source_perl_finish $perlcode_to_eval.each{|(c,fileline)| perl_eval(c,fileline)} end def perl_eval(perlcode,fileline=nil) $P.eval6q(perlcode,fileline) if not (ENV['TEST_ONLY_PARSE'] && ENV['TEST_ONLY_PARSE'] != 0) end _END ); ; source_ruby(<<'_END' $LogP = File.new("deletemeparse.rb","w") ###** Regex bootstrap ###*** Engine require 'strscan' class Engine attr_accessor :scanner,:choicepoints,:rest,:stack,:match,:grammar def initialize(scanner,pat,mat,grammar) @scanner = scanner @choicepoints = [] @rest = pat.dup @stack = [] @match = mat.skim_state_copy @grammar = grammar end def run backtracking = false failed = false top_cp = nil debug = nil #||true parse_debug = nil #||true $LogP.print "Engine IN\n" if debug while true if @rest.empty? $LogP.print "Engine OUT - Success\n" if debug return @match end top = @rest.pop $LogP.print("===============================\n", @rest[-1].inspect.slice(0..50),"\n", top.inspect.slice(0..150),"\n") if debug case top when :apat when Regexp if @scanner.scan(top) || (@scanner.eos? && "" =~ top) if nil || parse_debug #||true @stack.each{|e| $LogP.print(e,"/") if e.class == String};$LogP.print "\n" p1 = @scanner.pos p0 = p1 - 20; p0 = 0 if p0 < 0 s0 = @scanner.string.slice(p0,p1-p0) s1 = @scanner.string.slice(p1,50) s0.gsub!(/\n/){|w|'\n'};s0.gsub!(/\t/){|w|'\t'} s1.gsub!(/\n/){|w|'\n'};s1.gsub!(/\t/){|w|'\t'} $LogP.print "\033[0;7;34m#{s0}\033[0m#{s1}\n" end else if nil || parse_debug #||true path = '' @stack.each{|e| path += (e + "/") if e.class == String} if path != '' $LogP.print "\033[0;31m#{path}\033[0m" $LogP.print " #{top.inspect}\n" end end failed = true end $LogP.print top.inspect,' ',(failed ? 'failed' : 'ok'),"\n" if debug when Array case top[0] when :alt if not backtracking n = 0 if top[1].size > 1 @choicepoints.push([@scanner.pos,(@rest+[top]),@stack,@match,n]) end fail "assert" if top[1].size < 1 @rest += top[1][n].reverse else n = top_cp[-1] n += 1 fail "bug" if n >= top[1].size if n < (top[1].size - 1) @choicepoints.push([@scanner.pos,(@rest+[top]),@stack,@match,n]) end if n < top[1].size @rest += top[1][n].reverse end end when :rep_bstar _op,p,left=*top; if left > 0 then @rest += [[:alt,[p+[[:rep_bstar,p,left-1]],[] ] ]] end when :rep_bstar_ng _op,p,left=*top; if left > 0 then @rest += [[:alt,[[],p+[[:rep_bstar_ng,p,left-1]] ] ]] end when :rep _op,p,min,max,ng=*top @rest += ((p * min)+(ng ? [[:rep_bstar_ng,p,max-min]] : [[:rep_bstar,p,max-min]])).reverse when :seq @rest += top[1].reverse when :match_open #$LogP.print "------open\n" _op,rul,nam=*top @match = Match.new_from(@scanner.string,@scanner.pos) @match.rule = rul @stack = @stack.dup @stack.push("#{nam}") when :match_close #$LogP.print "------close\n" fail("splat\n#{caller.join("\n")}") if !@match @match = @match.skim_state_copy.close!(@scanner.pos) @stack = @stack.dup fail "assert" if not @stack.pop.class == String when :match_push @stack = @stack.dup @stack.push(@match.skim_state_copy) when :match_pop _op,nam=*top @stack = @stack.dup prev = @stack.pop.skim_state_copy if nam val = @match.skim_state_copy nam = val.rule if nam == true; fail "assert" if not nam if prev crnt = prev.hash[nam] if crnt crnt = [crnt] if crnt.class != Array val = crnt+[val] end end prev.hash[nam] = val if nam end @match = prev when :skimC _op,name,argl=*top argl ||= [] r = @grammar.method(name).call out = r.skimC(*argl) if out then @rest += out.reverse else failed = true end when :skimCo _op,obj,argl=*top argl ||= [] out = obj.skimC(*argl) if out then @rest += out.reverse else failed = true end when :full_skimCf _op,out=*top res = out.skimCf(top,@scanner,@rest,@stack,@match,@choicepoints) if res then top,@scanner,@rest,@stack,@match,@choicepoints = *res else failed = true end when :proc_full _op,out=*top res = out.call(top,@scanner,@rest,@stack,@match,@choicepoints) if res then top,@scanner,@rest,@stack,@match,@choicepoints = *res else failed = true end else fail "bug '#{top[0]}'" end else fail "bug >#{top}<" end backtracking=false $LogP.print "Pass #{failed ? 'failed' : 'worked'}\n" if debug $LogP.print "Choicepoints #{choicepoints.empty? ? 'empty' : @choicepoints.size}\n" if debug if failed if @choicepoints.empty? $LogP.print "Engine OUT - Failure\n" if debug return nil end top_cp = @choicepoints.pop @scanner.pos,@rest,@stack,@match = *top_cp $LogP.print "Backtracking to pos ",@scanner.pos," cp ",@choicepoints.size,"\n ",top_cp.inspect,"\n ",@rest[-1].inspect,"\n" if debug backtracking=true failed=false end end end end ###*** Match module MatchDescribe def match_describe(seen=nil) seen ||= {} return ("LOOP***"+match_describe_name) if seen.member?(self.object_id); seen[self.object_id] = true indent = ->(s){ s ? s.gsub(/(?m)^(?!\Z)/,' ') : '*nil*' } indent_except_top = ->(s){ s ? s.gsub(/(?m)^(?!\Z)/,' ').sub(/^ /,'') : '*nil*' } n = match_describe_name b = as_b ? 'true' : 'false' s = "'"+indent_except_top.call(as_s).gsub(/([\\'])/){|w|"\\#{w}"}+"'" a = as_a.map{|m| "\n"+indent.call(m.match_describe(seen))+"," }.join("") a += "\n" if a != "" h = as_h.map{|k,v| vs = if v.instance_of?(Array) "[\n" + indent.call(v.map{|m|m.match_describe(seen)}.join(",\n"))+"\n]" else v.match_describe(seen) end "\n #{k} => #{indent_except_top.call(vs)}," }.join("") h += "\n" if h != "" f = match_beg t = match_end "#{n}<#{b},#{s},#{f}-#{t},[#{a}],{#{h}}>" end def match_describe_name() "#{self.class}:#{object_id.to_s(36)}" end end class Match attr_accessor :on_str,:from,:to,:bool,:hash attr_writer :str attr_accessor :rule def initialize(on_str,from,to,bool,hash,str,rule=nil) @on_str,@from,@to,@bool,@hash,@str,@rule=on_str,from,to,bool,hash,str,rule end def self.new_from(on_str,from=0,to=nil) self.new(on_str,from,to,true,{},nil) end def skim_state_copy self.class.new(@on_str,@from,@to,@bool,@hash.skim_state_copy,@str,@rule) end def str(to=nil) @str || @on_str.slice(@from,(@to||to)-@from) end def close!(to) if @from # otherwise already .failed! @to = to @str = @on_str.slice(@from,@to-@from) if @from end self end def failed! @bool,@str,@hash,@from,@to = false,'',{},nil,nil self end def closer_proc SkimProc.new{|s,c| self.close!(s.pos); c.cont0(s)} end include MatchDescribe def as_b; @bool end; def as_s; @str end; def as_a; [] end; def as_h; @hash end def match_beg; @from end; def match_end; @to end def match_describe_name; "#{super}:#{rule ? rule.name : 'nil'}" end def inspect; match_describe end end class Object def skim_state_copy; self end end class Hash def skim_state_copy; cp = self.class.new; self.each{|k,v| cp[k]=v.skim_state_copy}; cp end end class Array def skim_state_copy; cp = dup; cp.map!{|e|e.skim_state_copy}; cp end end ###*** Rule class Rule attr_accessor :name,:raw_patA def initialize(*args) @name,@raw_patA=*args end def search(str,pos=0,can_skip=true) (pos..str.length).each{|p| scanner = StringScanner.new(str) scanner.pos = pos ev = Engine.new(scanner,[:apat,[:skimCo,self]],nil,$grammar).run return ev if ev && ev.bool break if !can_skip } return Match.new_from(str,pos).failed! end def skimC [:apat,[:seq,[ [:match_open,self,@name], [:seq,@raw_patA], [:match_close] ]]] end end class String def search(rule,pos=0,can_skip=true) rule.search(self,pos,can_skip) end end ###*** OperatorPrecedenceParser require 'digest/md5' class OperatorPrecedenceParser attr_accessor :tokens,:ws def initialize(tokens,ws) @tokens,@ws=tokens,ws end def skimC(token_filter=nil) [:apat,[:full_skimCf,OperatorPrecedenceParser::Parsing.new(@tokens,@ws,token_filter)]] end end class OperatorPrecedenceParser::Parsing @@depth = 0 def log(msg) if nil || true sp = " " * @@depth id = Digest::MD5.hexdigest(object_id.to_s).slice(0,6) $LogP.print "#{sp}OPP #{id} #{msg}\n" end end class Edge;end BOS = Edge.new EOS = Edge.new attr_accessor :tokens,:ws,:s,:token_filter def initialize(tokens,ws,token_filter=nil) @tokens,@ws,@token_filter=tokens,ws,token_filter end def skimCf(*args) @@depth += 1 scanner=args[1] @s=scanner ok = parse if ok log "OPP-PARSE succeeded\n" @@depth -= 1 args[4] = ok args else log "OPP-PARSE failed\n" @@depth -= 1 false end end attr_accessor :saw_ws def eat_ws m = @ws.search(@s.string,@s.pos,false) if m.bool @s.pos = m.to m else false end end def lexeme_compatible_with_context?(l,prepost,ws) if ws != :dontcare lws = l.whitespace_on_left return false if not case lws when nil; true when :prohibit; !ws when :require; !!ws when :ws_irrelevant; true else fail("assert: #{lws}") end end if prepost != nil lexp = l.expression_on_left return false if not case lexp when :need_expr case prepost when :pre; nil when :post; true when :post_commalike; l.is_commalike else fail('assert') end when :no_expr_needed; prepost == :pre else fail("assert: #{lexp}") end end return true end attr_accessor :prepost def lex(current_lexemes) log "LEX"; log "Lex'ing at #{@s.pos} >#{@s.string.slice(@s.pos,20)}" @saw_ws = eat_ws log "Lex saw ws" if @saw_ws log "Lex context: #{@prepost} #{@saw_ws ? 'ws' : 'nows'}" matches = [] ss = StringScanner.new(@s.string); ss.pos = @s.pos # log "BEGIN =================================" current_lexemes.each{|l| key = l.competition_key # log "x# #{key.inspect} \t#{l.name}" len = nil if key && key.is_a?(Regexp) x = (ss.scan(key) || (ss.eos? && "".match(key) && "")) # log l.name.inspect+' '+(x ? 'OK' : "FAILED #{key.inspect}")+"" next if not x ss.pos = @s.pos len = x.length end next if not lexeme_compatible_with_context?(l,nil,@saw_ws) m = l.search(@s.string,@s.pos,false) next if not m.bool len = m.hash[key].to - m.from if !len && key.is_a?(Symbol) len = m.to - m.from if !len matches.push([m,l,len]) } log "Lex #{matches.size} candidates" ms = matches.sort{|a,b| b[2] <=> a[2]} if not ms.empty? at = ms[0][2] log "Lex target #{at}" # p "=================== ",at,ms[0][0].inspect,'-----' ms = ms.find_all{|a| log "...#{a[2]} #{a[1].name}" at == a[2] and lexeme_compatible_with_context?(a[1],@prepost,:dontcare) } end log "Lex #{ms.size} matches" if ms.size > 1 then STDERR.print "\nWARNING: ambiguous parse: #{ms[0][0].as_s}\n" ms.each{|a| STDERR.print " #{a[1].name}\n"} end m = ms.empty? ? nil : ms[0][0] log "Lex RESULT: #{m.inspect}" return nil if not m @s.pos = m.to log "Lex now at #{@s.pos}" @saw_ws = false m end attr_accessor :ops,:opands,:ends,:ends_sz attr_accessor :tok attr_accessor :lexemes def init @lexemes = @tokens.map{|t|t.first_lexeme} @lexemes_at_top_level = @lexemes if @token_filter top_level_tokens = @tokens.find_all{|t| @token_filter.call(t)} @lexemes_at_top_level = top_level_tokens.map{|t|t.first_lexeme} end @prepost = :pre @saw_ws = false @ops = [BOS] @opands = [] @ends = []; @ends_sz = [] @tok = nil end def parse log "OPP-PARSE" init catch(:parse_fail) {parser_loop} end def log_state log "Currently:\n\topands: #{@opands.inspect}\n\tops: #{@ops.inspect}\n\ttok: #{@tok.inspect}" end def parser_loop while true log "Loop" get_token log_state if nil #||true if @tok == EOS break end log @tok.rule.inspect if nil #||true if not @tok.rule.token.is_operator? operand elsif @ops.size == 1 shift elsif @tok.rule == @ends[-1] crunch elsif (@ops[-1].rule.expression_on_right == :no_expr_needed && !@ops[-1].rule.next_lexeme) reduce elsif @tok.rule.expression_on_left == :no_expr_needed shift else st = @ops[-1].rule.precedence_on_right tk = @tok.rule.precedence_on_left if st < tk; shift elsif st > tk; reduce elsif st == 0 and tk == 0 shift # eg, prelist with circumfix elsif @tok.rule.associativity == :right shift else reduce end end end log "EOS processing" reduce while @ops.size > 1 error if not (@ops == [BOS] and @opands.size == 1 and @ends == [] and @tok == EOS) @s.pos = @saw_ws.from if @saw_ws @opands[0] end def get_token return if @tok close = @ends[-1] current_lexemes = close ? @lexemes + [close] : @lexemes_at_top_level @tok = lex(current_lexemes) || EOS end def operand log "operand" m = @tok; @tok = nil @opands.push(m) currently_argument_list_top_level = @token_filter && !@ends[-1] @prepost = m.rule.token.operand_post @prepost = :post if (@prepost == :post_commalike && !currently_argument_list_top_level) end def shift log "shift" m = @tok; @tok = nil @ops.push(m) eme = m.rule.next_lexeme if eme @ends.push(eme) @ends_sz.push(@ops.size) end @prepost = prepost_from_rule(@ops[-1].rule) end def reduce log "reduce" m0 = @ops.pop t = m0.rule.token count = t.part_count - 1 # all but m0 are on @opands if @opands.size < count log "reduce problem - not enough opands (#{@opands.size} vs #{count})" log @opands.inspect error end parts = @opands.slice!(0-count,count) m = t.create_match([m0,*parts]) @opands.push(m) @prepost = prepost_from_rule(@ops[-1].rule) if @ops[-1] != BOS end def crunch log "crunch" fail('assert') if @tok.rule != @ends[-1] m = @tok t = m.rule.token reduce while @ops.size > @ends_sz[-1] fail('assert') if @ops[-1].rule.token != @ends[-1].token @ends.pop; @ends_sz.pop @opands.push(@ops.pop) shift end def prepost_from_rule(r) r.expression_on_right == :need_expr ? :pre : :post end def error log "ERROR" log " op #{@ops[-1].inspect}" log " tok #{@tok.inspect}" throw :parse_fail,nil end end ###**** Tokens class Lexeme < Rule attr_accessor :token attr_accessor :competition_key attr_accessor :whitespace_on_left attr_accessor :expression_on_left,:expression_on_right attr_accessor :precedence_on_left,:precedence_on_right attr_accessor :associativity attr_accessor :next_lexeme attr_accessor :is_commalike def initialize(name,f,len=nil) super(name,f) @competition_key = len end end class Token_Operand attr_accessor :first_lexeme attr_accessor :operand_post def is_operator?; false end def initialize(name,f,len=nil) @first_lexeme = l = Lexeme.new(name,f,len) l.token = self l.whitespace_on_left = :ws_irrelevant l.expression_on_left = :no_expr_needed l.expression_on_right = :no_expr_needed l.is_commalike = false @operand_post = :post end end class Token_Operator attr_accessor :fixity,:strings,:precedence,:assoc,:ws_policy attr_accessor :arity,:part_count attr_accessor :name attr_accessor :first_lexeme attr_accessor :fixity_extra def is_operator?; true end def initialize(*args) @name,@fixity,@fixity_extra,@strings,@precedence,@assoc,@ws_policy,@is_commalike=*args @fixity=@fixity.to_sym @precedence ||= @@precgen.default_precedence @name = @name || "#{@fixity}:#{@strings.join(' ')}" # build lexemes lexemes = []; regexps = @strings.each_with_index{|str_or_re,i| re=nil native_re = nil if str_or_re.is_a?(String) pat = str_or_re.gsub(/(\W)/){|w|"\\#{w}"}; native_re = /#{pat}/ re = [:apat,/#{pat}/] else re = str_or_re end lexemes.push(Lexeme.new("#{name}:#{i}",re,native_re)) } # ... and connect them @first_lexeme = lexemes[0] lexemes[0].next_lexeme = lexemes[1] || nil lexemes[1].next_lexeme = nil if lexemes[1] # ... and set defaults lexemes.each{|l| l.token = self l.whitespace_on_left = nil l.associativity = @assoc l.is_commalike = @is_commalike } lexemes[0].whitespace_on_left = @ws_policy # finish up set = ->(n,el,pl,er,pr){ lexemes[n].expression_on_left = el ? :need_expr : :no_expr_needed lexemes[n].expression_on_right = er ? :need_expr : :no_expr_needed lexemes[n].precedence_on_left = pl ? @precedence : 0 lexemes[n].precedence_on_right = pr ? @precedence : 0 } case @fixity when :prefix @arity = 1; @part_count = 2 set[0, nil,nil, :e,:p] when :postfix @arity = 1; @part_count = 2 set[0, :e,:p, nil,nil] when :circumfix @arity = 1; @part_count = 3 set[0, nil,nil, :e,nil] set[1,:e,nil, nil,nil] when :infix @arity = 2; @part_count = 3 set[0, :e,:p, :e,:p] when :ternary @arity = 3; @part_count = 5 set[0, :e,:p, :e,nil] set[1, :e,nil, :e,:p] when :postcircumfix @arity = 2; @part_count = 4 set[0, :e,:p, :e,nil] set[1, :e,nil, nil,nil] else fail "assert #{@fixity}" end lexemes[-1].precedence_on_right = 0 if @fixity_extra == :list end def create_match(parts) $LogP.print parts.inspect parts.sort!{|a,b| a.from <=> b.from } m = Match.new_from(parts[0].on_str) m.rule = self m.hash[:parts] = parts m.from = parts[0].from m.close!(parts[-1].to) m end class PrecedenceGenerator attr :delta def initialize() @delta = 0.5 end def consume_a_delta() delta = @delta; @delta /= 2; delta end def default_precedence() 1 end def new_precedence_tighter_than(prec) prec + consume_a_delta end def new_precedence_looser_than (prec) prec - consume_a_delta end end @@precgen = PrecedenceGenerator.new def tighter; @@precgen.new_precedence_tighter_than(@precedence) end def looser; @@precgen.new_precedence_looser_than(@precedence) end def equiv; @precedence end end ###** Six ###*** Grammar class Grammar def self.abbrev(arg) arg.map{|a|a.is_a?(Symbol) ? sr(a) : a.is_a?(String) ? sym(a) : a.is_a?(Regexp) ? [:apat,a] : a } end def self.mangle(s) s.to_s.gsub(/z/,'zz').gsub(/([^a-z0-9])/i){|w|sprintf("z%0Xz",w[0])} end def self.alt(*specs) [:apat,[:alt,abbrev(specs)]] end def self.seq(*spec) p=[:apat];abbrev(spec).map{|e|p.push(*e)}; p end def self.rep(r,min=0,max=2**24,ng=nil) [:apat,[:rep,abbrev([r])[0],min,max,ng]] end def self.star(*args) r = args.size > 1 ? seq(*args) : args[0]; rep(r,0) end def self.plus(*args) r = args.size > 1 ? seq(*args) : args[0]; rep(r,1) end def self.ques(*args) r = args.size > 1 ? seq(*args) : args[0]; rep(r,0,1) end def self.sr(rn,bindname=nil,argl=[]) bindname = rn if bindname == nil # but not false [:apat,[:seq,[ [:match_push], [:skimC,rn,argl], [:match_pop,bindname] ]]] end def self.sr?(rn,argl=[]) sr(rn,false,argl) end def self.sr!(rn,argl=[]) fail "sr! unimplemented" end def self.sym(x) qx = x.class == Regexp ? "#{x}" : x.gsub(/(\W)/){|w|"\\#{w}"} qx += '\b' if qx =~ /\w\z/ seq(ques(sr?(:ws)),ncap([/#{qx}/],:symbol),ques(sr?(:ws))) end def self.ncap(spec,bindname) [:apat,[:seq,[ [:match_push], [:match_open,nil,:fake_symbol_rule], [:seq,seq(spec)], [:match_close], [:match_pop,bindname] ]]] end def self.defineA(name,f) vname = name.to_s.gsub(/\?/,'Q').gsub(/\!/,'B') eval "def #{name}; end" # essential magic (else action-at-a-distance bugs) eval %{ define_method(name) { @#{vname} ||= Rule.new(name,f) } },nil,"line #{__LINE__}, '#{name}'" # define_method(name) { Rule.new(name,f) } end def self.defineB(name,r) vname = name.to_s.gsub(/\?/,'Q').gsub(/\!/,'B') eval "def #{name}; end" # precautionary magic eval %{ define_method(name) { @#{vname} ||= r } },nil,"line #{__LINE__}, '#{name}'" end def self.named(name,*spec) # print "#{name} => ",seq(*spec).inspect,"\n\n" defineA(name,seq(*spec)) end def self.term(name,len,*spec) defineB("tokens__#{name}",Token_Operand.new(name,seq(*spec),len)) end def self.nterm(name,len,*spec) named(name,*spec) term(name,len,*spec) end def self.def_operator(fix,oppat,opdef) mangled = mangle(oppat.gsub(/\s+/,'_')) defineB("tokens__#{fix}_#{mangled}",opdef) end def self.option(n1,n2,*spec) named("#{n1}__#{n2}",*spec) end end class SixGrammar < Grammar def method_missing(method, *args) print "FAKING #{method}\n" Rule.new(method,[:apat,/(?!)/]) end def method(method,*args) return super if self.class.method_defined?(method) print "FAKING #{method}\n" proc{Rule.new(method,[:apat,/(?!)/])} end def self.trait_name(trait) @trait_name_cache ||= {} @trait_name_cache[trait] ||= seq('is',trait,:qualified_identifier) end def self.bare_trait(trait) @bare_trait_cache ||= {} @bare_trait_cache[trait] ||= alt(trait_name(trait), seq(trait,:qualified_identifier, star(:ws,'of',:qualified_identifier))) end def self.comma_list(r) seq(r,star(',',r)) end def self.reassembled_delimited_identifier(delim) seq(/\w+/,star(delim,/\w+/)) end def self.maybeParens(*spec) r = seq(*spec) alt(seq('(',r,')'),r) end def self.verbatimBlah(o,r,c) seq(/#{"\\"+o}/,r,:ws?,/#{"\\"+c}/) end def self.verbatimBraces(r) verbatimBlah('{',r,'}') end def self.verbatimBrackets(r) verbatimBlah('[',r,']') end def self.verbatimParens(r) verbatimBlah('(',r,')') end # def ws; @ws ||= Rule.new('ws',SixGrammar.plus(SixGrammar.alt(*(methods.grep(/^ws__/).map{|m| self.class.sr(m)})))) end # option :ws,:simple,/\s+/ # option :ws,:comment,/\#[^\n]*/ # option :ws,:doc_block,/^=\w[^\n]*(?:\n(?!=cut)(?>[^\n]*))*/,alt(/\z/,seq(/\n=cut/,:whiteSpaceLine)) # def ws!; ws end # named :ws?,ques(:ws) _paren = /\(\)/ _paren = /(?:[^()]|\(#{_paren}\))*/ _paren = /(?:[^()]|\(#{_paren}\))*/ _paren = /(?:[^()]|\(#{_paren}\))*/ ws_re = /(?:(?>\s+)|(?>(?\#[^\n]*)|^=\w(?>[^\n]*)(?>(?:\n(?!=cut)(?>[^\n]*))*)(?:\z|\n=cut[\ \t\r]*(?:\n|\z)))+/ named :ws, ws_re named :ws!, ws_re named :ws?, /#{ws_re}?/ named :before_ws, /(?=#{ws_re})/ def tokens; @tokens ||= methods.grep(/^tokens__/).map{|m|method(m).call} end def expr; @expr ||= Rule.new('expr',[:apat,[:seq,[ [:match_push], [:skimCo,OperatorPrecedenceParser.new(tokens,ws)], [:match_pop,:opp] ]]]) end def expr_opp_bare @expr_opp_bare ||= OperatorPrecedenceParser.new(tokens,ws) end named :expr_above_comma, sr(:expr_opp_bare,nil,[->(t){ if !t.is_operator? then true else t.precedence > $level__comma.precedence end}]) #=========================================================================== #-- Whitespace ------------------------------------------------------------- named :whiteSpaceLine,/(?:(?!\n)\s)*$/ # def self.colon_comma_list(r) seq(r,star(/,:/,r)) end # named :argument_list,colon_comma_list(:expr) named :argument_list,:expr optional_argument_list = ques(:argument_list) optional_expr_above_comma = ques(:expr_above_comma) acx = optional_argument_list ac1 = seq(:expr_above_comma) acq = optional_expr_above_comma ac0 = seq() term :apply_sub_notFolded,(:sub_name_bare),:sub_name_bare,/(?!\()/, [[:proc_full, ->(*args){ rest = args[2] = args[2].dup m = args[4] n = m.hash[:sub_name_bare] || fail("assert") n = n.as_s.to_sym # Completely kludge arity, which has become critical. :/ #ac = {:f1=>1,:fq=>:q,:f0=>0}[n] ac = {:ref=>1,:pi=>0,:shift=>1,:pop=>1,:try=>1}[n] case ac when nil; rest.push(*acx) when :s; rest.push(*acx) when 1; rest.push(*ac1) when :q; rest.push(*acq) when 0; rest.push(*ac0) else fail("assert") end args }]] term :sub_name_term,(nil), :sub_name_bare,/(?=\()/ named :method_args,ques(alt(seq(/:/,:argument_list), seq(/\(/,optional_argument_list,:ws?,/\)/))) named :implicit_invocant,/(?=\.)/ term :apply_method_term,(nil),:implicit_invocant,/\./,:qualified_identifier,:method_args #-- Declarations ------------------------------------------------ named :scope, alt('state','my','our','let','temp','env') named :type,/(?:\w|::)[\w:&|?]*/ # related to :type_var and/or :type_literal? named :trait, alt('is','does'),ques(/::/),:qualified_identifier,ques(/\([^\)]*\)/) named :block,:ws?,/\{/,:statement_list,:ws?,/\}/ # named :standalone_block,:block,:whiteSpaceLine # term :code_block,(/\{/),:block term :code_block,(/\{/),:block,ques(:whiteSpaceLine) # named :sub_declaration, seq(ques(:scope,ques(:identifier)),:sub_head, ques('handles',:expr), ques(bare_trait('returns')), ques(:sub_parameters__ParensMandatory), ques(bare_trait('returns')), star(:trait), :block) named :sub_head,alt(seq(ques('multi'),alt('sub','coro','submethod','method','macro')),'multi'),:sub_name_no_amp named :sub_parameters__ParensMandatory,'(',ques(comma_list(:formal_param)),')' named :sub_parameters__ParensOptional,ques(comma_list(:formal_param)) named :param_name, alt(seq(/\&/,:sub_name_no_amp),seq(alt(/[\$\@\%]/,/::/),:twigil_opt,/\w+/)) named :formal_param, seq(ques(:type,:ws),ques(/\\/), ques(:param_foretaste), :param_name, ques(:param_optness), star(:trait), ques(:param_default), ques('-->',:param_list__ParensOptional,alt(:formal_param,:type))) named :param_default, '=',:expr_above_comma named :param_foretaste, /\:|\*/ named :param_optness, /\?|\!/ # named :trusts_declaration, 'trusts', :qualified_identifier named :trait_declaration, :trait, :ws,/\z|(?=[;}])/ named :member_declaration, seq('has',ques(:qualified_identifier),:var_name, star(:trait),ques('handles',:expr)),ques('=',:expr) # named :rule_declaration,'rule',:identifier,:adverb_hash,sr(:balanced,nil,['{']) # rx_literal($,'{') # named :var_declaration, seq(:scope,ques(:qualified_identifier), alt(:var_name, seq(/\(/,comma_list(alt(:var_name,:undef_literal)),/\)/)), star(:trait), ques(alt('=','.=',':=','::='), :ws?, alt(:var_declaration,:expr))) # named :package_block_declaration, :package_head,:block named :package_declaration, :package_head named :package_head, ques(:scope),alt('package','module','class','role','grammar'),:qualified_identifier,ques(:version_part,ques(:author_part)),:ws,star(:trait) # named :no_declaration, 'no',alt(:no_version,:use_package) named :use_declaration, 'use',alt(:use_version,:use_package) named :perl_version, /(?:v|Perl-)[\d\.]+/,ques(:author_part) named :use_version, :perl_version named :no_version, :perl_version named :use_package, alt(seq(/jsan:(?!:)/,:use_JSAN_module), seq(/jsperl5:(?!:)/,:use_JSPerl5_module), seq(ques(:identifier,/:(?!:)/),:use_perl_package)) named :use_perl_package, :package_full_name,ques(alt(seq(/\(/,:ws,/\)/), :expr)) named :use_JSAN_module, seq(alt(:package_full_name, reassembled_delimited_identifier(".")), alt(seq(/\(/,:ws,/\)/), ques(:expr))) named :use_JSPerl5_module,seq(alt(:package_full_name, reassembled_delimited_identifier("::")), alt(seq(/\(/,:ws,/\)/), ques(:expr))) named :package_full_name, reassembled_delimited_identifier("::"),ques(:version_part),ques(:author_part) named :version_part, /-[\d\.\(\)]+/ named :author_part, /-[\w\(\)]+/ # named :inline_declaration, 'inline',:expr named :require_declaration, 'require',:package_full_name #-- Expressions / Terms ------------------------------------------------ # terms 42 "eek" $x /abc/ (1+2) a(1) :by(2) .meth listop term :fakestring,(/\'/), /\'(?:[^\'\\]|\\.)*\'/ term :fakierstring,(/\"/), /\"(?:[^\"\\]|\\.)*\"/ term :fakeEND,(/<<'_END'/), /<<'_END' *\n(.+?)\n_END *\n/m # named :dereference, /[\$\@\%\&]/,alt(:dereference,:sigiled_var,verbatimBraces(:expr)) # nterm :angle_bracket_literal,(/<\\]|\\.|>(?!>))*>> |<(?!<)(?:[^>\\]|\\.)*> |\xab(?:[^\xbb\\]|\\.)*\xbb/x named :hash_subscript_qw, :angle_bracket_literal named :hash_subscript_braces, /\{/,ques(:expr),/\}/ named :hash_subscript, alt(:hash_subscript_braces,:hash_subscript_qw) fixity_re = /#{ 'prefix: postfix: infix: circumfix: coerce: self: term: postcircumfix: rule_modifier: trait_verb: trait_auxiliary: scope_declarator: statement_control: infix_postfix_meta_operator: postfix_prefix_meta_operator: prefix_postfix_meta_operator: infix_circumfix_meta_operator:'.split(/\s+/).join("|")}/ named :fixity, fixity_re named :operator_name, :fixity,alt(:identifier,:hash_subscript) named :sub_name_bare, /(?![A-Z])(?!(?:sub|coro|macro)\b)(?!(?:do)\b)/,alt(:operator_name,:qualified_identifier) named :sub_name_no_amp, :twigil_opt,alt(:operator_name,:qualified_identifier) named :sub_name_full, /\&/,:sub_name_no_amp named :identifier, /[a-z_]\w*/i named :qualified_identifier,/[a-z_]\w*(?:\:\:[a-z_]\w*)*/i named :twigil_opt,/[\^*?\.!+;]?/ term :var_symbolic_deref,(/[\$\@\%\&]/), /[\$\@\%\&]/,plus(/::/,alt(/!|\//,seq(:twigil_opt,/\w+/))) nterm :var_sub,(nil), /\&/,:sub_name_no_amp # (nil) else prefix:& wins :/ nterm :var_simple,(nil), /[\$\@\%]/,:twigil_opt,:qualified_identifier term :var_error,(/\$!/), /\$!/ term :var_match_numbered,(/\$\d+/), /\$\d+/ term :var_match_named,(/\$]*>/ term :var_match,(/\$\//), /\$\// named :var_name, alt(:var_sub,:var_simple) # # :apply__True, #-- Folded metaoperators # term :do_block,(/do\b/), 'do',alt(:block, :statement) # nterm :block_formal_pointy,(/->/), /->/,:ws?,:sub_parameters__ParensOptional,star(:trait),:block nterm :block_formal_standard,(/(?:sub|coro|macro)\b/), sym(/sub|coro|macro/),ques(:sub_parameters__ParensMandatory),star(:trait),:block # fraction = /\.[\d_]+/ expo = /[eE][-+]?\d+/ number_re = /0(?:b[0-1]+|o[0-7]+|d[0-9]+|x[0-9a-fA-F]+) |[0-9][0-9_]*#{fraction}?#{expo}? |[-+]?(?:Inf|NaN)\b /x term :number,(number_re), number_re # term :empty_list_literal,(nil), verbatimParens(:ws?) # was /\(/, but that lost to postcircumfix_paren_empty(len 2), # which needs to be 2 to compete with postcircumfix:( ):0(len 1). # term :empty_array_literal,(/\[/), verbatimBrackets(:ws?) # nterm :array_literal,(/\[/), verbatimBrackets(ques(:expr)) # # :pair_literal is now unneeded -- alt(:pair_arrow,:pair_adverb) # :pair_arrow is now an op -- :identifier,'=>',:parseExpWithTightOps #infix:=> nterm :pair_adverb,(nil), /:/,alt(:shortcut_pair,:regular_pair) named :shortcut_pair, :var_name named :regular_pair_name, /\w+/ named :regular_pair, :regular_pair_name,ques(alt(:valueDot,:noValue,:valueExp)) named :valueDot, :ws,'.',ques(:valueExp) named :noValue, :ws named :valueExp, alt(verbatimParens(:expr),:array_literal,:angle_bracket_literal) # named :undef_literal, /undef\b/ # term :yada_literal,(/\.\.\.|\?\?\?|!!!/), alt('...','???','!!!') # #:q_literal, # named :adverb_hash,star(:pair_adverb) named :rx_pattern, seq(:ws?,alt(/\/(?:[^\/\\]|\\.)*\//, /\{(?:[^\}\\]|\\.)*\}/)) # the \b_dot_ is to win against apply sub. term :rx_literal,(/(?:rx|m|rule)\b./), sym(/rx|m|rule/),:adverb_hash,:rx_pattern term :rx_literal_bare,(/\//), /(?=\/)/,:rx_pattern term :subst_literal,(/s\b/), 's',:adverb_hash,:rx_pattern,:q_literal1 # #:nullary_literal, #:bareword_method, # nterm :closure_trait,(/(?:BEGIN|CHECK|INIT|FIRST|END)\b/), sym(/BEGIN|CHECK|INIT|FIRST|END/),:block # also stmt?!? # term :code_quotation,(/q:code/), /q:code/,ques('(:COMPILING)'),:block # term :type_var,(/::/), plus(/::/,alt(seq(/\(/,:expr,/\)/), seq(:twigil_opt,/\w+/))) # tl = /(?!Inf\b|NaN\b)(?!(?:BEGIN|CHECK|INIT|FIRST|END)\b)[A-Z]\w*(?:\:\:\w+)*/ term :type_literal,(tl), tl # #:apply__False, #-- Normal application # #term :parens,(/\(/), /\(/,ques(:expr),:ws?,/\)/ ### #-- Constructs ------------------------------------------------ named :for_construct,'for', maybeParens(:expr),ques(','),:expr named :loop_construct, 'loop',alt(:semi_loop_construct,:post_loop_construct) named :semi_loop_construct, maybeParens(ques(:expr),';',ques(:expr),';',ques(:expr)),:block named :post_loop_construct,:block,sym(/while|unitl/),:expr # named :cond_construct, sym(/if|unless/),:cond_body named :elsif_construct, 'elsif',:cond_body named :else_construct, 'else',:block named :cond_body, :cond_part,:block,ques(alt(:elsif_construct,:else_construct)) named :cond_part, maybeParens(alt(:type_var,:type_literal,:expr)) # named :while_until_construct,sym(/while|until/),:cond_part,:block named :given_construct, 'given',:cond_part,:block named :when_construct, 'when',:cond_part,:block named :default_construct, 'default',:block #-- :expr_statement ---------------------------------------- named :expr_statement,:expr,ques(alt(:post_conditional,:post_loop,:post_iterate)) named :post_conditional, sym(/if|unless/),:expr named :post_loop, sym(/while|until/),:expr named :post_iterate, 'for',:expr #-- Statements ---------------------------------------- named :prog, :statement_list,:ws? named :statement_list,star(seq(:ws?,:statement,star(';'))) def statement; @statement ||= Rule.new('statement',SixGrammar.alt(*(SixGrammar.statement_rules.map{|m| self.method(m).call.raw_patA}))) end def self.statement_rules; @statement_rules end def self.stmt(n) option :statement,n,n @statement_rules ||= [] @statement_rules.push("statement__#{n}".to_sym) end # block_declaration stmt :sub_declaration stmt :closure_trait stmt :rule_declaration stmt :package_block_declaration # declaration stmt :package_declaration stmt :var_declaration stmt :member_declaration stmt :trait_declaration stmt :use_declaration stmt :no_declaration stmt :inline_declaration stmt :require_declaration stmt :trusts_declaration # construct stmt :for_construct stmt :loop_construct stmt :cond_construct stmt :while_until_construct # stmt :standalone_block stmt :given_construct stmt :when_construct stmt :default_construct # expr stmt :expr_statement end $grammar = SixGrammar.new $grammar.tokens__code_block.operand_post = :post_commalike # ###*** Operator declarations $level__last = nil $level__eq = nil $level__dot = nil $level__comma = nil def level(*args) fix,fixity_extra,assoc,ws_policy,commalike=nil level_equiv = nil args.each{|a| a = a.to_s case a when /\A(prefix|postfix|circumfix|infix|postcircumfix|ternary)\Z/; fix = $1 when /\A(left|right|nonchain|chain)\Z/; assoc = $1.to_sym when /\A(list)\Z/; fixity_extra = $1.to_sym when /\A(nows)\Z/; ws_policy = :prohibit when /\A(needws)\Z/; ws_policy = :require when /\A(commalike)\Z/; commalike = true else a.split(/\s+/).each{|op| if fix =~ /\A(circumfix|postcircumfix|ternary)\Z/ then toks = op =~ /__/ ? op.split(/__/) : [op.slice(0..-2),op.slice(-1,1)] else toks = [op] end prec = (level_equiv ? level_equiv.precedence : $level__last ? $level__last.looser : nil) opdef = Token_Operator.new(nil,fix,fixity_extra,toks,prec,assoc,ws_policy,commalike) SixGrammar.def_operator(fix,op,opdef) if not level_equiv then level_equiv = opdef $level__last = opdef $level__eq = opdef if op == '=' $level__dot = opdef if op == '.' $level__comma = opdef if op == ',' end } end } end def define_assign_binops(opstr) prec = $level__eq.precedence opstr.split(/\s+/).each{|subop| op = "#{subop}=" opdef = Token_Operator.new(nil,:infix,nil,[op],prec) SixGrammar.def_operator(:infix,op,opdef) } end def define_method_call op = SixGrammar.seq(/\./,:qualified_identifier,:method_args) prec = $level__dot.precedence opdef = Token_Operator.new(:apply_method.to_s,:postfix,nil,[op],prec) SixGrammar.def_operator(:postfix,'method_call',opdef) end def define_more_crud op = SixGrammar.seq(/\(/,:ws?,/\)/) prec = $level__dot.precedence opdef = Token_Operator.new(:postcircumfix_paren_empty.to_s,:postfix,nil,[op],prec) SixGrammar.def_operator(:postfix,'empty_paren_call',opdef) end # S03 - 7 Apr 2006 # terms 42 "eek" $x /abc/ (1+2) a(1) :by(2) .meth listop # method postfix . .+ .? .* .() .[] .{} .«» .= # autoincrement ++ -- # exponentiation ** # symbolic unary ! + - ~ ? $ @ % & * ** +^ ~^ ?^ \ ^ = # multiplicative * / % x xx +& +< +> ~& ~< ~> # additive + - ~ +| +^ ~| ~^ # junctive and (all) & # junctive or (any) | ^ # named unary rand sleep abs etc. # nonchaining binary but does cmp <=> .. ^.. ..^ ^..^ ff ^ff ff^ ^ff^ fff ^fff etc. # chaining binary != == < <= > >= ~~ !~ eq ne lt le gt ge =:= === # tight and && # tight or || ^^ // # ternary ?? !! # assignment = := ::= += -= **= xx= etc. (and also =>) # list item separator , ¥ # list op (rightward) <== print push any all true not etc. # pipe forward ==> # loose and and # loose or or xor err # expr terminator ; {} as control block, statement modifiers # terms 42 "eek" $x /abc/ (1+2) a(1) :by(2) .meth listop level :circumfix, '(__)' # method postfix . .+ .? .* .() .[] .{} .«» .= # .= level :nows,:infix, '. .+ .? .*', :postcircumfix, '.() .[] .{} .«» .<>', '() [] {} «» <>' # bogus? ; # autoincrement ++ -- level :prefix, '++ --', :postfix, '++ --'; # exponentiation ** level :infix, '**'; # symbolic unary ! + - ~ ? $ @ % & * ** +^ ~^ ?^ \ ^ = level :prefix, '! + - ~ ? $ @ % & * ** +^ ~^ ?^ \ ^ ='; # multiplicative * / % x xx +& +< +> ~& ~< ~> level :infix, :left, '* / % x xx +& +< +> ~& ~< ~>'; # additive + - ~ +| +^ ~| ~^ level :infix, :left, '+ - ~ +| +^ ~| ~^'; # junctive and (all) & level :infix, :left, '&'; # junctive or (any) | ^ level :infix, :left, '| ^'; # named unary rand sleep abs etc. #XXX ------- # nonchaining binary but does cmp <=> .. ^.. ..^ ^..^ ff ^ff ff^ ^ff^ fff ^fff etc. level :infix, :nonchain, 'but does cmp .. ^.. ..^ ^..^ ff ^ff ff^ ^ff^ fff ^fff fff^ ^fff^',:needws,'<=>'; # chaining binary != == < <= > >= ~~ !~ eq ne lt le gt ge =:= === level :infix, :chain, '!= == ~~ !~ eq ne lt le gt ge =:= ===',:needws,'< <= > >='; # tight and && level :infix, '&&'; # tight or || ^^ // level :infix, '|| ^^ //'; # ternary ?? !! level :ternary, '??__!!'; # assignment = := ::= += -= **= xx= etc. (and also =>) level :infix, :right, '= := ::=',:commalike,'=>'; # list item separator , ¥ level :infix, :commalike, ', ¥'; # list op (rightward) <== print push any all true not etc. level :infix, :list, :needws, '<=='; #X? # pipe forward ==> level :infix, '==>'; # loose and and level :infix, 'and'; # loose or or xor err level :infix, 'or xor err'; # expr terminator ; {} as control block, statement modifiers #XXX ------- # end of table define_assign_binops '. * / % x xx +& +< +> ~& ~< ~> + - ~ +| +^ ~| ~^ & | ^ || ^^ //'; define_method_call define_more_crud ###** Past ###*** PastFromParse class PastFromParse @@map = {} def self.mangle(s) s.to_s.gsub(/z/,'zz').gsub(/([^a-z0-9])/i){|w|sprintf("z%0Xz",w[0])} end def self.node(name,p) @@map[mangle(name).to_sym] = p end node :rx_literal_bare,->(m){ pat = m.hash[:rx_pattern].as_s mods = {} Past::Rx.new(m,mods,pat) } node :rx_literal,->(m){ pat = m.hash[:rx_pattern].as_s mods = pair_list_to_hash(emit(m.hash[:adverb_hash])) Past::Rx.new(m,mods,pat) } node :adverb_hash,->(m){ a = m.hash[:pair_adverb] return [] if not a a = [a] if not a === Array a.map{|p|emit(p)} } def self.pair_list_to_hash(pl) h = {} pl.each{|p| if p.is_a?(Past::PPair) h[p.name] = p.value elsif p.is_a?(Past::Infix) && p.fun.as_s == '=>' h[p.fargs[0]] = p.fargs[1] else fail("assert - pair_list_to_hash not given pair list") end } h end node :pair_adverb,->(m){ rph = m.hash[:regular_pair].hash n = rph[:regular_pair_name].as_s v = ((rph[:valueExp] && (rph[:valueExp].hash[:expr] || rph[:valueExp].hash[:array_literal] || rph[:valueExp].hash[:angle_bracket_literal]) ) || nil) v = emit(v) if v if n =~ /^\d+\z/ base = n.to_i num = case base when 16; eval(v.src.as_s).hex when 8; eval(v.src.as_s).oct else fail(":#{n}() unimplemented") end Past::Number.new(m,num) else Past::PPair.new(m,n,v) end } node :array_literal,->(m){ l = m.hash[:expr] ? unpack_comma_list(m.hash[:expr].hash[:opp]) : [] Past::ArrayLiteral.new(m,l) } node :type_literal,->(m){ Past::TypeLiteral.new(m) # Past::String.new(m,m.as_s) } node :package_block_declaration,->(m){ kind = m.hash[:package_head].hash[:symbol].as_s name = m.hash[:package_head].hash[:qualified_identifier].as_s body = emit(m.hash[:block]) case kind when 'role'; Past::PRole.new(m,name,body) when 'class'; Past::PClass.new(m,name,body) when 'module'; Past::PModule.new(m,name,body) when 'package'; Past::PPackage.new(m,name,body) else fail("assert") end } node :member_declaration,->(m){ var = emit(m.hash[:var_name]) default = emitopt(m.hash[:expr]) Past::MemberDeclaration.new(m,var,default) } node :var_error,->(m){ Past::VarError.new(m) } node :closure_trait,->(m){ Past::ClosureTrait.new(m,m.hash[:symbol].as_s,emit(m.hash[:block])) } node :do_block,->(m){ b = emit(m.hash[:block]) Past::DoBlock.new(m,b) } # node :standalone_block,->(m){ # b = emit(m.hash[:block]) # b.is_a?(Past::HashLiteral) ? b : Past::StandaloneBlock.new(m,b) # } node :code_block,->(m){ b = emit(m.hash[:block]) if b.is_a?(Past::HashLiteral) b elsif m.hash[:whiteSpaceLine] Past::StandaloneBlock.new(m,b) else Past::CodeBlock.new(m,b) end } node :block,->(m){ begin e = m.hash[:statement_list].hash[:statement].hash[:expr_statement].hash[:expr].hash[:opp] h = pair_list_to_hash(unpack_comma_list(e)) Past::HashLiteral.new(m,h) rescue Past::Block.new(m,emit(m.hash[:statement_list])) end } node :use_declaration,->(m){ emit(m.hash[:use_version] || m.hash[:use_package]) } node :use_version,->(m){ Past::Comment.new(m,m.as_s) } node :use_package,->(m){ Past::UsePackage.new(m,m.as_s) } node :undef_literal,->(m){ Past::UndefLiteral.new(m)} node :empty_list_literal,->(m){ Past::ListLiteral.new(m,[]) } node :prog,->(m){ emit(m.hash[:statement_list])} node :expr,->(m){ if m.hash[:opp].rule.name == :sub_name_bare f = emit(m.hash.values[0]) cap = make_capture(nil) Past::Apply.new(m,f,cap) else emit(m.hash.values[0]) end } node :statement_list,->(m){ lst = m.hash[:statement] || [] lst = [lst] if !lst.kind_of?(Array) Past::StatementList.new(m,lst.map{|x|emit(x)}) } node :implicit_invocant,->(m){m.str = '$_'; Past::Variable.new(m)} node :apply_method_term,->(m){ o = emit(m.hash[:implicit_invocant]) f = emit(m.hash[:qualified_identifier]) cap = emit(m.hash[:method_args]) Past::ApplyMethod.new(m,o,f,cap) } node :apply_method,->(m){ o = emit(m.hash[:parts][0]) f = emit(m.hash[:parts][1].hash[:qualified_identifier]) cap = emit(m.hash[:parts][1].hash[:method_args]) Past::ApplyMethod.new(m,o,f,cap) } node :method_args,->(m){ p = m.hash[:argument_list] make_capture(p) } node :apply_sub_notFolded,->(m){ f = emit(m.hash[:sub_name_bare]) p = (m.hash[:argument_list] || (m.hash[:expr_above_comma] && m.hash[:expr_above_comma].hash[:expr_opp_bare])) cap = make_capture(p) Past::Apply.new(m,f,cap) } node :parseParenParamList,->(m){ emitopt(m.hash.values[0]) } node :parseHasParenParamList,->(m){ emit(m.hash.values[0]) } # node :parens,->(m){ m.hash[:expr] ? emit(m.hash[:expr]) : Past::ListLiteral.new(m,[]) } # node :var_name,->(m){ emit(m.hash.values[0]) } node :var_simple,->(m){ Past::Variable.new(m) } node :var_declaration,->(m){ vs = m.hash[:var_name] vs = [vs] if !vs.is_a?(Array) vars = vs.map{|v|emit(v)} vals = [] if m.hash[:expr] vals = unpack_comma_list(m.hash[:expr].hash[:opp]) elsif m.hash[:var_declaration] vals = [emit(m.hash[:var_declaration])] end sym = m.hash[:symbol] sym = sym[-1] if sym.is_a?(Array) sym = sym.as_s if sym sym = nil if sym == ',' op = sym Past::VarDecl.new(m,m.hash[:scope].hash[:symbol].as_s,nil,vars, emitopt(m.hash[:trait]), op, vals) } node :for_construct,->(m){ im = m.hash[:expr][0] l = unpack_comma_list(im.hash[:opp]) iter = l.size == 1 ? l[0] : Past::ArrayLiteral.new(im,l) body = emit(m.hash[:expr][1]) Past::For.new(m,iter,body) } def self.get_firstsense(m) sym = m.hash[:symbol].as_s firstsense = case sym when 'if'; true; when 'unless'; false; else fail("assert") end end node :post_conditional,->(m){ firstsense = get_firstsense(m) expr = emit(m.hash[:expr]) [firstsense,expr] } node :cond_construct,->(m){ firstsense = get_firstsense(m) body = emit(m.hash[:cond_body]) Past::Cond.new(m,firstsense,body) } node :cond_body,->(m){ this = [emit(m.hash[:cond_part]), emit(m.hash[:block])] rest = emitopt(m.hash[:elsif_construct] || m.hash[:else_construct]) [this] + (rest ? rest : []) } node :elsif_construct,->(m){ emit(m.hash[:cond_body]) } node :else_construct,->(m){ [[emit(m.hash[:block])]] } node :cond_part,->(m){ emit(m.hash[:expr]) } def self.create_sig(m,kind) param = [] tmp = (m.hash[:sub_parameters__ParensMandatory] || m.hash[:sub_parameters__ParensOptional]) if tmp param = tmp.hash[:formal_param] param = [] if not param param = [param] if param.class != Array param = param.map{|p|emit(p)} else t = nil slurpy = true n = Past::Variable.new('@_') optness = nil d = nil param = [Past::Parameter.new(nil,t,false,slurpy,false,n,optness,nil,d)] end Past::Signature.new(m,param,nil,kind) end node :sub_declaration,->(m){ scope = m.hash[:scope] ? m.hash[:scope].hash[:symbol].as_s : nil kind = m.hash[:sub_head].hash[:symbol].as_s name = emit(m.hash[:sub_head].hash[:sub_name_no_amp]) body = emit(m.hash[:block]) sig = create_sig(m,kind) Past::SubDecl.new(m,scope,kind,name,sig,body) } node :block_formal_pointy,->(m){ body = emit(m.hash[:block]) sig = create_sig(m) Past::SubDecl.new(m,nil,'sub',nil,sig,body) } node :block_formal_standard,->(m){ body = emit(m.hash[:block]) sig = create_sig(m) Past::SubDecl.new(m,nil,'sub',nil,sig,body) } node :formal_param,->(m){ t = emitopt(m.hash[:type]) n = emit(m.hash[:param_name]) d = emitopt(m.hash[:param_default]) optness = emitopt(m.hash[:param_optness]) foretaste = emitopt(m.hash[:param_foretaste]) slurpy = foretaste =~ /\*/ Past::Parameter.new(m,t,false,slurpy,false,n,optness,nil,d) } node :type,->(m){ m.as_s } node :param_name,->(m){ Past::Variable.new(m) } node :param_default,->(m){ emit(m.hash[:expr_above_comma].hash[:expr_opp_bare]) } node :param_foretaste,->(m){m.as_s} node :param_optness,->(m){m.as_s} node :sub_name_term,->(m){ emit(m.hash[:sub_name_bare]) } node :sub_name_bare,->(m){ Past::Variable.new(m) # emit(m.hash[:qualified_identifier]) } node :sub_name_no_amp,->(m){ emit(m.hash[:qualified_identifier]) } node :var_sub,->(m){ Past::Variable.new(m) # Past::VarSub.new(m,m.hash[:sub_name_no_amp].as_s) } node :qualified_identifier,->(m){ Past::Identifier.new(m) } # node :bare_identifier,->(m){ emit(m.hash[:qualified_identifier]) } node :sub_parameters__ParensOptional,->(m){ Past::Signature.new(tmp,param,nil,nil) } node :number,->(m){ Past::Number.new(m,m.as_s) } node :fakestring,->(m){ md = m.as_s.match(/\A'(.*)'\z/m) or fail "bug\n#{m.as_s.inspect}" s = md[1] s.gsub!(/\\(.)/){|c|c} Past::String.new(m,s) } node :fakeEND,->(m){ m.as_s =~ /^<<'_END' *\n(.+?)\n_END *\n\z/m or fail('assert') Past::String.new(m,$1) } node :fakierstring,->(m){ md = m.as_s.match(/\A"(.*)"\z/m) or fail "bug #{m.inspect}" s = md[1] return Past::String.new(m,'') if s == '' s.gsub!(/\\(.)/){|c|c} ss = StringScanner.new(s) subs = [] s0 = nil while not ss.eos? str = ss.scan(/(?:[^\\\$]|\\.)+/) if str str.gsub!(/\\n/,"\n") str.gsub!(/\\t/,"\t") s0 = str subs.push('"'+str+'"') next end code = ss.scan(/\$\w+/) if code subs.push(code) next end fail "assert" end if subs.size == 1 and s0 then Past::String.new(m,s0) else subs.push('""') if subs.size == 1 emit($grammar.expr.search(subs.join(" ~ "))) # offset end } node :angle_bracket_literal,->(m){ s = m.as_s q = s.match(/^(?:<(m){ emit(m.hash.values[0]) } node :word,->(m){ Past::Word.new(m) } node :token,->(m){ emit(m.hash.values[0]) } node :statement,->(m){ emit(m.hash.values[0]) } # node :statement__expr_statement,->(m){ emit(m.hash.values[0]) } node :expr_statement,->(m){ expr = emit(m.hash[:expr]) post = emitopt(m.hash[:post_conditional] || m.hash[:post_loop] || m.hash[:post_iterate]) if post firstsense,post_expr = *post Past::Cond.new(m,firstsense,[[post_expr,expr]]) else expr end } node :variable,->(m){ Past::Variable.new(m) } node 'infix:,',->(m){ l = unpack_comma_list(m) Past::ListLiteral.new(m,l) } node 'infix:+',->(m){ Past::Infix.new(m,m.hash[:parts][1],[0,2].map{|i|emit(m.hash[:parts][i])})} node 'prefix:+',->(m){ Past::Prefix.new(m,m.hash[:parts][0],[1].map{|i|emit(m.hash[:parts][i])})} node 'postfix:+',->(m){ Past::Postfix.new(m,m.hash[:parts][1],[0].map{|i|emit(m.hash[:parts][i])})} node 'postcircumfix:+',->(m){ Past::Postcircumfix.new(m,"#{m.hash[:parts][1].as_s} #{m.hash[:parts][3].as_s}",[0,2].map{|i|emit(m.hash[:parts][i])})} node 'postcircumfix:( )',->(m){ f = emit(m.hash[:parts][0]) p = m.hash[:parts][2] cap = make_capture(p) Past::Apply.new(m,f,cap) } node :postcircumfix_paren_empty,->(m){ f = emit(m.hash[:parts][0]) if m.hash[:parts][0].rule.name.to_sym == :apply_sub_notFolded f else cap = make_capture(nil) Past::Apply.new(m,f,cap) end } node 'ternary:?? !!',->(m){ t = emit(m.hash[:parts][0]) c = emit(m.hash[:parts][2]) a = emit(m.hash[:parts][4]) Past::Cond.new(m,true,[[t,c],[a]]) } node 'circumfix:( )',->(m){ e = emit(m.hash[:parts][1]) if e.is_a?(Past::PPair) || (e.is_a?(Past::Infix) && e.fun.as_s == '=>') then Past::WrappedPPair.new(m,e) else e end } def self.emitopt(m) return nil if !m emit(m) end def self.emit(m) fail("boom\n#{caller.join("\n")}") if !m fail("thud\n#{caller.join("\n")}") if !m.bool name = m.rule.name.to_sym mname = mangle(name).to_sym mname2 = mname mname2 = mangle('infix:+').to_sym if name.to_s =~ /^infix:/ mname2 = mangle('prefix:+').to_sym if name.to_s =~ /^prefix:/ mname2 = mangle('postfix:+').to_sym if name.to_s =~ /^postfix:/ mname2 = mangle('postcircumfix:+').to_sym if name.to_s =~ /^postcircumfix:/ hand = @@map[mname] || @@map[mname2] if hand hand.call(m) else warn("#{self} FAILED: no handler for rules named '#{name}'") Past::SimpleString.new('THIS IS INVALID') end end def self.unpack_comma_list(m) name = m.rule.name.to_s if name == 'infix:,' unpack_comma_list(m.hash[:parts][0]) + [emit(m.hash[:parts][2])] else [emit(m)] end end def self.make_capture(m0) return Past::Capture.new(m0,[]) if not m0 m1 = m0 if m1.hash[:expr] m1 = m1.hash[:expr].hash[:opp] end return Past::Capture.new(m0,[]) if m1.rule.name.to_sym == :empty_list_literal #if m1.rule.name.to_sym == :parens # m1 = m1.hash[:expr] # m1 = m1.hash[:opp] if m1 #end a = m1 ? unpack_comma_list(m1) : [] Past::Capture.new(m0,a) end end ###*** PastNodes class Object; def as_s; to_s end end module Past class PastObject attr_accessor :src attr_accessor :whiteboard def initialize(src) @src=src; @whiteboard={} end def inspect; src=@src;@src=@src.as_s;ret=super;@src=src;ret end def gensym; "v#{sprintf("%x",rand(1000000000))}" end def indent(s) s ? s.gsub(/(?m)^(?!\Z)/,' ') : '*nil*' end def indent_except_top(s) s ? s.gsub(/(?m)^(?!\Z)/o,' ').sub(/^ /,'') : '*nil*' end def down(m, *args, &block) if respond_to?(m) send(m,*args, &block) else down_not_self(m,*args,&block) end end def down_not_self(m, *args, &block) ms = m.to_s ms =~ /^(all|any|walk)_/ || fail("assert: #{ms}") any = $1 == 'any' all = $1 == 'all' ret = [] instance_variables.each{|iv| n = iv.sub(/^@/,'') next if !self.respond_to?(n) node = self.send(n) nodes = nil if node.is_a?(PastObject) nodes = [node] elsif node.is_a?(Array) nodes = node.find_all{|e| e.is_a?(PastObject)} else next end nodes.each{|nd| res = nd.down(m,*args,&block) return res if any && res ret = ret + res if all } } ret end def walk_past_init(h=nil) h ||= {} down_not_self(:walk_past_init,h) end end def self.frob(s) s.split(/\s*\n/).each{|l| next if l =~ /^#/ or l =~ /^\s*$/ l.gsub!(/^\s*/,''); cl,_fs=l.split(/\s+/); fs = _fs.split(/,\s*/); fs.shift if fs[0] == '-' as = ['src']+fs varcode = fs.empty? ? '' : <<-END attr_accessor #{fs.map{|f|":#{f}"}.join(',')} def initialize(#{as.join(',')}) super(src) #{fs.map{|f|"@#{f}"}.join(',')} = #{fs.join(',')} end END code = <<-END class #{cl} < PastObject #{"\n"+varcode} end END begin eval code; rescue Exception; fail "#{$!}:\n#{code}" end } end frob(%q{ Rx modifiers,pattern PPair name,value ArrayLiteral array HashLiteral thehash PRole name,body PClass name,body PModule name,body PPackage name,body UsePackage pkg TypeLiteral - MemberDeclaration var,default VarError - ClosureTrait flavor,block DoBlock block StandaloneBlock block CodeBlock block Comment comment UndefLiteral - ListLiteral list Block statementlist StatementList statements ApplyMethod obj,f,arggen Apply f,arggen Capture argl Identifier - Number num SimpleString - String str Quote quote,str Variable - VarSub name VarDecl scope,mumble,vars,trait,op,vals Cond sense_of_first_test,body For enu,body Word - SubDecl scope,kind,name,sig,body Signature params,returns,kind Arguments theargs Infix fun,fargs Prefix fun,fargs Postfix fun,fargs Postcircumfix fun,fargs WrappedPPair pair }) # Parameter end ###**** Past methods module Past class VarDecl def my?; scope == 'my' end def our?; scope == 'our' end def state?; scope == 'state' end def let?; scope == 'let' end def temp?; scope == 'temp' end def env?; scope == 'env' end def all_toplevel_vardecls; [self] end end module InitDecls def init_decls(h) decls = (h[:decls] || {}).dup down(:all_toplevel_vardecls).each{|vd| vars = vd.vars vars = [vars] if !vars.is_a?(Array) vars.each{|v| decls[v.perl] = vd } } h[:decls] = decls end end class StatementList include InitDecls def walk_past_init(h=nil) h ||= {} init_decls(h) if !h[:decls] down_not_self(:walk_past_init,h) end end class Block def all_toplevel_vardecls; [] end def block_toplevel_vardecls @statementlist.down_not_self(:all_toplevel_vardecls) end include InitDecls def walk_past_init(h) old_blk = h[:current_block]; h[:current_block] = self old_decls = h[:decls]; init_decls(h) block_toplevel_vardecls.each{|vd| vars = vd.vars vars = [vars] if !vars.is_a?(Array) vars.each{|v| h[:decls][v.perl] = vd } } @statementlist.down(:walk_past_init,h) h[:decls] = old_decls h[:current_block] = old_blk end end class Variable SIGIL_RE = /[$@%&]|::|@@/ TWIGIL_RE = /[\.^*+?=!<]/ # $ VARIABLE_RE = /^(?:(#{SIGIL_RE})(#{TWIGIL_RE})?)?(\w+(?:::\w+)*)$/ attr_accessor :type,:sigil_actual,:twigil,:path def initialize(src,type=nil) super(src) @type = type initialize_from_string(@src.as_s) end def initialize_from_string(code) m = code.match(VARIABLE_RE) || fail("invalid variable: #{code}") @sigil_actual,@twigil,name = m[1..3] @path = name.split(/::/) end def sigil; @sigil_actual || '&' end def perl t = type ? type.perl+' ' : '' close = twigil == '<' ? '>' : '' "#{t}#{sigil_actual||''}#{twigil||''}#{path.join('::')}#{close}" end def pathname; path.join('::') end def leafname; path[-1] end def has_path; path.size > 1 end def scalar?; sigil == '$' end def array?; sigil == '@' end def hash?; sigil == '%' end def exe?; sigil == '&' end def type?; sigil == '::' end def multislice?; sigil == '@@' end def accessor?; twigil == '.' end # object attribute accessor def self_declared_parameter?; twigil == '^' end def global?; twigil == '*' end def environmental?; twigil == '+' end def compiler?; twigil == '?' end def pod?; twigil == '=' end def match_capture?; twigil == '<' end def private?; twigil == '!' end # aka explicitly_private? def walk_past_init(h) @whiteboard[:declaration] = h[:decls][self.perl] #|| fail("assert") down_not_self(:walk_past_init,h) end def declaration; @whiteboard[:declaration] end end class Parameter < PastObject attr_accessor :is_invocant,:is_slurpy,:is_name_only, :variable,:explicit_optness,:traits,:default attr_accessor :opt def initialize(src,*args) super(src) raise ArgumentError if args.size != 8 type,@is_invocant,@is_slurpy,@is_name_only, variable,@explicit_optness,@traits,@default=*args @variable = variable #Variable.new(variable,type) @variable.type = type @opt = (@explicit_optness == '?' || (@explicit_optness != '!' && @default)) end def optional?; @opt end def required?; !@opt end def slurpy?; @is_slurpy end def positional?; !name_only? end def name_only?; @is_name_only end def invocant?; @is_invocant end def perl s = slurpy? ? '*' : '' n = name_only? ? ':' : '' o_concise = '' o_concise = '?' if optional? && !default o_concise = '!' if required? && default o_explicit = @explicit_optness o = o_explicit t = traits ? " "+traits.perl : '' d = default ? " = "+default.perl : '' "#{s}#{n}#{variable.perl}#{o}#{t}#{d}" end def slurpy_scalar?; slurpy? && scalar? end def slurpy_array?; slurpy? && array? end def slurpy_hash?; slurpy? && hash? end alias :method_missing_alias_Parameter :method_missing def method_missing(m,*args,&block) if @variable.respond_to?(m) then @variable.send(m,*args) else method_missing_alias_Parameter(m,*args,&block) end end end class Signature alias :initialize_Signature :initialize def initialize(*a) initialize_Signature(*a) add_implicit_self if kind == 'method' && invocants.empty? end def add_implicit_self implicit = Past::Parameter.new(nil,nil, true,false,false, Past::Variable.new('$self'), false,nil,nil) @params = [implicit] + (@params || []) end def perl r = returns ? ' --> '+returns.perl : '' ip = invocants.map{|p|p.perl}.join(', ') op = noninvocants.map{|p|p.perl}.join(', ') ip += ':' if ip "(#{ip}#{op}#{r})" end def invocants; ; params.select{|p| p.invocant?} end def noninvocants;params.select{|p| !p.invocant?} end def positionals; params.select{|p| p.positional?} end def name_onlys; params.select{|p| p.name_only?} end def slurpys; params.select{|p| p.slurpy?} end def types; params.select{|p| p.type?} end def exes; params.select{|p| p.exe?} end def slurpy_hash; params.select{|p| p.slurpy_hash?}[0] end def slurpy_positionals; params.select{|p| p.slurpy? && (p.scalar? || p.array?)} end def slurpy_scalar; params.select{|p| p.slurpy_scalar?}[0] end def slurpy_array; params.select{|p| p.slurpy_array?}[0] end # def named_names; nameds.map{|p|p.variable.leafname} end def positional_arity_min; positionals.select{|p| !p.optional?}.size end def positional_arity_max; slurpy_array ? Inf : positionals.size end # def nameds_required; nameds.select{|p| p.required?} end end end ###*** PastToRb module Past class PastObject def rb_type_mangle(tn) tn =~ /^Rb::/ ? tn.slice(4..-1) : ''+tn end end class Rx def emit_rb; pat = @pattern.dup if pat =~ /^{/ pat = pat.gsub(/^./,'').gsub(/.$/,'').gsub(/\//,'\\/') pat = "/#{pat}/" end "#{pat}x" end end class PPair def emit_rb; v = @value ? @value.emit_rb : "1" "Pair.new(#{@name.inspect},#{v})" end end class WrappedPPair def emit_rb; "WrappedPair.new(#{pair.emit_rb})" end end class ArrayLiteral def emit_rb; '['+@array.map{|e|e.emit_rb}.join(', ')+'].delisty' end end class HashLiteral def emit_rb; '{'+@thehash.map{|k,v|"#{k.emit_rb}=>#{v.emit_rb}"}.join(', ')+'}' end end class PClass def emit_rb n = rb_type_mangle(@name) b = @body.emit_rb "class #{n}\n init_class(self,Module.nesting)\n#{indent(b)}\nend\n" end end class MemberDeclaration def emit_rb vm = "#{var.emit_rb_def}m" code = "attr_accessor :#{vm}" va = "#{var.leafname}C" v = var.emit_rb code += "\ndef #{va}() ->(_S,selfS){ #{v} }end" gs = gensym sigex = var.rb_sigex code += "\nalias :initialize_#{gs} :initialize\ndef initialize(*a) initialize_#{gs}(*a); @#{vm} ||= BindBox#{sigex}.new end" code end end module NotClassHlp def emit_rb n = rb_type_mangle(@name) b = @body.emit_rb "module #{n}\n init_nonclass(self,Module.nesting)\n#{indent(b)}\nend\n" end end class PRole; include NotClassHlp end class PModule; include NotClassHlp end class PPackage; include NotClassHlp end class UsePackage def emit_rb; "useC.call(nil,#{@pkg.inspect})\n" end end class TypeLiteral def emit_rb rb_type_mangle(@src.as_s) end end class VarError def emit_rb; '$scalar_ERROR' end end class ClosureTrait def emit_rb; "#{@flavor} {\n#{indent(@block.emit_rb)}\n}" end end class DoBlock def emit_rb; "proc{\n#{indent(@block.emit_rb)}\n}.call()" end end class StandaloneBlock def emit_rb; "proc{\n#{indent(@block.emit_rb)}\n}.call()" end end class CodeBlock def emit_rb a = @whiteboard[:need_an_arg] ? '_S' : '' a = a == '' ? "_S" : "_E,#{a}" "->(#{a}){\n#{indent(@block.emit_rb)}\n}" end end class Comment def emit_rb; @comment.gsub(/^/,'# ').sub(/\n?\z/,"\n") end end class UndefLiteral def emit_rb; "pundef" end end class ListLiteral def emit_rb; 'Listy['+@list.map{|e|e.emit_rb}.join(",")+']' end end class Block def emit_rb b = @statementlist.emit_rb vs = [] down(:block_toplevel_vardecls).map{|vd| next if !vd.my? vars = vd.vars vars.is_a?(Array) ? vs += vars.map{|v|v.emit_rb} : vs += [vars.emit_rb] } vs = vs.sort.uniq if vs.empty? b else vl = vs.join(',') nl = (['nil'] * vs.size).join(',') "->(#{vl}){\n#{indent(b)}\n}.call(#{nl})" end end end class StatementList def emit_rb; @statements.map{|x|x.emit_rb}.join(";\n") end end class ApplyMethod def emit_rb f = @f.emit_rb if f =~ /^rUBY/ n = f.slice(4..-1) n.sub!(/_P$/,'?') "#{@obj.emit_rb}.#{n}(#{@arggen ? @arggen.emit_rb : ''})" else o = gensym n = "#{f}C" a = "#{@arggen ? @arggen.emit_rb : ''}" a = a == '' ? "#{o},#{o}" : "#{o},#{o},#{a}" "(#{o}=#{@obj.emit_rb}).#{n}.call(#{a})" end end end class Apply def emit_rb f = @f.src.as_s if f =~ /^rUBY/ n = f.slice(4..-1) n.sub!(/_P$/,'?') "#{n}(#{@arggen ? @arggen.emit_rb : ''})" elsif f == 'raw_rUBY' argl = @arggen.argl.dup code = argl.shift.str code = "#{code}.call(#{argl.map{|a|a.emit_rb}.join(',')})" if !argl.empty? code elsif f == 'source_ruby' argl = @arggen.argl.dup code = argl.shift.str code = "source_ruby(<<'_END'\n#{code}\n_END\n);\n" code elsif f == 'source_perl' argl = @arggen.argl.dup code6 = argl.shift.str rest = '' "source_perl(<<'_END'\n#{code6}\n_END\n#{rest});\n" elsif f == 'source_perl_compiled' argl = @arggen.argl.dup perlcode = argl.pop.str sum = chksum(perlcode+"\n") #print "Comp"+"iled: ",perlcode.length," ",sum,"\n" #File.open("deleteme_01","w"){|f|f.write perlcode.split(//).join("\n")} rubycode = $P.compile6(perlcode) coderb = rubycode.gsub(/\\/,'\\\\').gsub(/\'/,'\\ rb bug ').gsub(/ rb bug /,'\'') coderb = "'\n"+coderb+"\n'" rest = "'#{sum}',#{coderb}," "source_perl_compiled(#{rest}<<'_END'\n#{perlcode}\n_END\n);\n" elsif f == 'source_perl_finish' "source_perl_finish()" else n = "#{f}C" a = "#{@arggen ? @arggen.emit_rb : ''}" a = a == '' ? "_S" : "_S,#{a}" "#{@f.emit_rb}.call(#{a})" end end end class Capture def emit_rb; @argl.map{|a|a.emit_rb}.join(",") end end class VarDecl def emit_rb ini = [] @vars.each_with_index{|v,i| bb = case v.sigil when '$'; 'BindBox' when '@'; 'BindBoxa' when '%'; 'BindBoxh' when '&'; 'BindBoxc' else fail("assert") end vi = @vals[i] val1 = vi ? vi.emit_rb : '' val2 = vi ? val1 : 'pundef' r = case @op when nil; "#{bb}.new()" when '='; "#{bb}.new(#{val1})" when ':='; val2 when '::='; val2 else fail("assert #{op}") end ini.push(r) } lhs = if our? @vars.map{|v|v.emit_rb_symtab}.join(',') else @vars.map{|v|v.emit_rb}.join(',') end rhs = ini.empty? ? '' : " = "+ini.join(',') "#{lhs}#{rhs}" end end class Cond def emit_rb s = nil @body.each{|tc| test,cons=*tc if not s sym = @sense_of_first_test ? 'if' : 'unless' s = "#{sym} (#{test.emit_rb}).as_b\n#{indent(cons.emit_rb)}" elsif cons s += "\nelsif (#{test.emit_rb}).as_b\n#{indent(cons.emit_rb)}" else cons = test s += "\nelse\n#{indent(cons.emit_rb)}" end } s += "\nend" s end end class For def emit_rb; arity = 1 arity = @body.sig.positional_arity_min if @body.respond_to?(:sig) if arity == 1 @body.whiteboard[:need_an_arg] = 1 "(#{@enu.emit_rb}).each{|__e__|(#{@body.emit_rb}).call(__e__,__e__)}" else a=gensym;i=gensym;v=gensym; "(#{a} = (#{@enu.emit_rb}); #{i}=0; while #{a}.exists?(#{i}); #{v} = (#{@body.emit_rb}).call(:deadbeef,*(#{a}.slice(#{i},#{arity}))); #{i} += #{arity}; #{v} end)" end end end class Identifier def emit_rb; @src.as_s end end class Number def emit_rb; "#{@num}" end end class SimpleString; def emit_rb; @src.as_s.inspect end end class String def emit_rb; @str.inspect end end class Quote def emit_rb case @quote when '<'; s = @str "Listy[*('#{s}'.split(/\\s+/))]" else fail("assert or unimplemented") end end end class VarSub def emit_rb; "method('#{@name}')" end end class Variable def emit_rb_symtab tab = global? ? "symtab['::GLOBAL'].symtab" : "symtab" "#{tab}['#{sigil+leafname}']" end def rb_sigex {"\$"=>'S','@'=>'A','%'=>'H','&'=>'C'}[sigil] end def emit_rb_def v = @src.as_s decl = declaration if !decl || decl.my? return "_E" if v == '$+_' sigex = rb_sigex ns = pathname+sigex if twigil == '*' return "\$_#{ns}g" end ns else emit_rb_symtab end end def emit_rb c = emit_rb_def case twigil when '.'; "selfS.#{c}m" when '!'; "selfS.#{c}m" else c end end end class Infix def emit_rb a0 = fargs[0].emit_rb a1 = fargs[1].emit_rb fs = fun.as_s case fs when '~'; "(\"\#\{#{a0}}\" + \"\#\{#{a1}}\")" when ','; "#{a0} #{fs} #{a1}" when '.'; a1hack = fargs[1].src.as_s; "#{a0}#{fs}#{a1hack}" when '='; "(#{a0})._(#{a1})" when ':='; "#{a0} = #{a1}" when '::='; "#{a0} = #{a1}" when '~~'; "smartmatch(#{a0},#{a1})" when '=>'; s0 = fargs[0].src.as_s a0 = "'#{s0}'" if s0 =~ /^\w+$/ #ws "Pair.new(#{a0},#{a1})" when 'eq'; "#{a0}.as_s == #{a1}.as_s" when '=:='; "#{a0}.object_id == #{a1}.object_id" when '|'; "#{a0}.as_s + '_BOGUS_JUNCTION_' + #{a1}.as_s" #when '.='; '"UNIMPLEMENTED .= OP"' when '.='; "#{a0}._(#{a0}.#{a1})" when '~='; "#{a0}._(#{a0}.as_s + #{a1}.as_s)" when /^([-+*\/])=$/; "#{a0}._(#{a0} #{$1} #{a1})" else fs = {'eq'=>'==','ne'=>'!='}[fs] || fs "(#{a0} #{fs} #{a1})" end end end class Prefix def emit_rb as = fargs[0].emit_rb fs = fun.as_s case fs when '?'; "#{as}.as_b" when '!'; "!#{as}.as_b" when '+'; "#{as}.as_n" when '~'; "#{as}.as_s" when '%'; "#{as}.as_h0" when '++'; y=gensym;"(#{y}=#{as};#{as}._(#{y}+1);#{y})" when '--'; y=gensym;"(#{y}=#{as};#{as}._(#{y}-1);#{y})" when '\\'; "#{as}" #X else "#{fs}#{as}" end end end class Postfix def emit_rb as = fargs[0].emit_rb fs = fun.as_s case fs when '++'; y=gensym;"(#{y}=#{as};#{as}._(#{y}+1))" when '--'; y=gensym;"(#{y}=#{as};#{as}._(#{y}-1))" else "#{as}#{fs}" end end end class Postcircumfix def emit_rb a0 = fargs[0].emit_rb a1 = fargs[1].emit_rb fs = fun.as_s case fs when '[ ]'; "#{a0}.get(#{a1})" when '{ }'; "#{a0}.get(#{a1})" when '< >'; a1 = fargs[1].src.as_s; "#{a0}.get('#{a1}')" else "'INVALID POSTCIRCUMFIX #{fs}'" end end end class Word def emit_rb; @src.as_s end end class Parameter end class Signature def emit_rb_multi_pattern @params.map{|p| p.type ? p.type.src.as_s : 'Object' }.join(',') end def emit_rb_param_list @params.map{|p| n = p.variable.emit_rb_def n = "*#{n}" if p.slurpy? n }.join(',') end def emit_rb_param_list_with_nils @params.map{|p| n = p.variable.emit_rb_def if p.optional? then "#{n} = nil" else n end }.join(',') end def emit_rb_args_processing code = "" code += "_argl_,_argn_=_args_sep_(_args_);_arglsz_=_argl_.size\n" amin = positional_arity_min amax = positional_arity_max.as_s #Inf code += "fail(\"Incorrect argument count \#{_arglsz_} vs #{amin}..#{amax}\") if _arglsz_ > #{amax}\n" code += @params.size > 0 ? "#{emit_rb_param_list}=*_argl_.delisty\n" : "" @params.each{|p| v = p.variable.emit_rb_def n = p.variable.leafname code += "#{v} = ((x = _argn_.delete('#{n}')) && x[-1]) || #{v}\n" } if slurpy_hash code += "#{slurpy_hash.variable.emit_rb} = _argn_\n" else code += "fail(\"Unexpected named arguments \#{_argn_.keys.join(',')}\") if not _argn_.empty?\n" end @params.map{|p| v = p.variable.emit_rb_def n = p.variable.leafname if p.default code += "#{v} = #{p.default.emit_rb} if #{v} == nil\n" elsif p.optional? code += "#{v} = pundef if #{v} == nil\n" else code += "#{v} == nil && fail(\"Missing argument for required parameter #{n}\")\n" end } code end def arity; @params.size end end class SubDecl def emit_rb k = @kind.gsub(/\s+/,' ') k = "multi sub" if k == "multi" argsprocess = "#{@sig.emit_rb_args_processing}" lam = "->(*_args_){\n ->(_S,_E){\n _E=_args_.shift\n#{indent(argsprocess+@body.emit_rb)}\n}[nil,nil]\n}" lam2 = "->(*_args_){\n ->(_S,_E,selfC){\n _E=_args_.shift\n#{indent(argsprocess)}\n selfC = ->(_S){selfS}\n#{indent(@body.emit_rb)}\n}[nil,nil,nil]\n}" if @scope == 'my' case k when 'sub' fail("assert") if not @name n = "#{@name.emit_rb}C" "#{n} = #{lam}\n" else fail("my @k not implemented") end else case k when 'sub' if @name n = "#{@name.emit_rb}C" "current_class.def_pkg_var(#{n.to_sym.inspect},#{lam})\n" else "#{lam}\n" end when 'method' n = "#{@name.emit_rb}C" "current_class.def_pkg_var(#{n.to_sym.inspect},#{lam2})\n" when 'submethod' n = "#{@name.emit_rb}Csm" "current_class.def_pkg_var(#{n.to_sym.inspect},#{lam})\n" when 'multi sub' n = "#{@name.emit_rb}C" nm = "#{@name.emit_rb}M" spc = @sig.emit_rb_multi_pattern "multi(#{nm.to_sym.inspect},Object,#{spc},#{lam})\ncurrent_class.def_pkg_var(#{n.to_sym.inspect},->(*args){#{nm}(*args)})\n" else fail("#{k} not implemented") end end end end end #class Module; public :define_method end ###** Runtime ###*** Needed by P6 class SymbolTable attr_accessor :hash,:up def initialize(up) @hash,@up = {},up end def [](i) if @hash.member?(i); @hash[i] elsif @up; @up[i] else fail("Undefined #{i}") end end def []=(i,v) @hash[i] = v end end module HasSymbolTableM attr_accessor :pkgname def init_symtab(n,up=nil) uptable = up ? up.symtab : nil @table = SymbolTable.new(uptable) @pkgname = n @up = up end def symtab; @table end def path; (up ? up.path : []) + [n] end def fullname if up '::*::' + path.join('::') else fail('assert') if @n != 'GLOBAL' '::*' end end end class RPackage def self.make_GLOBAL g = self.new('GLOBAL') g.symtab['::GLOBAL'] = g m = self.new('Main',g) g.symtab['::Main'] = m end def initialize(n,up=nil) init_symtab(n,up) end include HasSymbolTableM def thebinding; binding end end class Class include HasSymbolTableM def thebinding; binding end def init_class(cls,nest) cls.name =~ /([^:]+)\z/ init_symtab($1,nest[1]) if !symtab end end class Module include HasSymbolTableM def thebinding; binding end def init_nonclass(cls,nest) cls.name =~ /([^:]+)\z/ init_symtab($1,nest[1]) if !symtab end end ###*** P6 require 'readline' class P6 attr_accessor :pkgspace attr_accessor :verbose def initialize @verbose=true @pkgspace = RPackage.make_GLOBAL end def P6_binding; @pkgspace.thebinding end def P6_binding_Main; @pkgspace.symtab['::Main'].thebinding end def note(head,val,insp=true) print "__________\n#{head}\n#{insp ? val.inspect : val}\n----------\n" if @verbose end def number_lines(s) cnt = 0 s.split(/\n/).map{|l|cnt+=1; "#{cnt}\t#{l}\n"}.join("") end def file_prelude "\n \#(\n\$ruby_init_code = <<'_CODE'\n#{$ruby_init_code}_CODE\neval(\$ruby_init_code)\n #)\n" end def compile_file(src,dest) src = get_file(src) rb = compile6(src) f = File.open(dest,"w") f.write(file_prelude) f.write(rb) f.close_write rb end def get_file(fn) src = `cat #{fn}` end def parse(src) note :src,src tree = $grammar.prog.search(src,0,false) note :tree,tree tree end def clean (ENV['CLEAN'] && ENV['CLEAN'].to_i > 0) ? ENV['CLEAN'].to_i : nil end require 'digest/md5' def compile6(src,fileline=nil) if ENV['TEST_ONLY_PARSE'] && ENV['TEST_ONLY_PARSE'] != '0' print "1..2\n" tree = parse(src) if tree.bool then print "ok Parsed something...\n" else print "notok Parse failed.\n" end if tree.to == src.length then print "ok Parsed. #{tree.to} vs #{src.length}\n" else print "notok Partial\n" end exit end print "Parsing #{fileline[0]} line #{fileline[1]}.\n" if fileline k = Digest::MD5.hexdigest(src) if (!clean||clean==2) && File.exists?("cache/#{k}.rb") print "Using cache/#{k}.rb\n" return `cat cache/#{k}.rb` end tree = parse(src) if not tree.bool print "Parse failed.\n" return end if not tree.to == src.length print "Parse partial.(#{tree.to} of #{src.length})\n" else print "Parsed.\n" if @verbose end ast = PastFromParse::emit(tree) ast.walk_past_init note :ast,ast rbc = ast.emit_rb note :rbc,number_lines(rbc),false if !clean File.open("cache/#{k}.rb","w"){|f|f.print(rbc)} end rbc end def eval6_file(fn) fnc = "#{fn}.rb" if FileTest.exist?(fnc) && !clean print "Using precompiled #{fnc}\n" rbc = `cat #{fnc}` eval_safe(rbc,nil,fnc,0) else print "Loading #{fn}\n" src = get_file(fn) eval6(src,[fn,1]) end end def eval6q(src,fileline=nil) v = @verbose; @verbose = false; res = eval6(src,fileline); @verbose = v; res end def eval6(src,fileline=nil) rbc = compile6(src,fileline) print "====================\n" if @verbose res = eval_safe(rbc, nil,"compiled:#{fileline ? fileline[0] : ''}", fileline ? fileline[1] : 0) print "=> ",res.inspect,"\n" if @verbose res end def eval_safe(rbc,binding=nil,file=nil,line=nil) binding ||= self.P6_binding_Main eval('_S = BindBox.new(pundef); _E = BindBox.new(pundef)',binding) begin res = eval(rbc,binding,file,line) rescue NameError => boom print number_lines(rbc),boom,"\n",boom.backtrace.join("\n"),"\n" rescue SyntaxError => boom print number_lines(rbc),boom,"\n",boom.backtrace.join("\n"),"\n" end res end def repl begin histfile = File::expand_path("deleteme_hist") if File::exists?(histfile) Readline::HISTORY.push(*(eval(IO.read(histfile))||[])) end while true #src = gets src = Readline.readline("\033[0;31m6\033[0m: ",true) #src = begin readline rescue "" end if src == "" || !src;print "\n"; break end if src == "\n"; break end eval6(src,['repl-input',1]) end rescue Interrupt exit ensure h = Readline::HISTORY.to_a.reverse.uniq.slice(0,100).reverse.inspect open(histfile,"w"){|io|io.puts(h)} end end end $P = P6.new def repl; $P.repl end ###*** Force include() to work with dynamic modules. $module_included_by = {} class Module alias :append_features_pre_ModuleIncludeGraph :append_features def append_features(m) d = $module_included_by[self] ||= [] d.push(m) append_features_pre_ModuleIncludeGraph(m) end def self.refresh_includes_of(m0) todo = [m0] while !todo.empty? m = todo.shift d = $module_included_by[m] || [] d.each{|m1| m.funcall(:append_features_pre_ModuleIncludeGraph,m1) } todo += d end end alias :append_features_pre_ModuleIncludeDynamic :append_features def append_features(m) append_features_pre_ModuleIncludeDynamic(m) Module.refresh_includes_of(m) end end ###*** Multiple inheritance $clasule_of = {} module ClasuleInstanceM def list_is; @@list_is ||= [] end def list_does; @@list_does ||= [] end end module ClasuleM def _clasule_new m = Module.new m.module_eval('include ClasuleInstanceM') $clasule_of[self] = m include m def_is(superclass) if superclass m end def clasule; $clasule_of[self] || _clasule_new end def def_is(k) self.clasule.append_features(k.clasule) self.clasule.list_is.push(k) end def def_does(k) self.clasule.append_features(k.clasule) self.clasule.list_does.push(k) end end class Class; include ClasuleM end class Module; include ClasuleM end class Object def obj_clasule; (class << self; self end).clasule end end class Module def defc(sym,b) clasule.funcall(:define_method,sym,b) end end ###*** Multi-method dispatch # Derived from Christopher Cyll's multi-0.1. (md5 ...30826914ae multi.rb) # Very not the right thing. def multi(method_name, *patterns, &body) body ||= patterns.pop Multi::DISPATCHER.add(Multi::Dispatch, self, method_name, patterns, body) end module Multi class Dispatch def initialize(patterns, body) @patterns = patterns @body = body end def match?(params) pairs = params.zip(@patterns) return pairs.all? do |param, pattern| if pattern.kind_of?(Class) param.kind_of?(pattern) elsif pattern.instance_of?(Proc) begin pattern.call(param) rescue false end elsif pattern.instance_of?(Regexp) pattern.match(param) else param == pattern end end end def call(params, block) @body.call(*params, &block) end end class Dispatcher def initialize @map = {} end def add(type, obj, method_name, patterns, body) method_name = method_name.id2name if method_name.kind_of?(Symbol) body = patterns.pop if body.nil? klass = obj.kind_of?(Module) ? obj : class << obj; self end key = [klass, method_name] @map[key] ||= [] @map[key].push(type.new(patterns, body)) if ! obj.methods.include?(method_name) klass.funcall(:define_method,method_name.to_sym,proc{|*params, &block| Multi::DISPATCHER.dispatch(klass, method_name, params, block) }) end end def dispatch(klass, method_name, params, block) candidates = @map[[klass, method_name]] handler = candidates.find{|candidate| candidate.match?(params) } if handler.nil? printed_params = params.map{|param| param.inspect}.join(', ') raise "No match for #{obj}.#{method_name}(#{printed_params})" end handler.call(params, block) end end DISPATCHER = Dispatcher.new() end ###*** Dynamic env # thanks to Tanaka Akira module Kernel def with_context(params) Thread.current[:dynamic] ||= [] Thread.current[:dynamic].push params begin yield ensure Thread.current[:dynamic].pop end end def find_in_context(name) Thread.current[:dynamic].reverse_each {|params| return params[name] if params.has_key? name } raise "Can't find context value for #{name}" end end ###*** Basics module Kernel def current_class; self.is_a?(Class) ? self : self.class end #X end module Kernel def _args_sep_(args) pa = []; na = {} args.each do |a| if a.pair? (na[a.name] ||= []).push(a.value) else a = a.unwrap if a.wrapped_pair? pa.push(a) end end [pa,na] end end class Object def pair?; false end def wrapped_pair?; false end end class Pair; def pair?; true end end class Module def def_pkg_var(sym,val) # p self,self.class,methods.grep(/#{sym}/) # p begin eval "#{sym}" rescue nil end class_eval %{ def #{sym}; @@#{sym} end def #{sym}=(v); @@#{sym} = v end } # p self,self.class,methods.grep(/#{sym}/),methods.grep(/#{sym}/) class_eval %{ @@#{sym} = ObjectSpace._id2ref(#{val.object_id}) } end end module Kernel def _S; pundef end end class Object; def listy?; false end end ###*** BindBox require 'delegate' class BetterDelegator < Delegator; end class << Object alias :pre_BetterDelegator_method_added :method_added def method_added(id) #print "method_added(#{id.id2name}) on #{self}\n" if self == Object #print "punting #{id.id2name}\n" BetterDelegator.funcall(:undef_method,id) end pre_BetterDelegator_method_added(id) end end class Object; def __getobj__; self end end module ScalarSetM def _(*opt) o = *opt o = *o.to_a if o.listy? o ||= pundef __setobj__(o) end end class BindBoxKeyed < BetterDelegator attr_accessor :aggregate,:key def initialize(aggregate,key) @aggregate,@key=aggregate,key end def __getobj__; @aggregate[@key] || pundef end def __setobj__(o) @aggregate[@key] = o end include ScalarSetM end class BindBox < BetterDelegator attr_accessor :__getobj__ def initialize(*args) super(nil) _(*args) end def __setobj__(o) @__getobj__= o end include ScalarSetM end class BindBoxa < BindBox def _(*args) a = [] args.each{|e| if e.listy? || e.is_a?(Array) || e.is_a?(Range) then a.push(*e.to_a) else a.push(e) end } super(a) end end class BindBoxh < BindBox def self._fudge_init(a) o = {} a.pop if a == [nil] a = a.delisty while not a.empty? k = a.shift v = nil if k.isaC.call(nil,"Pair").as_b k,v = k.name,k.value elsif k.wrapped_pair? p = k.unwrap k,v = p.name,p.value else fail("Odd number of elements initializing hash") if a.empty? v = a.shift end o[k] = v end o end def _(*a) o = BindBoxh._fudge_init(a) super(o) end end class BindBoxc < BindBox end BindBoxS = BindBox BindBoxA = BindBoxa BindBoxH = BindBoxh BindBoxC = BindBoxc ###*** some subs class Undef; end module Kernel; def pundef; Undef.new end end def df(sym,b) sym = "#{sym}C".to_sym bb = BindBox.new(b) eval %{ module Kernel def_pkg_var(#{sym.to_sym.inspect},ObjectSpace._id2ref(#{bb.object_id})) end } end df :say,->(_E,*args){ print args.map{|a|a.as_s},"\n"} df :defined,->(_E,x){ x.is_defined} df :substr,->(_E,s,off,len){ s.slice(off,len)} df :split,->(_E,re,s){ s.split(re)} df :die,->(_E,*args){ fail(*args)} df :try,->(_E,b){ $scalar_ERROR = pundef begin b.call(_E) rescue Exception $scalar_ERROR = "#{$!}" pundef end } df :use,->(_E,c){ puse(c) } def puse(n) fn = "#{n}.pm" $P.eval6_file(fn) end ###*** misc ###**** is_defined class BindBoxKeyed; def is_defined; __getobj__.is_defined end end class BindBox; def is_defined; @__getobj__.is_defined end end class Object; def is_defined; true end end class Undef; def is_defined; false end end class Object; def definedC;->(_S){self.is_defined}end end class NilClass; def is_defined; false end end #X ###**** as_s class BindBoxKeyed; def as_s; __getobj__.as_s end end class BindBox; def as_s; @__getobj__.as_s end end # class Object; def as_s; is defined earlier. class Array; def as_s; map{|e|e.as_s}.join(" ") end end class Undef; def as_s; "" end end class Pair; def as_s; "#{name.as_s}\t#{value.as_s}" end end # class Float; def as_s; is defined elsewhere. ###**** as_i class BindBoxKeyed; def as_i; __getobj__.as_i end end class BindBox; def as_i; @__getobj__.as_i end end class Object; def as_i; to_i end end class Array; def as_i; size end end class Listy; def as_i; size end end ###**** as_b class BindBoxKeyed; def as_b; __getobj__.as_b end end class BindBox; def as_b; @__getobj__.as_b end end class Object; def as_b; true end end class Fixnum; def as_b; self == 0 ? false : true end end class FalseClass; def as_b; false end end class NilClass; def as_b; false end end class Undef; def as_b; false end end ###**** as_h class Hash; def as_h0; self end end class Array; def as_h0; fail("Odd number of elements in array") if size % 2 != 0 Hash[*self] end end ###**** as_n # is elsewhere ###**** ref class BindBox; def refC;proc{ @__getobj__.refC.call }end end class Object; def refC;proc{ "#{self.class}" }end end class String; def refC;proc{ "Str" }end end class TrueClass; def refC;proc{ "Bool" }end end class FalseClass; def refC;proc{ "Bool" }end end class Proc; def refC;proc{ "Code" }end end ###**** true false class Object class Blah attr_accessor :trueC,:falseC def initialize @trueC = BindBox.new(->(_E){ 1 == 1}) @falseC = BindBox.new(->(_E){ 1 == 2}) end self end BLAH = Blah.new def bool; BLAH end end ###**** isa module Any; end class Object; include Any; end module Bool; end class TrueClass; include Bool end class FalseClass; include Bool end module Str; end class String; include Str; end module Num; end module Int; include Num end class Fixnum; include Int; end class Float; include Num; end module Sub; end module Block; end module Code; end class Proc; include Code; end module NotAModuleHack; end class Object # also used by Array/List isaC def isa_hlp(t) t.is_a?(Module) ? t : begin eval("#{t}") rescue Exception; NotAModuleHack end end def isaC;->(_E,s){ self.is_a?(isa_hlp(s)) }end end class BindBox; def isaC;->(_E,s){ @__getobj__.isaC.call(_E,s) }end end ###**** goto class Method; def gotoC;->(*args){ call(*args) }end end class Proc; def gotoC;->(*args){ call(*args) }end end $scalar_ERROR = pundef ###**** arity module Code; def arityC;->(_E){ self.arity }end end #... but sub() procs all have *args so -1 arity. # So need to wrap and label them. Something for another time. ###**** Range class Range def [](i) self.to_a[i] end def to_i; self.to_a.size end end ###**** List # Object#listy? is above. class Array def exists?(n) n >= 0 && n < self.size end def listyconcat(*as) ListyThing.new(self,*as) end def delisty if find{|e| e.listy?} a = [] each{|e| e.listy? ? a.push(*e.to_a) : a.push(e)} a else self end end end module ListyThingM; end class Listy def listy?; true end include ListyThingM def self.[](*elems) new(*elems) end attr_accessor :parts,:offsets def initialize(*elems) @parts = [] normal = nil elems.each{|e| if e.kind_of?(ListyThingM) @parts.push(normal) if normal; normal = nil @parts.push(e) else normal ||= [] normal.push(e) end } @parts.push(normal) if normal @parts.push([]) if @parts.empty? @offsets = [0] end def [](n) pi,off = _find_index(n) pi ? parts[pi][off] : nil end def []=(n,v) pi,off = _find_index(n) pi ? (parts[pi][off] = v) : nil end def size if parts.size > offsets.size off = offsets[-1] (offsets.size..(parts.size-1)).each{|pi| off = offsets[pi] = off + parts[pi-1].size } end offsets[-1] + parts[-1].size end def _find_index(n) if n < 0 npos = size+n return npos < 0 ? nil : self._find_index(size+n) end pi = @offsets.size-1 pi -= 1 while @offsets[pi] > n while true off = n - @offsets[pi] return [pi,off] if (@parts.size == @offsets.size || @parts[pi].exists?(off)) @offsets[pi+1] = @offsets[pi] + @parts[pi].size pi += 1 end end def exists?(n) n >= 0 && n < size end #X def listyconcat(*as) self.class.new(self,*as) end def each(&b) parts.each{|p| p.each(&b)};self end def to_a; a=[]; each{|e| a.push(e)}; a end def map(&b) to_a.map(&b) end end ###**** Array module ArrayM def get(k) BindBoxKeyed.new(self,k) end def elemsC;->(_E){ self.size }end def endC;->(_E){ self.size-1 }end def chompC;->(_E){ self.map{|e|e.chomp} }end def pushC;->(_E,*args){ a = args.delisty a = a[0] if a.size == 1 && a[0].is_a?(Array) #X! self.push(*a) }end def unshiftC;->(_E,*args){ a = args.delisty a = a[0] if a.size == 1 && a[0].is_a?(Array) #X! self.unshift(*a) }end def shiftC;->(_E){ self.shift() || pundef }end def popC;->(_E){ self.pop() || pundef }end def isaC;->(_E,s){ (s == List || s == 'List' || s == Array || s == 'Array' || self.is_a?(isa_hlp(s))) }end def existsC;->(_E,k){ k = size+k if k < 0 k >= 0 && exists?(k) }end def sortC;->(_E){ sort{|a,b| a.as_s <=> b.as_s} }end def joinC;->(_E,s){ map{|e|e.as_s}.join(s.as_s) }end def mapC;->(_E,b){ map{|e| b.call(e)} #X? }end def deleteC;->(_E,*ks){ ks.map{|k| e = self[k]; (k == -1 || k == size - 1) ? delete_at(k) : (self[k] = pundef); e } }end def keysC;->(_E){(0..(size-1)).to_a}end def valuesC;->(_E){dup}end def kvC;->(_E){a=[];each_with_index{|v,k|a.push(k,v)};a}end def pairsC;->(_E){a=[];each_with_index{|v,k|a.push(Pair.new(k,v))};a}end end List = Listy class Listy include ArrayM end class Array include ArrayM end class Range include ArrayM def size; self.to_a.size end end ###**** Hash class Hash def get(k) BindBoxKeyed.new(self,k) end def existsC;->(_E,k){ self[k] && true }end def pairsC;->(_E){ map{|k,v|Pair.new(k,v)} }end def deleteC;->(_E,*ks){ ks.map{|k| e = delete(k); e || pundef} }end def keysC;->(_E){keys}end def valuesC;->(_E){values}end def kvC;->(_E){a=[];each{|k,v|a.push(k,v)};a}end def sortC;->(_E){ sort{|a,b| a[0].as_s <=> b[0].as_s} }end end ###**** Pair class Pair attr_accessor :name,:value def initialize(name,value) @name,@value=name,value end def nameC;->(_E){ @name }end def keyC;->(_E){ @name }end def valueC;->(_E){ @value }end def keysC;->(_E){ [@name] }end def valuesC;->(_E){ [@value] }end def kvC;->(_E){ [@name,@value] }end def pairsC;->(_E){[self]}end def <=>(o) c = name <=> o.name; c != 0 ? c : value <=> o.value end end class WrappedPair attr_accessor :pair def initialize(pair) @pair=pair end def unwrap; @pair end def wrapped_pair?; true end def __getobj__; @pair end end ###**** smartmatch class P6 # multi(:smartmatch,Object,Object){|a,b| a == b} # multi(:aaa,Object){ 42 } # fail('assert') if aaa(3) != 42 # p self,"====================" end ###**** ... class Fixnum; def hex; "#{self}".hex end end class Fixnum; def as_n; self end end class Float; def as_n; self end end class String def as_n case self when /^\+?Inf(inity)?$/; Inf when /^\-Inf(inity)?$/; -Inf when 'NaN'; NaN when /^(0x[0-9a-f]+)/i; eval($1) when /^(0o[0-7]+)/i; eval($1) when /\.|^[0-9]+e/; f = to_f; f == f.floor ? f.to_i : f else to_i end end end class Float def as_s if nan?; 'NaN' elsif infinite?; self < 0 ? '-Inf' : 'Inf' else self == floor ? to_i.to_s : to_s end end end class Fixnum def infinite?; false end def nan?; false end end Kernel::Inf = 1.0/0 Kernel::NaN = 0.0/0 class Range; def as_n; to_a.size end end class Array; def as_n; size end end class Listy; def as_n; size end end class Hash; def as_n; size end end class Object; def asC;->(_S,f){sprintf(f,self)}end end class String def asC;->(_S,f){ begin sprintf(f,self) rescue ArgumentError sprintf(f,self.to_f) end } end def lcfirstC;->(_S){gsub(/^./){|c|c.downcase}} end def lcC;->(_S){downcase} end def ucfirstC;->(_S){gsub(/^./){|c|c.upcase}} end def ucC;->(_S){upcase} end def capitalizeC;->(_S){gsub(/\w+/){|w|w.capitalize}} end def bytesC;->(_S){length} end def charsC;->(_S){length} end def indexC;->(_S,ss,from=0){index(ss,from)||-1} end def rindexC;->(_S,ss,from=0){rindex(ss,from)||-1} end def chompC;->(_S){chomp} end def transC;->(_S,*m){s=self;m.each{|p|s = s.tr(p.name,p.value)};s} end end class Array; def asC;->(_S,f,j){ self.map{|e|e.asC.call(_S,f)}.join(j) }end end class Listy; def asC;->(_S,f,j){ self.map{|e|e.asC.call(_S,f)}.join(j) }end end class Hash; def asC;->(_S,f,j){ self.map{|k,v|sprintf(f,k,v)}.join(j) }end end class Pair; def asC;->(_S,f){ sprintf(f,name,value) }end end require 'rbconfig' # for Config::CONFIG class Object; def self.newC;->(_S,_self,*a){ new(*a) }end end ###** main #print $grammar.parse.search((" "*1000)+"4").inspect # $P.eval6q($six_rt) if not (ENV['TEST_ONLY_PARSE'] && ENV['TEST_ONLY_PARSE'] != 0) def main if ARGV.empty? $P.repl else #STDERR.print ARGV.inspect,"\n\n" exec("perl",*ARGV) if ARGV[0] == '-le' # pugs "make test" :/ ARGV.shift if ARGV[0] == '-w' if ARGV[0] == '-c' $P.compile_file(ARGV[1],ARGV[2]) elsif ARGV[0] == '-e' $P.eval6(ARGV[1]) else print "Parseing #{ARGV[0]}\n" $P.eval6_file(ARGV[0]) end end end _END ); ; source_perl_compiled(0,'',<<'_END' #say("Welcome to red clam.") sub abs($n=$+_) { $n.rUBYabs } sub sign($n) { $n <=> 0 } sub rand($n=1) { rUBYrand($n.rUBYas_i) } sub exp($n) { Rb::Math.rUBYexp($n.rUBYto_f) } sub sqrt($n) { Rb::Math.rUBYsqrt($n.rUBYto_f) } sub log($n) { Rb::Math.rUBYlog($n.rUBYto_f) } sub log10($n) { Rb::Math.rUBYlog10($n.rUBYto_f) } sub int($x) { my $n = $x.rUBYas_n; if $n.rUBYinfinite_P { $n } elsif $n.rUBYnan_P { $n } elsif $n >= 0 { $n.rUBYfloor } else { $n.rUBYceil } } sub shift($o){$o.shift} sub pop($o){$o.pop} sub isa($o,$t){$o.isa($t)} sub ref($o){$o.ref} sub push($o,*@a){$o.push(*@a)} sub unshift($o,*@a){$o.unshift(*@a)} sub eval($code){rUBYeval6q($code)} sub sprintf($f,*@a){rUBYsprintf($f,*@a)} sub hash(*@a){ my %h = *@a; %h } sub elems($a){ $a.elems } sub end($a){ $a.end } sub undefine($x){$x = undef} sub undef(){rUBYpundef} sub lcfirst($s=$+_){$s.lcfirst} sub lc($s=$+_){$s.lc} sub ucfirst($s=$+_){$s.ucfirst} sub uc($s=$+_){$s.uc} sub capitalize($s=$+_){$s.capitalize} sub bytes($s=$+_){$s.bytes} sub chars($s=$+_){$s.chars} sub index($s=$+_,$ss,$from=0){$s.index($ss,$from)} sub rindex($s=$+_,$ss,$from=0){$s.rindex($ss,$from)} #sub chomp($a){ $a.chomp } sub chomp($s=$+_){$s.chomp} sub trans($s,*@m){$s.trans(*@m)} sub keys($o){$o.keys} sub values($o){$o.values} sub kv($o){$o.kv} sub sort($o){$o.sort} sub pi(){raw_rUBY('Math::PI')} sub sin($n){raw_rUBY('Math.sin(nS.as_n)')} sub cos($n){raw_rUBY('Math.cos(nS.as_n)')} sub tan($n){raw_rUBY('Math.tan(nS.as_n)')} sub asin($n){raw_rUBY('Math.asin(nS.as_n)')} sub acos($n){raw_rUBY('Math.acos(nS.as_n)')} sub atan($n,$n2=undef){ $n2 ?? raw_rUBY('Math.atan2(nS.as_n,n2S.as_n)') !! raw_rUBY('Math.atan(nS.as_n)') } our $*OS = raw_rUBY('Config::CONFIG["host_os"]'); our $*OSVER = undef; our $*PERVER = "5.13.0"; our $*PUGS_BACKEND = "BACKEND_PUGS"; # lie for tests :( our @*ARGS = raw_rUBY('ARGV'); #our %*ENV = raw_rUBY('ENV'); our $*IN = raw_rUBY('STDIN'); our $*OUT = raw_rUBY('STDOUT'); our $*ERR = raw_rUBY('STDERR'); our $*PID = raw_rUBY('Process.pid'); our $*UID = raw_rUBY('Process.uid'); our $*GID = raw_rUBY('Process.gid'); say "Welcome! :)" _END ); ; source_perl_finish(); source_ruby(<<'_END' main _END ); ; source_ruby(<<'_END' # (setq ruby-here-doc-beg-re "<<\\(-\\)?\\(\\([a-zA-Z0-9][a-zA-Z0-9_]*\\)\\|[\"]\\([^\"]+\\)[\"]\\|[']\\([^'_][^']*\\)[']\\)") ###* Footer #; Local Variables: #; mode: outline-minor #; outline-regexp: "###[*]+" #; ruby-indent-level: 2 #; perl-indent-level: 2 #; perl-continued-statement-offset: 2 #; perl-continued-brace-offset: -2 #; indent-tabs-mode: nil #; End: #; vim: shiftwidth=2: _END );