Class: Iode::Interpreter

Inherits:
Object
  • Object
show all
Defined in:
lib/iode/interpreter.rb

Overview

Iode interpreter, providing the central #eval function.

Instance Method Summary collapse

Constructor Details

#initialize(scope = Scope.new) ⇒ Interpreter

Create a new Interpreter with a given Scope.

Parameters:

  • scope (Scope) (defaults to: Scope.new)

    the initial environment



24
25
26
# File 'lib/iode/interpreter.rb', line 24

def initialize(scope = Scope.new)
  @env = scope
end

Instance Method Details

#apply(fn, args) ⇒ Object

Apply a function to its arguments.

Parameters:

  • fn (Callable)

    a Proc or a Lambda

  • args (Array)

    a list of arguments to apply with

Returns:

  • (Object)

    the function return value



117
118
119
120
121
122
123
# File 'lib/iode/interpreter.rb', line 117

def apply(fn, args)
  if fn.respond_to?(:call)
    fn.call(*args)
  else
    raise "Cannot apply non-function `#{fn}`"
  end
end

#car(list) ⇒ Object

Get the head (car) of a list.

Parameters:

  • list (Array)

    the list to return the car from

Returns:

  • (Object)

    the first element in list



35
36
37
38
# File 'lib/iode/interpreter.rb', line 35

def car(list)
  v, *_ = list
  v
end

#cdr(list) ⇒ Array

Get the tail (cdr) of a list.

Parameters:

  • list (Array)

    the list to return the cdr from

Returns:

  • (Array)

    all but the head of the list



47
48
49
50
# File 'lib/iode/interpreter.rb', line 47

def cdr(list)
  _, *v = list
  v
end

#eval(sexp) ⇒ Object

Given an iode data structure, execute it.

Parameters:

  • sexp (Object)

    any valid S-expression in iode

Returns:

  • (Object)

    whatever the expression evaluates to



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/iode/interpreter.rb', line 132

def eval(sexp)
  case sexp
  when Array
    case car(sexp)
    when nil
      nil
    when :quote
      car(cdr(sexp))
    when :quasiquote
      car(cdr(sexp))
    when :if
      if eval(car(cdr(sexp)))
        eval(car(cdr(cdr(sexp))))
      else
        eval(car(cdr(cdr(cdr(sexp)))))
      end
    when :progn
      progn(*cdr(sexp))
    when :set!
      @env[car(cdr(sexp))] = eval(car(cdr(cdr(sexp))))
    when :def
      @env.define(car(cdr(sexp)), eval(car(cdr(cdr(sexp)))))
    when :lambda
      lambda(car(cdr(sexp)), *cdr(cdr(sexp)))
    when :macro
      macro(car(cdr(sexp)), *cdr(cdr(sexp)))
    when :apply
      eval([car(cdr(sexp)), *car(cdr(cdr(sexp)))])
    else
      callee = eval(car(sexp))
      case callee
      when Macro
        eval(apply(callee, cdr(sexp)))
      else
        apply(callee, cdr(sexp).map(&method(:eval)))
      end
    end
  when Symbol
    @env[sexp]
  else
    sexp
  end
end

#lambda(argnames, *sexps) ⇒ Lambda

Create a new lambda.

These lambdas act as closures in their environment.

Parameters:

  • argnames (Array)

    a list of argument names as function inputs

  • *sexps (Object...)

    variadic list of S-Expressions for the body

Returns:

  • (Lambda)

    a callable lambda



78
79
80
81
82
83
84
# File 'lib/iode/interpreter.rb', line 78

def lambda(argnames, *sexps)
  Lambda.new do |*args|
    Interpreter.new(
      @env.push_scope(Hash[argnames.zip(args)])
    ).progn(*sexps)
  end
end

#macro(argnames, *sexps) ⇒ Macro

Create a new macro.

Macros are acually just a special case of lambda and also close their environment and can be passed as arguments.

Parameters:

  • argnames (Array)

    a list of argument names as macro inputs

  • *sexps (Object...)

    variadic list of S-Expressions for the body

Returns:

  • (Macro)

    a callable macro



99
100
101
102
103
104
105
# File 'lib/iode/interpreter.rb', line 99

def macro(argnames, *sexps)
  Macro.new do |*args|
    Interpreter.new(
      @env.push_scope(Hash[argnames.zip(args)])
    ).progn(*sexps)
  end
end

#progn(*sexps) ⇒ Object

Create an explicit progn block.

A progn encapsulates a list of S-Expressions to be evaluated in sequence. The last evaluated S-Expression becomes the value of the progn.

Parameters:

  • *sexps (Object...)

    a list of S-Expressions to wrap in a progn

Returns:

  • (Object)

    the value of the last S-Expression



62
63
64
# File 'lib/iode/interpreter.rb', line 62

def progn(*sexps)
  sexps.inject(nil){|_,s| eval(s)}
end