~tim/scheme-vm

bab645d9e2780626256c3947655a9a56724b1381 — Tim Morgan 6 years ago e4df98a
Lots of stuff
M compiler.rb => compiler.rb +42 -32
@@ 66,11 66,11 @@ class Compiler
          expr
        end
      else
        list(sexp, options)
        do_list(sexp, options)
      end
    elsif name.is_a?(Array) || name.is_a?(VM::Pair)
      call(sexp, options)
    elsif respond_to?((underscored_name = name.gsub('->', '_to_').gsub(/([a-z])-/, '\1_')), :include_private)
    elsif respond_to?((underscored_name = 'do_' + name.gsub('->', '_to_').gsub(/([a-z])-/, '\1_')), :include_private)
      send(underscored_name, args, options)
    elsif (transformer = options[:syntax][name])
      macro = compile_sexp([transformer, ['quote', sexp]], options.merge(use: true)) + [VM::HALT]


@@ 105,8 105,12 @@ class Compiler
      [VM::PUSH_TRUE, pop_maybe(options)]
    when '#f'
      [VM::PUSH_FALSE, pop_maybe(options)]
    when /\A#\\(.)/
      [VM::PUSH_CHAR, $1, pop_maybe(options)]
    when /\A#\\(.+)\z/
      char = {
        'space'   => ' ',
        'newline' => "\n"
      }.fetch($1, $1[0])
      [VM::PUSH_CHAR, char, pop_maybe(options)]
    when /\A"(.*)"\z/
      [VM::PUSH_STR, $1]
    when ''


@@ 120,7 124,13 @@ class Compiler
    end
  end

  def car((arg, *_rest), options)
  def do_begin(args, options)
    args.each_with_index.map do |arg, index|
      compile_sexp(arg, options.merge(use: index == args.size - 1))
    end
  end

  def do_car((arg, *_rest), options)
    [
      compile_sexp(arg, options.merge(use: true)),
      VM::PUSH_CAR,


@@ 128,7 138,7 @@ class Compiler
    ]
  end

  def cdr((arg, *_rest), options)
  def do_cdr((arg, *_rest), options)
    [
      compile_sexp(arg, options.merge(use: true)),
      VM::PUSH_CDR,


@@ 136,7 146,7 @@ class Compiler
    ]
  end

  def cons(args, options)
  def do_cons(args, options)
    fail 'cons expects exactly 2 arguments' if args.size != 2
    [
      args.map { |arg| compile_sexp(arg, options.merge(use: true)) },


@@ 145,7 155,7 @@ class Compiler
    ]
  end

  def append(args, options)
  def do_append(args, options)
    [
      args.map { |arg| compile_sexp(arg, options.merge(use: true)) },
      VM::PUSH_NUM, args.size,


@@ 154,7 164,7 @@ class Compiler
    ]
  end

  def null?((arg, *_rest), options)
  def do_null?((arg, *_rest), options)
    [
      compile_sexp(arg, options.merge(use: true)),
      VM::CMP_NULL,


@@ 162,7 172,7 @@ class Compiler
    ]
  end

  def string_ref((string, index), options)
  def do_string_ref((string, index), options)
    [
      compile_sexp(string, options.merge(use: true)),
      compile_sexp(index, options.merge(use: true)),


@@ 171,7 181,7 @@ class Compiler
    ]
  end

  def string_length((string, *_rest), options)
  def do_string_length((string, *_rest), options)
    [
      compile_sexp(string, options.merge(use: true)),
      VM::STR_LEN,


@@ 179,7 189,7 @@ class Compiler
    ]
  end

  def list_to_string((list, *_rest), options)
  def do_list_to_string((list, *_rest), options)
    [
      compile_sexp(list, options.merge(use: true)),
      VM::LIST_TO_STR,


@@ 188,7 198,7 @@ class Compiler
  end


  def quote((arg, *_rest), options)
  def do_quote((arg, *_rest), options)
    if arg.is_a?(Array)
      compile_sexp(arg, options.merge(quote: true))
    else


@@ 196,7 206,7 @@ class Compiler
    end
  end

  def quasiquote((arg, *_rest), options)
  def do_quasiquote((arg, *_rest), options)
    if arg.is_a?(Array)
      compile_sexp(arg, options.merge(quasiquote: true))
    else


@@ 204,7 214,7 @@ class Compiler
    end
  end

  def define((name, val), options)
  def do_define((name, val), options)
    options[:locals][name] = true
    [
      compile_sexp(val, options.merge(use: true)),


@@ 212,7 222,7 @@ class Compiler
    ]
  end

  def lambda((args, *body), options)
  def do_lambda((args, *body), options)
    arg_locals = {}
    if args.is_a?(Array)
      if args.include?('.')


@@ 246,7 256,7 @@ class Compiler
    ]
  end

  def apply((lambda, *args), options)
  def do_apply((lambda, *args), options)
    fail 'apply expects at least 2 arguments' if args.empty?
    [
      args.map { |arg| compile_sexp(arg, options.merge(use: true)) },


@@ 257,7 267,7 @@ class Compiler
    ]
  end

  def list(args, options)
  def do_list(args, options)
    members = args.flat_map do |arg|
      expr = compile_sexp(arg, options.merge(use: true))
      if expr.first == 'splice'


@@ 274,15 284,15 @@ class Compiler
    ]
  end

  def define_syntax((name, transformer), options)
  def do_define_syntax((name, transformer), options)
    transformer.shift
    options[:syntax][name] = syntax_rules(transformer, options)
    options[:syntax][name] = do_syntax_rules(transformer, options)
    []
  end

  # TODO: identifiers
  # TODO: bindings isolated from current scope
  def syntax_rules((_identifiers, *rules), options)
  def do_syntax_rules((_identifiers, *rules), options)
    last = []
    while (rule = rules.pop)
      (pattern, template) = rule


@@ 303,7 313,7 @@ class Compiler
    end
  end

  def if((condition, true_body, false_body), options)
  def do_if((condition, true_body, false_body), options)
    true_instr  = compile_sexp(true_body, options.merge(use: true)).flatten.compact
    false_instr = compile_sexp(false_body, options.merge(use: true)).flatten.compact
    [


@@ 326,18 336,11 @@ class Compiler
    '='   => VM::CMP_EQ_NUM,
    'eq?' => VM::CMP_EQ
  }.each do |name, instruction|
    define_method(name) do |args, options|
    define_method('do_' + name) do |args, options|
      compare(instruction, args, options)
    end
  end

  def include((arg), options)
    [
      compile_sexp(arg, options.merge(use: true)),
      VM::INT, VM::INT_INCLUDE
    ]
  end

  def compare(instruction, (arg1, arg2), options)
    [
      compile_sexp(arg1, options.merge(use: true)),


@@ 347,10 350,17 @@ class Compiler
    ]
  end

  def print(args, options)
  def do_include((arg), options)
    [
      compile_sexp(arg, options.merge(use: true)),
      VM::INT, VM::INT_INCLUDE
    ]
  end

  def do_write(args, options)
    [
      args.map { |arg| compile_sexp(arg, options.merge(use: true)) },
      VM::INT, VM::INT_PRINT_VAL
      VM::INT, VM::INT_WRITE
    ]
  end


A lib/assert.scm => lib/assert.scm +9 -0
@@ 0,0 1,9 @@
(define assert-eq
  (lambda (expected actual)
    (if (not (eq? expected actual))
        (begin
          (print "assert-eq failed:")
          (write "  expected: ")
          (print expected)
          (write "  actual:   ")
          (print actual)))))

A lib/logic.scm => lib/logic.scm +15 -0
@@ 0,0 1,15 @@
(define and
  (lambda conditions
    (if (eq 0 (length conditions))
        #t
        (if (eq 1 (length conditions))
            (car conditions)
            (if (car conditions)
                (apply and (cdr conditions))
                #f)))))

(define not
  (lambda (condition)
    (if condition
        #f
        #t)))

M lib/string.scm => lib/string.scm +11 -3
@@ 11,10 11,18 @@

(define string-append
  (lambda strings1
    (define s-a
    (define s->l
      (lambda strings2
        (if (= 0 (length strings2))
            (list)
            (append (string->list (car strings2))
                    (apply string-append (cdr strings2))))))
    (s-a strings1)))
                    (apply s->l (cdr strings2))))))
    (list->string (apply s->l strings1))))

(define print
  (lambda args
    (if (= 0 (length args))
        (write #\newline)
        (begin
          (write (car args))
          (apply print (cdr args))))))

M spec/compiler_spec.rb => spec/compiler_spec.rb +38 -14
@@ 69,13 69,37 @@ describe Compiler do
    context 'character #\c' do
      before do
        @result = subject.compile([
          '#\c'
          '#\c',
          '#\space',
          '#\newline'
        ])
      end

      it 'compiles into vm instructions' do
        expect(d(@result)).to eq([
          'VM::PUSH_CHAR', 'c',
          'VM::POP',
          'VM::PUSH_CHAR', ' ',
          'VM::POP',
          'VM::PUSH_CHAR', "\n",
          'VM::HALT'
        ])
      end
    end

    context 'begin' do
      before do
        @result = subject.compile([
          ['begin', ['write', '1'], ['write', '2']]
        ])
      end

      it 'compiles into vm instructions' do
        expect(d(@result)).to eq([
          'VM::PUSH_NUM', '1',
          'VM::INT', VM::INT_WRITE,
          'VM::PUSH_NUM', '2',
          'VM::INT', VM::INT_WRITE,
          'VM::HALT'
        ])
      end


@@ 281,7 305,7 @@ describe Compiler do
      context 'given a list' do
        before do
          @result = subject.compile([
            ['quote', ['foo', '2', '3', ['print', '4']]]
            ['quote', ['foo', '2', '3', ['write', '4']]]
          ])
        end



@@ 290,7 314,7 @@ describe Compiler do
            'VM::PUSH_ATOM', 'foo',
            'VM::PUSH_NUM', '2',
            'VM::PUSH_NUM', '3',
            'VM::PUSH_ATOM', 'print',
            'VM::PUSH_ATOM', 'write',
            'VM::PUSH_NUM', '4',
            'VM::PUSH_NUM', 2, # arg count
            'VM::PUSH_LIST',


@@ 321,7 345,7 @@ describe Compiler do
      context 'given a simple list' do
        before do
          @result = subject.compile([
            ['quasiquote', ['foo', '2', '3', ['print', '4']]]
            ['quasiquote', ['foo', '2', '3', ['write', '4']]]
          ])
        end



@@ 330,7 354,7 @@ describe Compiler do
            'VM::PUSH_ATOM', 'foo',
            'VM::PUSH_NUM', '2',
            'VM::PUSH_NUM', '3',
            'VM::PUSH_ATOM', 'print',
            'VM::PUSH_ATOM', 'write',
            'VM::PUSH_NUM', '4',
            'VM::PUSH_NUM', 2, # arg count
            'VM::PUSH_LIST',


@@ 421,17 445,17 @@ describe Compiler do
      end
    end

    context 'print' do
    context 'write' do
      before do
        @result = subject.compile([
          ['print', '1']
          ['write', '1']
        ])
      end

      it 'compiles into vm instructions' do
        expect(d(@result)).to eq([
          'VM::PUSH_NUM', '1',
          'VM::INT', VM::INT_PRINT_VAL,
          'VM::INT', VM::INT_WRITE,
          'VM::HALT'
        ])
      end


@@ 523,7 547,7 @@ describe Compiler do
            ['define', 'one',
              ['lambda', [],
                '1']],
            ['print', ['one']]
            ['write', ['one']]
          ])
        end



@@ 536,7 560,7 @@ describe Compiler do
            'VM::SET_LOCAL', 'one',
            'VM::PUSH_LOCAL', 'one',
            'VM::CALL',
            'VM::INT', VM::INT_PRINT_VAL,
            'VM::INT', VM::INT_WRITE,
            'VM::HALT'
          ])
        end


@@ 810,7 834,7 @@ describe Compiler do
      context 'given value is used' do
        before do
          @result = subject.compile([
            ['print', ['if', '#t', '2', '3']]
            ['write', ['if', '#t', '2', '3']]
          ])
        end



@@ 821,7 845,7 @@ describe Compiler do
            'VM::PUSH_NUM', '2',
            'VM::JUMP', 3,
            'VM::PUSH_NUM', '3',
            'VM::INT', VM::INT_PRINT_VAL,
            'VM::INT', VM::INT_WRITE,
            'VM::HALT'
          ])
        end


@@ 854,7 878,7 @@ describe Compiler do
              ['syntax-rules', [],
                [['and', 'test'], 'test']]],
            ['and', '10'],
            ['and', ['print', '11']]
            ['and', ['write', '11']]
          ])
        end



@@ 863,7 887,7 @@ describe Compiler do
            'VM::PUSH_NUM', '10',
            'VM::POP',
            'VM::PUSH_NUM', '11',
            'VM::INT', VM::INT_PRINT_VAL,
            'VM::INT', VM::INT_WRITE,
            'VM::HALT'
          ])
        end

M spec/examples/fib_spec.rb => spec/examples/fib_spec.rb +2 -2
@@ 17,7 17,7 @@ describe 'Fib' do
                (+
                  (fib (- n 1))
                  (fib (- n 2))))))
        (print (fib 8))
        (write (fib 8))
      END
    end



@@ 38,7 38,7 @@ describe 'Fib' do
                    c
                    (f (- i 1) n (+ c n)))))
            (f n 0 1)))
        (print (fib 8))
        (write (fib 8))
      END
    end


M spec/fixtures/include-test.scm => spec/fixtures/include-test.scm +1 -1
@@ 1,1 1,1 @@
(print "hello from include-test")
(write "hello from include-test")

A spec/lib/bool-spec.scm => spec/lib/bool-spec.scm +4 -0
@@ 0,0 1,4 @@
(include "assert")

(assert-eq #t #t)
(assert-eq #t (not #f))

M spec/lib/list_spec.rb => spec/lib/list_spec.rb +4 -4
@@ 14,7 14,7 @@ describe 'Library' do
        <<-END
          (define empty-list
            (list))
          (print (empty? empty-list))
          (write (empty? empty-list))
        END
      end



@@ 29,7 29,7 @@ describe 'Library' do
        <<-END
          (define non-empty-list
            (list 1 2 3))
          (print (empty? non-empty-list))
          (write (empty? non-empty-list))
        END
      end



@@ 46,7 46,7 @@ describe 'Library' do
        <<-END
          (define empty-list
            (list))
          (print (length empty-list))
          (write (length empty-list))
        END
      end



@@ 61,7 61,7 @@ describe 'Library' do
        <<-END
          (define non-empty-list
            (list 1 2 3))
          (print (length non-empty-list))
          (write (length non-empty-list))
        END
      end


A spec/lib_spec.rb => spec/lib_spec.rb +14 -0
@@ 0,0 1,14 @@
require_relative './spec_helper'
require 'stringio'

describe 'Libs' do
  let(:out) { StringIO.new }

  it 'passes all tests' do
    Dir[File.expand_path('../lib/**/*.scm', __FILE__)].each do |path|
      Program.new(File.read(path), stdout: out).run
      out.rewind
      expect(out.read).to eq('')
    end
  end
end

M spec/vm_spec.rb => spec/vm_spec.rb +17 -36
@@ 315,7 315,6 @@ describe VM do
        [
          VM::PUSH_FUNC,
          VM::PUSH_REMOTE, 'my_func',
          VM::INT, VM::INT_PRINT,
          VM::RETURN,
          VM::ENDF,
          VM::SET_LOCAL, 'my_func',


@@ 330,10 329,9 @@ describe VM do
      end

      it 'pushes the address of the current function onto the stack' do
        stdout.rewind
        expect(stdout.read.to_i).to eq(
        expect(subject.stack).to eq([
          subject.heap.size - instructions.size + 1
        )
        ])
      end
    end



@@ 363,7 361,7 @@ describe VM do

          VM::PUSH_FUNC,
          VM::PUSH_REMOTE, 0, # x
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::RETURN,
          VM::ENDF,
          VM::SET_LOCAL, 1, # my_func


@@ 395,11 393,11 @@ describe VM do
        VM::PUSH_ARGS,
        VM::SET_LOCAL, 3,            # list containing third and fourth args
        VM::PUSH_LOCAL, 1,
        VM::INT, VM::INT_PRINT_VAL,
        VM::INT, VM::INT_WRITE,
        VM::PUSH_LOCAL, 2,
        VM::INT, VM::INT_PRINT_VAL,
        VM::INT, VM::INT_WRITE,
        VM::PUSH_LOCAL, 3,
        VM::INT, VM::INT_PRINT_VAL,
        VM::INT, VM::INT_WRITE,
        VM::RETURN,
        VM::ENDF,
        VM::SET_LOCAL, 0,


@@ 519,28 517,11 @@ describe VM do
  end

  describe 'INT' do
    context 'given arg of INT_PRINT' do
      before do
        subject.execute([
          VM::PUSH_NUM, '123',
          VM::INT, VM::INT_PRINT,
          VM::HALT
        ])
      end

      it 'prints the address of the last item on the stack' do
        stdout.rewind
        address = stdout.read.to_i
        value = subject.resolve(address)
        expect(value).to eq(VM::Int.new(123))
      end
    end

    context 'given arg of INT_PRINT_VAL' do
    context 'given arg of INT_WRITE' do
      before do
        subject.execute([
          VM::PUSH_STR, 'hello world',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::HALT
        ])
      end


@@ 613,7 594,7 @@ describe VM do
      subject.execute([
        VM::PUSH_FUNC,
        VM::PUSH_STR, 'yo',
        VM::INT, VM::INT_PRINT_VAL,
        VM::INT, VM::INT_WRITE,
        VM::RETURN,
        VM::ENDF,
        VM::DUP,


@@ 637,7 618,7 @@ describe VM do
          VM::PUSH_ARGS,
          VM::SET_LOCAL, 'x',
          VM::PUSH_LOCAL, 'x',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::RETURN,
          VM::ENDF,
          VM::SET_LOCAL, 'foo',


@@ 666,9 647,9 @@ describe VM do
          VM::PUSH_ARG,
          VM::SET_LOCAL, 'y',
          VM::PUSH_LOCAL, 'x',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::PUSH_LOCAL, 'y',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::RETURN,
          VM::ENDF,
          VM::SET_LOCAL, 'foo',


@@ 701,11 682,11 @@ describe VM do
          VM::PUSH_ARG,
          VM::SET_LOCAL, 'z',
          VM::PUSH_LOCAL, 'x',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::PUSH_LOCAL, 'z',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::PUSH_LOCAL, 'y',
          VM::INT, VM::INT_PRINT_VAL,
          VM::INT, VM::INT_WRITE,
          VM::RETURN,
          VM::ENDF,
          VM::SET_LOCAL, 'foo',


@@ 937,7 918,7 @@ describe VM do
        VM::PUSH_STR, 'func.',
        VM::SET_LOCAL, 0,
        VM::PUSH_LOCAL, 0,
        VM::INT, VM::INT_PRINT_VAL,
        VM::INT, VM::INT_WRITE,
        VM::POP,
        VM::RETURN,
        VM::ENDF,


@@ 949,7 930,7 @@ describe VM do
        VM::PUSH_STR, 'main.',
        VM::SET_LOCAL, 0,
        VM::PUSH_LOCAL, 0,
        VM::INT, VM::INT_PRINT_VAL,
        VM::INT, VM::INT_WRITE,
        VM::POP,

        VM::PUSH_LOCAL, 1,

M vm.rb => vm.rb +4 -7
@@ 64,8 64,7 @@ class VM
    const_set(name.to_sym, index)
  end

  INT_PRINT     = 1
  INT_PRINT_VAL = 2
  INT_WRITE     = 1
  INT_INCLUDE   = 3

  attr_reader :stack, :heap, :stdout, :ip


@@ 222,10 221,7 @@ class VM
      when INT
        func = fetch
        case func
        when INT_PRINT
          address = peek
          stdout_print(address)
        when INT_PRINT_VAL
        when INT_WRITE
          if (address = pop)
            val = resolve(address)
            stdout_print(val)


@@ 283,7 279,7 @@ class VM
          p @ip
        else
          print 'stack:  '
          puts @stack.map { |a| "#{a} => #{resolve(a).inspect}" }.join(', ') rescue puts @stack.inspect
          puts @stack.map { |a| resolve(a).inspect }.join("\n" + (' ' * 28)) rescue puts @stack.inspect
        end
      end
    end


@@ 437,6 433,7 @@ class VM
    pair
    bool
    string
    logic
  )

  def lib_sexps(lib)

M vm/empty_list.rb => vm/empty_list.rb +4 -0
@@ 11,5 11,9 @@ class VM
    def to_s
      '()'
    end

    def to_a
      []
    end
  end
end

M vm/pair.rb => vm/pair.rb +1 -1
@@ 61,7 61,7 @@ class VM
    end

    def inspect
      "#<VM::Pair @address=#{@address}, @next_node=#{@next_node}, @value=#{@heap[@address].inspect}>"
      "#<VM::Pair size=#{size}, car=#{car.inspect}>"
    end
  end
end