class RubyLisp::Function

Attributes

bindings[RW]
body[RW]
env[RW]
is_macro[RW]
lambda[RW]
name[RW]

Public Class Methods

new(name, env, asts, &block) click to toggle source
Calls superclass method
# 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

Public Instance Methods

construct_arities(asts) click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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