class Waltz

Attributes

compiled[RW]
control_stack[RW]
control_words[RW]
runtime_stack[RW]
runtime_words[RW]

Public Class Methods

new(waltz_lib=nil) click to toggle source
# File lib/waltz.rb, line 14
def initialize waltz_lib=nil
  @runtime_stack = []
  @control_stack = []
  @compiled = []

  @runtime_words = {
    '+' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a + b)
    },

    '*' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a * b)
    },

    '-' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a - b)
    },

    '/' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a / b)
    },

    '=' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a == b)
    },

    '>' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a > b)
    },

    '<' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push (a < b)
    },

    'swap' => -> {
      a, b = @runtime_stack.pop(2)
      @runtime_stack.push b, a
    },

    'dup' => -> {
      @runtime_stack.push @runtime_stack.last
    },

    'drop' => -> {
      @runtime_stack.pop
    },

    'over' => -> {
      @runtime_stack.push @runtime_stack[-2]
    },

    '..' => -> {
      puts "stack: #{@runtime_stack.inspect}"
    },

    '.' => -> {
      p @runtime_stack.pop
    }
  }

  @control_words = {
    ':' => -> {
      if @control_stack.empty?
        @control_stack.push ":"
      else
        raise ": inside control stack: #{@control_stack}"
      end
    },

    ';' => -> {
      unless @control_stack.first == ":"
        raise ": not balanced with ; in control stack: #{@control_stack}"
      end

      word, *body = @control_stack[1..-1]

      unless word
        raise "Unnamed word definition in control stack: #{@control_stack}"
      end

      @runtime_words[word] = body
      @control_stack = []
    }
  }

  if waltz_lib
    compile_and_run waltz_lib
  end
end

Public Instance Methods

compile(line) click to toggle source
# File lib/waltz.rb, line 122
def compile line
  words = line.remove_backslash_comments.split

  words.each do |word|
    control_action = @control_words[word]
    runtime_action = @runtime_words[word]

    if @control_stack.first == ":"
      if @control_stack.count == 1
        if control_action or runtime_action
          raise "#{word} is already defined."
        else
          @control_stack.push word # name of new word
          next
        end
      else
        current_stack = @control_stack
      end
    else
      current_stack = @compiled
    end

    if control_action
      control_action.call
    elsif runtime_action
      if runtime_action.is_a? Array
        # do dynamic lookup for now
        current_stack.push -> { run_word word }
      else
        current_stack.push runtime_action
      end
    else
      if /(0|[1-9]\d*)\.\d+/ =~ word
        current_stack.push -> { @runtime_stack.push word.to_f }
      elsif /0|[1-9]\d*/ =~ word
        current_stack.push -> { @runtime_stack.push word.to_i }
      else
        # assume word will be defined by the time it's run
        current_stack.push -> { run_word word }
      end
    end
  end
end
compile_and_run(line) click to toggle source
# File lib/waltz.rb, line 187
def compile_and_run line
  compile line
  if @control_stack.empty?
    run
  else
    raise "Control stack not empty: #{@control_stack.inspect}"
  end
end
load_state(state) click to toggle source
# File lib/waltz.rb, line 115
def load_state state
  runtime_stack, control_stack, compiled = state
  @runtime_stack = runtime_stack
  @control_stack = control_stack
  @compiled = compiled
end
repl() click to toggle source
# File lib/waltz.rb, line 196
def repl
  prompt = 'waltz> '
  while line = Readline.readline(prompt, true)
    save_state = state
    begin
      compile line
      if @control_stack.empty?
        run
        prompt = 'waltz> '
      else
        prompt = '   ... '
      end
    rescue => e
      p e
      load_state(save_state)
    end
  end
end
run() click to toggle source
# File lib/waltz.rb, line 179
def run
  @compiled.each do |action|
    action.call
  end
ensure
  @compiled = []
end
run_word(word) click to toggle source
# File lib/waltz.rb, line 166
def run_word word
  runtime_action = @runtime_words[word]
  if runtime_action.is_a? Proc
    runtime_action.call
  elsif runtime_action.is_a? Array
    runtime_action.each { |action| action.call }
  elsif runtime_action.is_a? NilClass
    raise "Undefined word: #{word}"
  else
    raise "Unexpected runtime word type #{word}: #{word.class}"
  end
end
state() click to toggle source
# File lib/waltz.rb, line 111
def state
  [@runtime_stack.dup, @control_stack.dup, @compiled.dup]
end