~tim/scheme-vm

053f2c4eaa2c0392e3e60ab17b105a54b11c4190 — Tim Morgan 6 years ago b0f5734
Add null? to vm, and add empty? and length to lib
M compiler.rb => compiler.rb +8 -0
@@ 125,6 125,14 @@ class Compiler
    ]
  end

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

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

A lib/list.scm => lib/list.scm +5 -0
@@ 0,0 1,5 @@
(define empty? (lambda (l)
  (null? l)))

(define length (lambda (l)
  (if (empty? l) 0 (+ 1 (length (cdr l))))))

M program.rb => program.rb +14 -1
@@ 17,10 17,23 @@ class Program
  private

  def compiler
    @compiler ||= Compiler.new(@sexps)
    @compiler ||= Compiler.new(lib_sexps + @sexps)
  end

  def vm
    @vm ||= VM.new(@instr, stdout: @stdout, args: @args)
  end

  def lib_sexps
    libs = [
      lib_code('list.scm')
    ]
    Parser.new(libs.join).parse
  end

  ROOT_PATH = File.expand_path("..", __FILE__)

  def lib_code(filename)
    File.read(File.join(ROOT_PATH, 'lib', filename))
  end
end

M spec/compiler_spec.rb => spec/compiler_spec.rb +17 -0
@@ 111,6 111,23 @@ describe Compiler do
      end
    end

    context 'null?' do
      before do
        @result = subject.compile([
          ['null?', ['list']],
        ])
      end

      it 'compiles into vm instructions' do
        expect(d(@result)).to eq([
          'VM::PUSH_NUM', 0,
          'VM::PUSH_LIST',
          'VM::CMP_NULL',
          'VM::HALT'
        ])
      end
    end

    context 'local variable' do
      before do
        @result = subject.compile([

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

describe 'Library' do
  let(:stdout) { StringIO.new }

  let(:subject) { Program.new(code, stdout: stdout) }

  before { subject.run }

  describe 'empty?' do
    context 'given an empty list' do
      let(:code) do
        <<-END
          (define empty-list
            (list))
          (print (empty? empty-list))
        END
      end

      it 'returns #t' do
        stdout.rewind
        expect(stdout.read).to eq('#t')
      end
    end

    context 'given a non-empty list' do
      let(:code) do
        <<-END
          (define non-empty-list
            (list 1 2 3))
          (print (empty? non-empty-list))
        END
      end

      it 'returns #f' do
        stdout.rewind
        expect(stdout.read).to eq('#f')
      end
    end
  end

  describe 'length' do
    context 'given an empty list' do
      let(:code) do
        <<-END
          (define empty-list
            (list))
          (print (length empty-list))
        END
      end

      it 'returns 0' do
        stdout.rewind
        expect(stdout.read).to eq('0')
      end
    end

    context 'given a non-empty list' do
      let(:code) do
        <<-END
          (define non-empty-list
            (list 1 2 3))
          (print (length non-empty-list))
        END
      end

      it 'returns the list length' do
        stdout.rewind
        expect(stdout.read).to eq('3')
      end
    end
  end
end

M spec/vm_spec.rb => spec/vm_spec.rb +28 -5
@@ 468,7 468,7 @@ describe VM do
      ])
    end

    it 'removes both values and puts a 1 or 0 on the stack' do
    it 'removes both values and puts a #t or #f on the stack' do
      expect(subject.stack_values).to eq([
        VM::BoolFalse.instance,
        VM::BoolFalse.instance,


@@ 493,7 493,7 @@ describe VM do
      ])
    end

    it 'removes both values and puts a 1 or 0 on the stack' do
    it 'removes both values and puts a #t or #f on the stack' do
      expect(subject.stack_values).to eq([
        VM::BoolFalse.instance,
        VM::BoolTrue.instance,


@@ 518,7 518,7 @@ describe VM do
      ])
    end

    it 'removes both values and puts a 1 or 0 on the stack' do
    it 'removes both values and puts a #t or #f on the stack' do
      expect(subject.stack_values).to eq([
        VM::BoolTrue.instance,
        VM::BoolFalse.instance,


@@ 543,7 543,7 @@ describe VM do
      ])
    end

    it 'removes both values and puts a 1 or 0 on the stack' do
    it 'removes both values and puts a #t or #f on the stack' do
      expect(subject.stack_values).to eq([
        VM::BoolTrue.instance,
        VM::BoolTrue.instance,


@@ 565,7 565,30 @@ describe VM do
      ])
    end

    it 'removes both values and puts a 1 or 0 on the stack' do
    it 'removes both values and puts a #t or #f on the stack' do
      expect(subject.stack_values).to eq([
        VM::BoolTrue.instance,
        VM::BoolFalse.instance
      ])
    end
  end

  describe 'CMP_NULL' do
    before do
      subject.execute([
        VM::PUSH_NUM, 0,
        VM::PUSH_LIST,
        VM::CMP_NULL,
        VM::PUSH_NUM, '1',
        VM::PUSH_NUM, '2',
        VM::PUSH_NUM, 2,
        VM::PUSH_LIST,
        VM::CMP_NULL,
        VM::HALT
      ])
    end

    it 'removes the value and puts a #t or #f on the stack' do
      expect(subject.stack_values).to eq([
        VM::BoolTrue.instance,
        VM::BoolFalse.instance

M vm.rb => vm.rb +5 -2
@@ 33,6 33,7 @@ class VM
    ['CMP_LT',       0],
    ['CMP_LTE',      0],
    ['CMP_EQ',       0],
    ['CMP_NULL',     0],
    ['DUP',          0],
    ['ENDF',         0],
    ['INT',          1],


@@ 107,8 108,7 @@ class VM
        push_val(pair)
      when PUSH_LIST
        count = pop_raw
        last = empty_list
        address = nil
        address = last = empty_list
        count.times do
          arg = pop
          address = alloc


@@ 172,6 172,9 @@ class VM
        num2 = pop_val
        num1 = pop_val
        num1 == num2 ? push_true : push_false
      when CMP_NULL
        val = pop_val
        val == EmptyList.instance ? push_true : push_false
      when DUP
        val = peek
        push(val)

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

    def to_s
      raw.to_s
      '#f'
    end
  end
end

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

    def to_s
      raw.to_s
      '#t'
    end
  end
end