~tim/scheme-vm

scheme-vm/compiler/macro.rb -rw-r--r-- 2.9 KiB
00cdee12Tim Morgan Remove old code 3 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
require_relative 'pattern'

class Compiler
  class Macro
    def initialize(macro, compiler)
      @macro = macro
      @locals = macro[:locals]
      (_, @literals, *@patterns) = macro[:transformer]
      @compiler = compiler
    end

    def expand(sexp)
      (@values, @template) = match_template(sexp)
      sexp = expand_template(@template)
      sexp = mangle_macro_bindings(sexp)
      sexp
    end

    private

    def match_template(sexp)
      @patterns.each do |pattern, template|
        values = Pattern.new(pattern, literals: @literals).match(sexp)
        return [values, template] if values
      end
      raise "Could not match any template for #{sexp.inspect}"
    end

    def expand_template(template)
      return @values[template] if @values.key?(template)
      return template unless template.is_a?(Array)
      ([nil] + template).each_cons(2).flat_map do |(prev, part)|
        if part == '...' && prev
          expand_elipsis_template(prev)
        else
          [expand_template(part)].compact
        end
      end
    end

    def expand_elipsis_template(template)
      if template.is_a?(Array)
        name = template.flatten.detect { |n| @values.key?("#{n}...") }
        (0...@values["#{name}..."].size).map do |index|
          duplicate_elipsis_template(template, index, @values)
        end
      else
        expand_template("#{template}...")
      end
    end

    def duplicate_elipsis_template(template, index, values)
      template.map do |part|
        if part.is_a?(Array)
          duplicate_elipsis_template(part, index, values)
        elsif values.key?(part)
          values["#{part}..."][index]
        else
          part
        end
      end
    end

    def mangle_macro_bindings(sexp)
      return sexp unless sexp.is_a?(Array) && @template.is_a?(Array)
      sexp.map do |part|
        if part.is_a?(Array)
          mangle_macro_bindings(part)
        elsif mangled_identifiers.key?(part)
          mangled_identifiers[part]
        else
          part
        end
      end
    end

    def mangled_identifiers
      @bindings ||= begin
        # FIXME: won't work with some identifiers and literal elipsis
        identifiers = @template.flatten.select { |name| name =~ /\A[a-z]/ }.uniq
        identifiers.each_with_object({}) do |identifier, hash|
          hash[identifier] = if macro?(identifier)
                               identifier
                             elsif known_identifier?(identifier)
                               @macro[:lib] ? "##{@macro[:lib]}:#{identifier}" : identifier
                             else
                               @compiler.mangle_identifier(identifier)
                             end
        end
      end
    end

    def macro?(name)
      return false if @macro[:syntax].nil?
      @macro[:syntax][name] # TODO: maybe built_in_function should be here -- not sure!
    end

    def known_identifier?(name)
      @locals.include?(name) || @compiler.built_in_function?(name)
    end
  end
end