Created
August 24, 2013 18:13
-
-
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 "…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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