#!/usr/bin/env ruby # (c) Copyright 2008 Darren Smith. # MIT License. args = ARGV if (i=ARGV.index("--")) gs_args = args[i+1..-1] args = args[0,i] else gs_args = [] end def usage STDERR.puts "usage: ruby #$0 filename.gs? options* filename.gs to run file or omit for interactive -q: no implicit output -n: no \"\#{ruby code eval}\" -r: int -1 ? generates rational instead of float --: all following args are problem input (single array of strings on stack, no STDIN)" exit(1) end options,filenames = args.partition{|arg|arg[0]=="-"} RationalOption = options.include? "-r" QuineOption = options.include? "-q" NoInterpolationOption = options.include? "-n" AllowedOptions = %w"r q n" unknown_options = options.reject{|option|AllowedOptions.include? option[1..-1]} if !unknown_options.empty? STDERR.puts "unknown options %p" % [unknown_options] usage end if filenames.size > 1 STDERR.puts "multiple filenames present, there can only be one %p" % [filenames] usage end if defined? Encoding Encoding.default_external="ASCII-8BIT" end LB = [] # Make these still work for non integers on newer ruby versions too class Integer alias orig_union | alias orig_inter & alias orig_xor ^ def |(b); orig_union b.to_i end def &(b); orig_inter b.to_i end def ^(b); orig_xor b.to_i end end class Numeric def |(b); to_i.orig_union b end def &(b); to_i.orig_inter b end def ^(b); to_i.orig_xor b end def ~; ~to_i end end GIntId = 0 GArrayId = 1 GStringId = 2 GBlockId = 3 class Gtype def initialize_copy(other); @val = other.val.dup; end attr_reader :val def addop(rhs); rhs.class != self.class ? (a,b=gscoerce(rhs); a.addop(b)) : factory(@val + rhs.val); end def subop(rhs); rhs.class != self.class ? (a,b=gscoerce(rhs); a.subop(b)) : factory(@val - rhs.val); end def uniop(rhs); rhs.class != self.class ? (a,b=gscoerce(rhs); a.uniop(b)) : factory(@val | rhs.val); end def intop(rhs); rhs.class != self.class ? (a,b=gscoerce(rhs); a.intop(b)) : factory(@val & rhs.val); end def difop(rhs); rhs.class != self.class ? (a,b=gscoerce(rhs); a.difop(b)) : factory(@val ^ rhs.val); end def ==(rhs); rhs.class_id!=GIntId && @val==rhs.val; end def eql?(rhs); rhs.class_id!=GIntId && @val==rhs.val; end def hash; @val.hash; end def <=>(rhs); @val<=>rhs.val; end def notop; self.falsey ? 1 : 0; end end class Numeric def class_id; GIntId; end def is_garray; false; end def unsafe_assignment; false; end def addop(rhs); rhs.is_a?(Numeric) ? self + rhs : (a,b=gscoerce(rhs); a.addop(b)); end def subop(rhs); rhs.is_a?(Numeric) ? self - rhs : (a,b=gscoerce(rhs); a.subop(b)); end def uniop(rhs); rhs.is_a?(Numeric) ? self | rhs : (a,b=gscoerce(rhs); a.uniop(b)); end def intop(rhs); rhs.is_a?(Numeric) ? self & rhs : (a,b=gscoerce(rhs); a.intop(b)); end def difop(rhs); rhs.is_a?(Numeric) ? self ^ rhs : (a,b=gscoerce(rhs); a.difop(b)); end def to_gs; Gstring.new(to_s); end def ginspect; to_gs; end def go; Stack< rhs ? 1 : 0; end def equalop(rhs); self == rhs ? 1 : 0; end if RationalOption def question(b) (b<0 && equal?(1) ? 1r : self) ** b end else def question(b) self**(b<0 ? b.to_f : b) end end def leftparen; Stack<b.val ? 1 : 0); end def sort; factory(@val.sort); end def zip r=[] @val.size.times{|x| @val[x].val.size.times{|y| (r[y]||=@val[0].factory([])).val<<@val[x].val[y] } } Garray.new(r) end def ~ val end def comma @val.size end end class Gstring < Garray def initialize(a) @val=case a when NilClass then [] when String then a.unpack('C*') when Array then a when Garray then a.flatten.val end end def factory(a) Gstring.new(a) end def to_gs self end def ginspect factory(to_s.inspect) end def to_s @val.pack('C*') end def class_id; GStringId; end def gscoerce(b) if b.class == Gblock [to_s.compile,b] else b.gscoerce(self).reverse end end def question(b) if b.class == Gstring to_s.index(b.to_s)||-1 elsif b.class == Garray b.question(self) else @val.index(b)||-1 end end def ~ to_s.compile.go nil end end class LazyInput def method_missing(meth, *args, &block) @input ||= ( STDERR.puts "waiting for input to proceed, Ctrl-D for proceed" Gstring.new(STDIN.read)) @input.send(meth, *args, &block) end undef class def ginspect Gstring.new("LazyInput") end def to_gs Gstring.new("") end end class RubyCode def initialize(source,safe) @source = source @safe = safe # unsafe if this code could execute a golfscript block end def unsafe_assignment; true; end attr_reader :source, :safe def go (@cc||=eval"lambda{#{replace_pops @source}}")[] end end def gpop(name) gwarn'pop on empty stack from %p'%name if Stack.empty?;i=LB.size;LB[i] -= 1 while i>0 && LB[i-=1] >= Stack.size;a=Stack.pop; end def gpop_inline(n,name) lhs=[*'a'..'d'][0,n]*"," "(gwarn'pop on empty stack from #{name.inspect}';Stack.replace(([nil]*3+Stack.dup)[-#{n}..-1])) if Stack.size<#{n};i=LB.size;while i>0 && LB[i-=1] > (new_size = Stack.size-#{n});LB[i]=new_size;end;#{lhs}=Stack.pop #{n == 1 ? '' : n};" end def replace_pops(code) code .gsub(/POP1{(.*?)}/){ gpop_inline(1,$1) } .gsub(/POP2{(.*?)}/){ gpop_inline(2,$1) } .gsub(/POP3{(.*?)}/){ gpop_inline(3,$1) } end def exit_compiled(stmt_no) "return resume(#{stmt_no+1})" end def safety_check(stmt_no) "#{exit_compiled(stmt_no)} unless @compiled" end def wipe_all_compiled Blocks.each{|block|block.compiled = nil; block.call_count = 0} end NoEmptyCheck = "(gwarn'cannot assign empty stack';exit(1))if Stack.empty?" Blocks=[] class Gblock < Garray def initialize(impl,gs_source) @val=Gstring.new(gs_source).val @impl=impl @call_count = 0 Blocks<= tokens.size break if begin_no==0 if interactive_mode while ind >= tokens.size s=Readline.readline(" "*depth + "> ", true) exit(0) if !s tokens.concat lex(s) end else pwarn "unmatched {", tokens, begin_no break end end t=tokens[-1+ind+=1] last=t statements.append case t when "{" then block,ind = *compile_helper(tokens,ind,depth+1,interactive_mode) [:block,var("{#{$nprocs+=1}",block)] when "}" then pwarn "unmatched }", tokens, ind if begin_no == 0 break when ":" pwarn "setting the space token (probably accidental)", tokens, ind if tokens[ind]==" " pwarn "expecting identifier, found EOF", tokens, ind if ind>=tokens.size pwarn "cannot really set "+tokens[ind], tokens, ind if "{}:".chars.include? tokens[ind] [:assign,var(tokens[-1+ind+=1])] when /^["']/ then [:var,var(t,Gstring.new(NoInterpolationOption ? eval(t.gsub('#', '# ')).gsub('# ','#') : eval(t)))] when /^-?[0-9]/ then [:var,var(t,t.to_i)] else; [:var,var(t)] end } source=tokens[begin_no...ind-(last=="}"?1:0)]*"" [Gblock.new(statements,source), ind] end end def gpush a Stack.push(*a) if a end def gpush01 a Stack.push(a) if a end def cc(n,name,impl,safe=true) var name,RubyCode.new(n == 0 ? impl : "POP#{n}{#{name}}" + impl,safe) end def order(name,impl,safe=true) cc 2,name,'a,b=b,a if a.class_id','Stack< order greater than, elements greater than or equal to index = order equal to, element at index , 1 or 2 [*0...n], size, select . 1 dup ? order pow, index, find ( 1 deincrement, uncons ) 1 increment, right uncons and or xor 2 print 1 p 1 n 0 puts 1 rand 1 do 1 while until 2 if 3 abs 1 zip 1 base 2 V 0 Print Golfscript/Ruby version Q 0 Print quick ref\'' Stack = [] '"\n":n; {print n print}:puts; {`puts}:p; {1$if}:and; {1$\if}:or; {\!!{!}*}:xor; '.compile.go if filenames.empty? puts "Golfscript Interactive Mode" require "readline" while (code=Readline.readline("> ", true)) begin code.compile(true).go gpush Garray.new(Stack) 'p'.compile.go rescue puts "#{$!.class}: #{$!.message}" end end else code=File.read(filenames[0]) if gs_args.empty? Stack << (STDIN.isatty ? LazyInput.new : Gstring.new(STDIN.read)) else Stack << Garray.new(gs_args.map{|arg| Gstring.new(arg)}) end code.compile.go gpush Garray.new(Stack) 'puts'.compile.go if !QuineOption end