Skip to content

Instantly share code, notes, and snippets.

@deciode
Created August 24, 2013 18:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save deciode/6329560 to your computer and use it in GitHub Desktop.
Save deciode/6329560 to your computer and use it in GitHub Desktop.
This is a Ruby interpreter for StackStack, an esolang in bloom from the mind of T.J.S.1. It should not be considered suitable for use until It performs at least some of the argument guards mandated by the spec. That said, it works just fine in all of my tests; I'll be releasing those shortly along with the six lines of Bash that constitute the "…
#!/usr/bin/env ruby
#coding: utf-8
# Like Integer() and Float(), Num() will raise an exception
# on non-numeric input, but the happy path roughly simulates
# JavaScript's everything-floats-until-it-doesn't behavior.
def Num n
(n = Float n) % 1 > 0 ? n : n.to_i
end
class StackStack
# The stack is "global" by necessity, but the variable store
# is shared for parity with the reference interpreter. Also,
# functions are stored in it in case they're values some day.
# These should /probably/ be class variables, but @@ is loud.
S, V = [], {}
# JavaScript's falsy values are now ours. Yay.
Falsy = {0 => 1, false => 1, '' => 1}
def initialize src
if V.empty? # Are we the first instance?
src.gsub! /(#.*\n|(".*"))/, '\2' # Strip comments.
src.gsub! /\$(\S+)(.+?\n\n)/m do # Find functions.
V[$~[1]] = StackStack.new $~[2] # Store subprograms.
nil # Remove them from the source.
end
end
# Lex terms as either quote-delimited strings or sequences of
# non-whitespace. If the lexeme wasn't Numeric, try to remove
# its quotes and store it as a Ruby String. !delete() returns
# nil if it didn't do anything, in which case we have what is
# hopefully a valid StackStack operation, stored as a Symbol.
@terms = src.scan(/"[^"]*"|\S+/).map do |t|
Num t rescue t.delete!('"') || t.to_sym
end
# All branching is precomputed in the interest of efficiency.
# Keying @I with the offset of an if/else will give back the
# offset of the corresponding else/endif, but not vice-versa.
i, @I, w, ws = [], {}, [], {}
@terms.each_with_index do |t, o|
next unless Symbol === t
i << [o] if t == :if
i[-1].concat [o, o] if t == :else
@I.update Hash[*i.pop << o] if t == :endif
w << o if t == :while
ws[o] = w.pop if t == :endwhile
end
# while/endwhile are bidirectional, so @W does go both ways.
@W = ws.update ws.invert
end
# TODO: Flesh this idea out.
def to_s
S << @terms; ''
end
# Virtually all of StackStack's operations are handled cleanly.
# roll and input* are a little verbose, but everything else is
# largely self-explanatory. Symbols are treated as strings and
# matched against regular expressions where "appropriate", but
# this aspect of the language hasn't quite solidified just yet.
def run trace = false
ip = -1 # instruction pointer
while (ip +=1) < @terms.size
case term = @terms[ip]
when Numeric, String; S << term # Literals get pushed.
when /@(\S+)/; V[$1].run(trace) # Functions get run.
when :stop; exit
# These are the agnostic operators.
when /[-+\/*%]/; S << S.pop(2).reduce($&)
# Integers get all the bits.
when /[&|^]/; S << S.pop(2).map(&:to_i).reduce($&)
# negate is lonely.
when :~; S << ~S.pop.to_i
# strings
when :ascii; S << S.pop.ord
when /tostr/; S << S.pop.to_s
when :strlen; S << S[-1].size
when /index/; S << S[-2][S.pop]
when :char; S << ('' << S.pop)
when /cat/; S << S.pop(2).join
# stack
when :pop; S.pop
when :rand; S << rand
when /^dup/; S << S[-1]
when /size/; S << S.size
when :swap; S.insert(-2, S.pop)
when :roll; (n = S.pop) > 0 ?
S << S.delete_at(-n) :
S.insert(n, S.pop)
# storage
when :store; V[S.pop] = S.pop
when /^retr/; S << V[S.pop]
# comparison
when />|gre/; S << (S.pop < S.pop)
when /=|^eq/; S << (S.pop == S.pop)
when /<|les/; S << (S.pop > S.pop)
when /!|not/; S << Falsy[S.pop].to_i
# arrays
when :new; S << Class.const_get(S.pop.capitalize).new
when :push; S[-2] << S.pop
when :sum; S << S[-1].reduce(:+)
when :join; S << S.pop(2).reduce(:*)
# IO
when :print; print S.pop
when /in.*/; i = STDIN.gets.chomp rescue exit # Die on EOF.
S << ($&['s'] ? i : (Num i rescue i))
# branching
when /(end)?while/
ip = @W[ip] if Falsy[S.pop] == ($1 ? nil : 1)
when :if; ip = @I[ip] if Falsy[S.pop]
when :else; ip = @I[ip]
when :endif # NOP
else raise "Unrecognized term: '#{term}'"
end
puts "term: #{term}\tstack: #{S}" if trace
end
end
end
StackStack.new(ARGF.read).run
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment