Class: RubyLisp::Function

Inherits:
Proc
  • Object
show all
Defined in:
lib/rubylisp/function.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, env, asts, &block) ⇒ Function

Returns a new instance of Function.



46
47
48
49
50
51
52
53
# File 'lib/rubylisp/function.rb', line 46

def initialize(name, env, asts, &block)
  super()
  @name = name
  @env = env
  @arities = construct_arities(asts)
  @is_macro = false
  @lambda = block
end

Instance Attribute Details

#bindingsObject

Returns the value of attribute bindings.



44
45
46
# File 'lib/rubylisp/function.rb', line 44

def bindings
  @bindings
end

#bodyObject

Returns the value of attribute body.



44
45
46
# File 'lib/rubylisp/function.rb', line 44

def body
  @body
end

#envObject

Returns the value of attribute env.



44
45
46
# File 'lib/rubylisp/function.rb', line 44

def env
  @env
end

#is_macroObject

Returns the value of attribute is_macro.



44
45
46
# File 'lib/rubylisp/function.rb', line 44

def is_macro
  @is_macro
end

#lambdaObject

Returns the value of attribute lambda.



44
45
46
# File 'lib/rubylisp/function.rb', line 44

def lambda
  @lambda
end

#nameObject

Returns the value of attribute name.



44
45
46
# File 'lib/rubylisp/function.rb', line 44

def name
  @name
end

Instance Method Details

#construct_arities(asts) ⇒ Object



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
# File 'lib/rubylisp/function.rb', line 55

def construct_arities(asts)
  arities_hash = asts.each_with_object({'arities' => [],
                                        'required' => 0}) do |ast, result|
    arity = Arity.new(ast)

    if arity.rest_args.empty?
      # Prevent conflicts like [x] vs. [y]
      if result['arities'].any? {|existing|
        existing.required_args.count == arity.required_args.count &&
        existing.rest_args.empty?
      }
        raise RuntimeError,
              "Can't have multiple overloads with the same arity."
      end

      # Prevent conflicts like [& xs] vs. [x]
      if result['rest_required']
        unless arity.required_args.count <= result['rest_required']
        raise RuntimeError,
              "Can't have a fixed arity function with more params than a " +
              "variadic function."
        end
      end
    else
      # Prevent conflicts like [x] vs. [& xs]
      if arity.required_args.count < result['required']
        raise RuntimeError,
              "Can't have a fixed arity function with more params than a " +
              "variadic function."
      end

      # Prevent conflicts like [x & xs] vs. [x y & ys]
      if result['arities'].any? {|existing| !existing.rest_args.empty?}
        raise RuntimeError,
              "Can't have more than one variadic overload."
      end

      result['rest_required'] = arity.required_args.count
    end

    result['required'] = [result['required'], arity.required_args.count].max
    result['arities'] << arity
  end

  arities_hash['arities']
end

#gen_env(arity, args, env) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/rubylisp/function.rb', line 121

def gen_env(arity, args, env)
  # set out_env to the current namespace so that `def` occurring within
  # the fn's environment will define things in the namespace in which the
  # function is called
  out_env = env.out_env || env.find_namespace
  env = Environment.new(outer: @env, out_env: out_env)
  # so the fn can call itself recursively
  env.set(@name, self)

  if arity.rest_args.empty?
    # bind values to the required args
    arity.required_args.zip(args).each do |k, v|
      env.set(k, v)
    end
  else
    # bind values to the required args (the rest args are skipped here)
    arity.required_args.zip(args).each do |k, v|
      env.set(k, v)
    end

    # bind the rest argument to the remaining arguments or nil
    rest_args = if args.count > arity.required_args.count
                  args[arity.required_args.count..-1].to_list
                else
                  nil
                end

    env.set(arity.rest_args.first, rest_args)
  end

  env
end

#get_arity(args) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/rubylisp/function.rb', line 102

def get_arity(args)
  # Assert that there are enough arguments provided for the arities we have.
  sexp = list [Symbol.new(@name), *args]
  variadic = @arities.find {|arity| !arity.rest_args.empty?}
  fixed_arities = @arities.select {|arity| arity.rest_args.empty?}
  fixed = fixed_arities.find {|arity| arity.required_args.count == args.count}

  # Return the arity most appropriate for the number of args provided.
  if fixed
    fixed
  elsif variadic
    assert_at_least_n_args sexp, variadic.required_args.count
    variadic
  else
    raise RuntimeError,
          "Wrong number of args (#{args.count}) passed to #{@name}"
  end
end