Class: Heist::Runtime::Frame

Inherits:
Object
  • Object
show all
Defined in:
lib/heist/runtime/frame.rb

Overview

Frame is used by the Stack class to encapsulate a single Expression bound to a Scope. Frame objects evaluate expressions one sub-expression at a time, providing fine-grained opportunity for inspecting intermediate results and supporting the Scheme notion of continuations.

The state of a Frame is held in five variables: @expression, @scope, @values, @current and @complete. @expression is the Expression object the Frame is evaluating, and @scope is a Scope object in which to evaluate it. @values is a copy of @expression that is used to store the values returned by the subexpressions; it represents the partially evaluated state of the expression for use when saving a Stack when creating a Continuation. @current is a pointer to the subexpression that is to be evaluated next, and @complete is a boolean that indicates whether all the required subexpressions have been evaluated.

See Stack for a fuller explanation of how expressions are evaluated.

Direct Known Subclasses

Body

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(expression, scope) ⇒ Frame

A Frame is initialized using an Expression and a Scope, which are simply stored as instance variables. No evaluation takes place upon initialization.



28
29
30
31
# File 'lib/heist/runtime/frame.rb', line 28

def initialize(expression, scope)
  reset!(expression)
  @scope = scope
end

Instance Attribute Details

#expressionObject (readonly)

Returns the value of attribute expression.



23
24
25
# File 'lib/heist/runtime/frame.rb', line 23

def expression
  @expression
end

#scopeObject (readonly)

Returns the value of attribute scope.



23
24
25
# File 'lib/heist/runtime/frame.rb', line 23

def scope
  @scope
end

Instance Method Details

#cloneObject

Returns a copy of the Frame. The @values variable is also copied by this method so that state changes do not transfer from the copy to the original.



88
89
90
91
92
# File 'lib/heist/runtime/frame.rb', line 88

def clone
  copy, values = super, @values.clone
  copy.instance_eval { @values = values }
  copy
end

#complete?Boolean

Returns true iff the Frame has completed evaluating its expression.

Returns:

  • (Boolean)


34
35
36
# File 'lib/heist/runtime/frame.rb', line 34

def complete?
  @complete
end

#fill!(subexpr, value) ⇒ Object

Fills the hole corresponding to subexpr in the receiving Frame with value. When a hole is filled, the @current pointer is moved to the next subexpression that needs evaluating.



97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/heist/runtime/frame.rb', line 97

def fill!(subexpr, value)
  value = value.value if Value === value and @values and
                         not (Syntax === @values.car)
  
  epair, vpair = @expression, @values
  while Cons === epair and not epair.null?
    if epair.car.equal?(subexpr)
      vpair.car = value
      @current = epair.cdr
    end
    epair, vpair = epair.cdr, vpair.cdr
  end
end

#process!Object

Processes a single subexpression from the Frame and returns the result. If all the required subexpressions have been evaluated, we call the resulting function with the arguments and return the result of that call.



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
# File 'lib/heist/runtime/frame.rb', line 42

def process!
  case @expression
  
    when Cons then
      # If we have evaluated all the subexpressions, or we have a
      # +Syntax+ value first in the list, we can make a function call
      # and return its result.
      function = @values.car
      if Syntax === function or @current.null?
        @complete = true
        raise SyntaxError.new("Invalid expression: #{@expression}") unless Function === function
        result = function.call(@scope, @values.cdr)
        
        # If the result of the call is a macro expansion, inline it
        # and set its expression as the new expression for the frame
        return result unless Macro::Expansion === result
        return reset!(result.expression, true)
      end
      
      # Otherwise, we still have work to do on the subexpressions, so
      # we evaluate the next one in the list.
      stack = @scope.runtime.stack
      stack << Frame.new(@current.car, @scope)
  
    # If it's a Vector (it will be unquoted if it has arrived here, copy
    # it and return it. We cannot freeze it for fear of breaking macros,
    # and we must copy it so that vector-set! cannot modify the parse tree.
    when Vector then
      @complete = true
      @expression.dup
  
    # If the expression is an +Identifier+, just look it up.
    when Identifier then
      @complete = true
      @scope[@expression]
  
    # Otherwise, the expression is just data so return it.
    else
      @complete = true
      Heist.evaluate(@expression, @scope)
  end
end

#replaces(expression) ⇒ Object

Sets the replacement target for the receiving Frame to the given expression. When the receiving frame returns, it will fill the hole corresponding to the expression in the previous Frame on the Stack.



115
116
117
# File 'lib/heist/runtime/frame.rb', line 115

def replaces(expression)
  @target = expression
end

#targetObject

Returns the Expression that is the replacement target for the previous Frame on the Stack. When the receiving Frame completes, the returned value will replace the return value of this method in the previous Frame. The target of a Frame is its @expression by default, but tail calls require us to change the target as, when a tail call returns, the Frame that originated it will no longer by on the stack so replaces is used to change the replacement target for the tail call.



127
128
129
# File 'lib/heist/runtime/frame.rb', line 127

def target
  @target || @expression
end