class Campa::Evaler

All the actual logic on how to evaluate the differente known forms in implemented in here.

Attributes

printer[R]

Public Class Methods

new() click to toggle source
# File lib/campa/evaler.rb, line 6
def initialize
  @printer = Printer.new
end

Public Instance Methods

call(expression, env = {}) click to toggle source

Returns the result of a given form evaluation.

The parameter expression is evaluated based on it's type. Primitives (like booleans, nil, strings…) are returned as is. {Symbol}s and {List}s are handled like this:

  • {Symbol}'s are searched in the given {Context} (env parameter).

  • {List}'s are considered function invocations.

@param expression Can be any known form. @param env [#[], []=] Hash or {Context} containing the bindings

for the current <i>Campa</i> execution.
# File lib/campa/evaler.rb, line 22
def call(expression, env = {})
  context = self.context(env)

  case expression
  when Numeric, TrueClass, FalseClass, NilClass, String, ::Symbol, List::EMPTY
    expression
  when Symbol
    resolve(expression, context)
  when List
    invoke(expression, context)
  end
end
eval(reader, env = {}) click to toggle source

Receives a {Reader} object and evaluate all forms returned by each #next call. Uses {#call} to do the actual evaluation.

@param reader [Reader] representing the source code to be evaluated @param env [Context] to evaluate the code @return [Object] the result of evaluating the last form

available in the given {Reader}
# File lib/campa/evaler.rb, line 44
def eval(reader, env = {})
  context = self.context(env)

  result = nil
  while (token = reader.next)
    result = call(token, context)
  end
  result
end

Private Instance Methods

args_for_fun(fun, args, context) click to toggle source
# File lib/campa/evaler.rb, line 111
def args_for_fun(fun, args, context)
  return args if fun.respond_to?(:macro?) && fun.macro?

  args.map { |exp| call(exp, context) }
end
context(env) click to toggle source
# File lib/campa/evaler.rb, line 58
def context(env)
  return env if env.is_a?(Context)

  Context.new(env)
end
cr?(invocation) click to toggle source
# File lib/campa/evaler.rb, line 82
def cr?(invocation)
  invocation.head.is_a?(Symbol) &&
    invocation.head.label.match?(CR_REGEX)
end
extract_fun(invocation, context) click to toggle source
# File lib/campa/evaler.rb, line 98
def extract_fun(invocation, context)
  # probable lambda invocation
  return call(invocation.head, context) if invocation.head.is_a?(List)

  resolve(invocation.head, context)
    .then { |rs| rs.is_a?(List) ? call(rs, context) : rs }
    .tap { |fn| raise not_a_function(invocation) if !fn.respond_to?(:call) }
end
invoke(invocation, context) click to toggle source
# File lib/campa/evaler.rb, line 70
def invoke(invocation, context)
  return invoke_cadr(invocation, context) if cr?(invocation)

  fn = extract_fun(invocation, context)
  args = args_for_fun(fn, invocation.tail.to_a, context)
  if with_env?(fn)
    fn.call(*args, env: context)
  else
    fn.call(*args)
  end
end
invoke_cadr(invocation, context) click to toggle source
# File lib/campa/evaler.rb, line 87
def invoke_cadr(invocation, context)
  call(
    List.new(
      Symbol.new("_cadr"),
      invocation.head,
      call(invocation.tail.head, context)
    ),
    context
  )
end
not_a_function(invocation) click to toggle source
# File lib/campa/evaler.rb, line 107
def not_a_function(invocation)
  Error::NotAFunction.new printer.call(invocation.head)
end
params_from_fun(fun) click to toggle source
# File lib/campa/evaler.rb, line 124
def params_from_fun(fun)
  return fun.parameters if fun.is_a?(Proc)

  fun.method(:call).parameters
end
resolve(symbol, context) click to toggle source
# File lib/campa/evaler.rb, line 64
def resolve(symbol, context)
  raise Error::Resolution, printer.call(symbol) if !context.include?(symbol)

  context[symbol]
end
with_env?(fun) click to toggle source
# File lib/campa/evaler.rb, line 117
def with_env?(fun)
  !params_from_fun(fun)
    .filter { |param| param[0] == :keyreq }
    .find { |param| param[1] == :env }
    .nil?
end