~tim/scheme-vm

c2d0420274fb50473a9c9246b8fd5ed3d5462d41 — Tim Morgan 1 year, 10 months ago 945470c
Refactor library import
2 files changed, 80 insertions(+), 66 deletions(-)

A compiler/import.rb
M compiler/libraries.rb
A compiler/import.rb => compiler/import.rb +73 -0
@@ 0,0 1,73 @@
require_relative './import_binding'

class Import
  def initialize(import_sets:, options:, relative_to:, compiler:)
    @import_sets = import_sets
    @options = options
    @relative_to = relative_to
    @compiler = compiler
    @includes = []
  end

  attr_reader :import_sets, :options, :relative_to, :compiler

  def compile
    sets = import_sets.map do |set|
      import_set(set, options)
    end
    @includes + sets
  end

  private

  def import_set(set, options)
    bindings = import_set_bindings(set, options)
    bindings.map do |binding|
      if binding.syntax
        options[:syntax][binding.external_name] = binding.syntax
        []
      else
        options[:locals][binding.external_name] = true
        [VM::IMPORT_LIB, binding.library_name, binding.internal_name, binding.external_name]
      end
    end
  end

  def import_set_bindings(set, options)
    return import_set_all(set, options) unless set[1].is_a?(Array)
    (directive, source, *identifiers) = set
    bindings = import_set_bindings(source, options)
    available = bindings.each_with_object({}) { |binding, hash| hash[binding.external_name] = binding }
    case directive
    when 'only'
      bindings = available.values_at(*identifiers).compact
    when 'except'
      bindings = available.values_at(*(available.keys - identifiers)).compact
    when 'prefix'
      prefix = identifiers.first
      bindings.each { |binding| binding.prefix = prefix }
    when 'rename'
      renamed = Hash[identifiers]
      bindings.each do |binding|
        next unless (new_name = renamed[binding.external_name])
        binding.external_name = new_name
      end
    else
      raise "unknown import directive #{directive}"
    end
    bindings
  end

  def import_set_all(set, _options)
    name = set.join('.')
    @includes += compiler.include_library_if_needed(name, relative_to, options)
    @compiler.libs[name][:bindings].map do |external_name, internal_name|
      ImportBinding.new(
        library_name: name,
        internal_name: internal_name,
        external_name: external_name,
        syntax: @compiler.libs[name][:syntax][internal_name]
      )
    end
  end
end

M compiler/libraries.rb => compiler/libraries.rb +7 -66
@@ 1,6 1,6 @@
require_relative '../vm'
require_relative '../loader'
require_relative './import_binding'
require_relative './import'

class Compiler
  module Libraries


@@ 30,73 30,14 @@ class Compiler
    end

    def do_import((*sets), relative_to, options)
      sets.map do |set|
        import_set(set, relative_to, options)
      end
    end

    def import_set(set, relative_to, options)
      (include, bindings) = import_set_bindings(set, relative_to, options)
      [
        include,
        bindings.map do |binding|
          if binding.syntax
            options[:syntax][binding.external_name] = binding.syntax
            []
          else
            options[:locals][binding.external_name] = true
            [VM::IMPORT_LIB, binding.library_name, binding.internal_name, binding.external_name]
          end
        end
      ]
    end

    def import_set_bindings(set, relative_to, options)
      return import_set_all(set, relative_to, options) unless set[1].is_a?(Array)
      (directive, source, *identifiers) = set
      (include, bindings) = import_set_bindings(source, relative_to, options)
      available = bindings.each_with_object({}) { |binding, hash| hash[binding.external_name] = binding }
      case directive
      when 'only'
        bindings = available.values_at(*identifiers).compact
      when 'except'
        bindings = available.values_at(*(available.keys - identifiers)).compact
      when 'prefix'
        prefix = identifiers.first
        bindings.each { |binding| binding.prefix = prefix }
      when 'rename'
        renamed = Hash[identifiers]
        bindings.each do |binding|
          next unless (new_name = renamed[binding.external_name])
          binding.external_name = new_name
        end
      else
        raise "unknown import directive #{directive}"
      end
      [include, bindings]
    end

    def import_set_all(set, relative_to, _options)
      name = set.join('.')
      isolated_options = { locals: {}, syntax: {} }
      include = include_library_if_needed(name, relative_to, isolated_options)
      [
        include,
        @libs[name][:bindings].map do |external_name, internal_name|
          ImportBinding.new(
            library_name: name,
            internal_name: internal_name,
            external_name: external_name,
            syntax: @libs[name][:syntax][internal_name]
          )
        end
      ]
      import = Import.new(import_sets: sets, options: options, relative_to: relative_to, compiler: self)
      import.compile
    end

    def include_library_if_needed(name, relative_to, options)
      return [] if @libs.key?(name)
      filename = name.tr('.', '/')
      do_include(["\"#{filename}.scm\""], relative_to, options)
    def include_library_if_needed(library_name, relative_to, options)
      return [] if @libs.key?(library_name)
      filename = library_name.tr('.', '/') + '.scm'
      do_include(["\"#{filename}\""], relative_to, options)
    end

    def do_define_library((name, *declarations), options)