~tim/scheme-vm

6276f397c829732a84c5ed8c87ba3253e0370267 — Tim Morgan 4 years ago 9584a02
Update code style
M .rubocop.yml => .rubocop.yml +3 -0
@@ 7,6 7,9 @@ Style/WordArray:
Style/PerlBackrefs:
  Enabled: false

Style/IndentArray:
  Enabled: false

Metrics/LineLength:
  Max: 120


M Gemfile => Gemfile +1 -1
@@ 4,7 4,7 @@ gem 'parslet'
gem 'pry'

group :development do
  gem 'rspec'
  gem 'pry-stack_explorer'
  gem 'rspec'
  gem 'stackprof'
end

M bin/scheme => bin/scheme +1 -1
@@ 6,7 6,7 @@ require_relative '../program'
options = { debug: 0 }
opt_parser = OptionParser.new do |opts|
  opts.banner = "Usage: scheme [options] path [arguments]\n" \
                "       scheme [options] -e \"(print (+ 2 2))\" [arguments]"
                '       scheme [options] -e "(print (+ 2 2))" [arguments]'

  opts.on('-e SCRIPT', 'Execute script passed as argument') do |script|
    options[:script] = script

M compiler.rb => compiler.rb +10 -10
@@ 10,7 10,7 @@ require 'pry'

class Compiler
  ROOT_PATH = VM::ROOT_PATH
  LOAD_PATH = [File.join(ROOT_PATH, 'lib'), File.join(ROOT_PATH, 'spec')]
  LOAD_PATH = [File.join(ROOT_PATH, 'lib'), File.join(ROOT_PATH, 'spec')].freeze

  include Compiler::Libraries
  include Compiler::Lib::Scheme::Base


@@ 99,7 99,7 @@ class Compiler
    elsif options[:locals][name]
      call(sexp, options)
    else
      fail VM::VariableUndefined, name
      raise VM::VariableUndefined, name
    end
  end



@@ 118,7 118,7 @@ class Compiler
    (name, *args) = sexp
    expr = compile_sexp(args.first, options.merge(quasiquote: false))
    if name == 'unquote-splicing'
      fail 'can only use unquote-splicing with a list' if expr.compact.last != VM::PUSH_LIST
      raise 'can only use unquote-splicing with a list' if expr.compact.last != VM::PUSH_LIST
      ['splice', expr.first]
    else
      expr


@@ 219,7 219,7 @@ class Compiler
  end

  def push_var(name, options)
    fail VM::VariableUndefined, name unless options[:locals][name]
    raise VM::VariableUndefined, name unless options[:locals][name]
    [
      VM::PUSH_VAR,
      name


@@ 245,12 245,12 @@ class Compiler
  end

  def parse_file(filename, relative_to: nil)
    if filename =~ /\A\./ && relative_to
      path = File.join(File.dirname(relative_to), filename)
    else
      path = @load_path.map { |p| File.join(p, filename) }.detect { |p| File.exist?(p) }
    end
    fail "File #{filename} not found in load path #{@load_path.join(';')}" unless path
    path = if filename.start_with?('.') && relative_to
             File.join(File.dirname(relative_to), filename)
           else
             @load_path.map { |p| File.join(p, filename) }.detect { |p| File.exist?(p) }
           end
    raise "File #{filename} not found in load path #{@load_path.join(';')}" unless path
    code = File.read(path)
    @source[filename] = code
    Parser.new(code, filename: filename).parse

M compiler/lib/scheme/base.rb => compiler/lib/scheme/base.rb +2 -2
@@ 12,7 12,7 @@ class Compiler
        end

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


@@ 56,7 56,7 @@ class Compiler
        end

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

M compiler/libraries.rb => compiler/libraries.rb +2 -2
@@ 12,7 12,7 @@ class Compiler

    def do_include(paths, relative_to, options)
      paths.map do |path|
        fail "include expects a string, but got #{path.inspect}" unless path =~ /\A"(.+)?"\z/
        raise "include expects a string, but got #{path.inspect}" unless path =~ /\A"(.+)?"\z/
        filename = "#{$1}.scm"
        sexps = parse_file(filename, relative_to: relative_to)
        compile_sexps(sexps, options: options)


@@ 69,7 69,7 @@ class Compiler
          [name, internal_name, renamed[external_name] || external_name, syntax]
        end
      else
        fail "unknown import directive #{directive}"
        raise "unknown import directive #{directive}"
      end
      [include, bindings]
    end

M compiler/macro.rb => compiler/macro.rb +1 -1
@@ 23,7 23,7 @@ class Compiler
        values = Pattern.new(pattern, literals: @literals).match(sexp)
        return [values, template] if values
      end
      fail "Could not match any template for #{sexp.inspect}"
      raise "Could not match any template for #{sexp.inspect}"
    end

    def expand_template(template)

M parser.rb => parser.rb +1 -1
@@ 71,7 71,7 @@ module LISP
      ',@' => 'unquote-splicing',
      ','  => 'unquote',
      '`'  => 'quasiquote'
    }
    }.freeze

    rule(comment: subtree(:comment))


M spec/lib_spec.rb => spec/lib_spec.rb +1 -1
@@ 27,7 27,7 @@ Dir[File.expand_path('../lib/**/*.scm', __FILE__)].each do |path|
        puts result
        failed = true
      end
      fail 'spec failed' if failed
      raise 'spec failed' if failed
    end
  end
end

M spec/support/dumpable_string_io.rb => spec/support/dumpable_string_io.rb +1 -2
@@ 4,6 4,5 @@ class DumpableStringIO < StringIO
    []
  end

  def marshal_load(_data)
  end
  def marshal_load(_data); end
end

M vm.rb => vm.rb +6 -6
@@ 79,7 79,7 @@ class VM
    ['IMPORT_LIB',    3],
    ['HALT',          0],
    ['DEBUG',         0]
  ]
  ].freeze

  INSTRUCTIONS.each_with_index do |(name, _arity), index|
    const_set(name.to_sym, index)


@@ 96,7 96,7 @@ class VM
    VM::EmptyList,
    VM::Int,
    VM::Pair
  ]
  ].freeze

  attr_reader :stack, :heap, :ip, :call_stack, :closures, :call_args, :libs, :last_atom
  attr_accessor :stdout, :debug


@@ 130,7 130,7 @@ class VM
    debug_output.print_ip if debug >= 2
    case instruction
    when NOOP
      # nothing
      :noop
    when RETURN
      do_return(debug)
    when DEBUG


@@ 143,7 143,7 @@ class VM
  end

  def fetch
    fail "heap[#{@ip}] is not executable" unless executable?(@ip)
    raise "heap[#{@ip}] is not executable" unless executable?(@ip)
    instruction = @heap[@ip]
    @ip += 1
    instruction


@@ 165,7 165,7 @@ class VM

  def resolve(address)
    return Unspecified.instance if address.nil?
    @heap[address] || fail("invalid address #{address}")
    @heap[address] || raise("invalid address #{address}")
  end

  def push(address)


@@ 316,7 316,7 @@ class VM
  def return_value
    if peek
      val = pop_raw
      return val if val.is_a?(Fixnum)
      return val if val.is_a?(Integer)
      return 1 if val == false
    end
    0

M vm/debug_output.rb => vm/debug_output.rb +3 -4
@@ 60,10 60,9 @@ class VM
    def print_stack
      print 'stack:  '
      puts @vm.stack.map { |a| a ? @vm.heap[a].inspect : a.inspect }.join("\n" + (' ' * 28))
      if (atom = @vm.last_atom)
        print 'code:  '.rjust(28)
        puts "#{atom.filename}##{atom.line}:#{atom.column}"
      end
      return unless (atom = @vm.last_atom)
      print 'code:  '.rjust(28)
      puts "#{atom.filename}##{atom.line}:#{atom.column}"
    end
  end
end

M vm/gc.rb => vm/gc.rb +1 -1
@@ 9,7 9,7 @@ class VM
        next if value.nil?
        next if active?(location)
        puts "garbage collecting #{value.inspect} at #{location}" if debug >= 3
        fail "heap[#{@ip}] is not writable" unless @vm.writable?(location)
        raise "heap[#{@ip}] is not writable" unless @vm.writable?(location)
        @vm.heap[location] = nil
      end
    end

M vm/int.rb => vm/int.rb +3 -3
@@ 7,7 7,7 @@ class VM

    def initialize(num)
      @num = num.to_i
      fail 'integer out of range' if @num < MIN || @num > MAX
      raise 'integer out of range' if @num < MIN || @num > MAX
    end

    def raw


@@ 19,8 19,8 @@ class VM
      raw == other.raw
    end

    alias_method :==, :eq?
    alias_method :eqv?, :eq?
    alias == eq?
    alias eqv? eq?

    def +(other)
      Int.new(raw + other.raw)

M vm/operations.rb => vm/operations.rb +11 -14
@@ 58,7 58,7 @@ class VM
    def do_push_local
      name = fetch
      address = named_args[name] || locals[name]
      fail VariableUndefined, name unless address
      raise VariableUndefined, name unless address
      push(address)
    end



@@ 72,13 72,13 @@ class VM
        address = c[:locals][name]
        push(address)
      else
        fail VariableUndefined, name
        raise VariableUndefined, name
      end
    end

    def do_push_args
      last = empty_list
      while args.size > 0
      until args.empty?
        arg = args.pop
        address = alloc
        @heap[address] = build_pair(arg, last)


@@ 141,7 141,7 @@ class VM
        @call_stack << { func: new_ip, return: @ip, args: @call_args, named_args: {} }
      end
      pp @call_stack if @call_stack.size > MAX_CALL_DEPTH
      fail CallStackTooDeep, 'call stack too deep' if @call_stack.size > MAX_CALL_DEPTH
      raise CallStackTooDeep, 'call stack too deep' if @call_stack.size > MAX_CALL_DEPTH
      @ip = new_ip
    end



@@ 153,7 153,7 @@ class VM
      end
      new_ip = pop
      @call_stack << { func: new_ip, return: @ip, args: @call_args, named_args: {} }
      fail CallStackTooDeep, 'call stack too deep' if @call_stack.size > MAX_CALL_DEPTH
      raise CallStackTooDeep, 'call stack too deep' if @call_stack.size > MAX_CALL_DEPTH
      @ip = new_ip
    end



@@ 283,7 283,7 @@ class VM

    def do_append
      count = pop_raw
      if count == 0
      if count.zero?
        push(empty_list)
      else
        raw = (0...count).map { pop_val }.reverse.map(&:to_a).inject(&:+)


@@ 300,10 300,10 @@ class VM
    def do_raw
      raw = pop_raw
      case raw
      when Fixnum
      when Integer
        push_val(VM::Int.new(raw))
      else
        fail "unknown raw value type #{raw.inspect}"
        raise "unknown raw value type #{raw.inspect}"
      end
    end



@@ 319,17 319,14 @@ class VM
      elsif (c = find_closure_with_symbol(name))
        c[:locals][name] = pop
      else
        fail VariableUndefined, name
        raise VariableUndefined, name
      end
    end

    def do_set_args
      count = pop_raw
      if @stack.size >= count
        @call_args = (0...count).map { pop }.reverse
      else
        fail NoStackValue, "stack size is #{@stack.size}, but you tried to use #{count}"
      end
      raise NoStackValue, "stack size is #{@stack.size}, but you tried to use #{count}" if @stack.size < count
      @call_args = (0...count).map { pop }.reverse
    end

    def do_set_arg

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

    def to_s
      items = to_a.flat_map do |item|
        if item.is_a?(Fixnum) # address
        if item.is_a?(Integer) # address
          @heap[item].to_s
        else
          ['.', item.to_s]