Class: Lisp::Frame

Inherits:
Atom show all
Defined in:
lib/rubylisp/frame.rb

Instance Attribute Summary

Attributes inherited from Atom

#value

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Atom

#alist?, #all?, #apply_to, #character?, #class?, #copy, #doc, #evaluate, #false?, #function?, #list?, #macro?, #negative?, #number?, #object?, #pair?, #positive?, #primitive?, #print_string, #quoted, #set!, #special?, #string?, #symbol?, #true?, #vector?, #zero?

Constructor Details

#initialize(m = nil) ⇒ Frame



169
170
171
# File 'lib/rubylisp/frame.rb', line 169

def initialize(m=nil)
  @value = m || {}
end

Class Method Details

.clone_impl(args, env) ⇒ Object



151
152
153
154
155
# File 'lib/rubylisp/frame.rb', line 151

def self.clone_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  frame.clone
end

.get_slot_if_impl(args, env) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/rubylisp/frame.rb', line 85

def self.get_slot_if_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  key = args.cadr.evaluate(env)
  return Lisp::Debug.process_error("Frame key (#{key.to_s}) must be a symbol but was #{key.type}.", env) unless key.symbol?
  frame.get(key)
end

.get_slot_impl(args, env) ⇒ Object



75
76
77
78
79
80
81
82
# File 'lib/rubylisp/frame.rb', line 75

def self.get_slot_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  key = args.cadr.evaluate(env)
  return Lisp::Debug.process_error("Frame key (#{key.to_s}) must be a symbol but was #{key.type}.", env) unless key.symbol?
  return Lisp::Debug.process_error("Frame key (#{key.to_s}) must name an existing slot.", env) unless frame.has_slot?(key)
  frame.get(key)
end

.get_super_function(selector, env) ⇒ Object



128
129
130
131
132
133
134
135
136
# File 'lib/rubylisp/frame.rb', line 128

def self.get_super_function(selector, env)
  f = env.frame
  return nil if f.nil?
  f.parents.each do |p|
    func = p.get(selector)
    return func unless func.nil?
  end
  nil
end

.has_slot_impl(args, env) ⇒ Object



65
66
67
68
69
70
71
72
# File 'lib/rubylisp/frame.rb', line 65

def self.has_slot_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  key = args.cadr.evaluate(env)
  return Lisp::Debug.process_error("Frame key must be a symbol but was #{key.type}.", env) unless key.symbol?
  return Lisp::TRUE if frame.has_slot?(key)
  Lisp::FALSE
end

.keys_impl(args, env) ⇒ Object



158
159
160
161
162
# File 'lib/rubylisp/frame.rb', line 158

def self.keys_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  ConsCell.array_to_list(frame.value.keys)
end

.make_frame_impl(args, env) ⇒ Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/rubylisp/frame.rb', line 49

def self.make_frame_impl(args, env)
  c = args
  m = {}
  while !c.nil?
    k = c.car
    return Lisp::Debug.process_error("Slot names must be a symbol, found a {k.type}.", env) unless k.symbol?
    return Lisp::Debug.process_error("Slot names must end in a colon, found '#{k}'.", env) unless k.naked?
    v = c.cadr.evaluate(env)
    m[k] = v
    c = c.cddr
  end

  Lisp::Frame.with_map(m)
end

.registerObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/rubylisp/frame.rb', line 5

def self.register
  Primitive.register("make-frame", "(make-frame slot-name slot-value ... )\n\nFrames can be created using the make-frame function, passing it an alternating sequence of slot names and values:\n\n        (make-frame a: 1 b: 2)\n\nThis results in a frame with two slots, named a: and b: with values 1 and 2, respectively.") do |args, env|
    Lisp::Frame::make_frame_impl(args, env)
  end
  
  Primitive.register("has-slot?", "(has-slot? frame slot-name)\n\nThe has-slot? function is used to query whether a frame contains (directly or in an ancestor) the particular slot.") do |args, env|
    Lisp::Frame::has_slot_impl(args, env)
  end
  
  Primitive.register("get-slot", "(get-slot _frame_ _slot-name_)\n\nThe get-slot function is used to retrieve values from frame slots") do |args, env|
    Lisp::Frame::get_slot_impl(args, env)
  end
  
  Primitive.register("get-slot-if", "(get-slot-if frame slot-name)\n\nThe same as above, except that if a matching slot is not found, nil is returned instead of raising an error.") do |args, env|
    Lisp::Frame::get_slot_if_impl(args, env)
  end
  
  Primitive.register("remove-slot!", "(remove-slot! frame slot-name)\n\nThe remove-slot! function is used to function is used to remove a slot from a frame. It only removes slots from the frame itself. not any of it's parents. remove-slot! return #t if the slot was removed, #f otherwise.") do |args, env|
    Lisp::Frame::remove_slot_impl(args, env)
  end
  
  Primitive.register("set-slot!", "(set-slot! frame slot-name new-value)\n\nThe set-slot! function is used to change values in frame slots") do |args, env|
    Lisp::Frame::set_slot_impl(args, env)
  end
  
  Primitive.register("send", "(send frame slot-name arg...)\n\nSend the message slot-name to frame, passing along the arg collection. The result is what is returned by the code in that slot.") do |args, env|
    Lisp::Frame::send_impl(args, env)
  end
  
  Primitive.register("send-super", "**(send-super slot-name arg...)\n\nLike send, but sends to the first parent that has the named slot. send-super can only be used from within a frame.") do |args, env|
    Lisp::Frame::send_super_impl(args, env)
  end
  
  Primitive.register("clone", "(clone frame)\n\nFrames represent things. For example, you could use a frame that looks like {x: 1 y: 10} to represent a point. A system that would use point frames will typically need many independant points. The approach to this is to create a prototypical point data frame, and use the clone function to create individual, independant frames.") do |args, env|
    Lisp::Frame::clone_impl(args, env)
  end
  
  Primitive.register("keys", "(keys frame)\n\nReturn a list of the keys in the frame.") do |args, env|
    Lisp::Frame::keys_impl(args, env)
  end
  
end

.remove_slot_impl(args, env) ⇒ Object



94
95
96
97
98
99
100
101
# File 'lib/rubylisp/frame.rb', line 94

def self.remove_slot_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  key = args.cadr.evaluate(env)
  return Lisp::Debug.process_error("Frame key (#{key.to_s}) must be a symbol but was #{key.type}.", env) unless key.symbol?
  return Lisp::TRUE if frame.remove(key)
  Lisp::FALSE
end

.send_impl(args, env) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/rubylisp/frame.rb', line 114

def self.send_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  selector = args.cadr.evaluate(env)
  return Lisp::Debug.process_error("Selector must be a symbol but was #{selector.type}.", env) unless selector.symbol?
  return Lisp::Debug.process_error("Message sent must name an existing slot in the receiver.", env) unless frame.has_slot?(selector)
  func = frame.get(selector)
  return Lisp::Debug.process_error("Message sent must select a function slot but was #{func.type}.", env) unless func.function?
  params = args.cddr
  frame_env = Lisp::EnvironmentFrame.extending(env, frame)
  frame_env.bind_locally(Symbol.named("self"), frame)
  func.apply_to(params, frame_env)
end

.send_super_impl(args, env) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/rubylisp/frame.rb', line 138

def self.send_super_impl(args, env)
  return Lisp::Debug.process_error("super can only be used within the context of a frame.", env) unless env.frame
  selector = args.car.evaluate(env)
  return Lisp::Debug.process_error("Selector must be a symbol but was #{selector.type}.", env) unless selector.symbol?
  func = get_super_function(selector, env)
  return Lisp::Debug.process_error("Message sent must select a function slot but was #{func.type}.", env) unless func && func.function?
  params = args.cdr
  frame_env = Lisp::EnvironmentFrame.extending(env, env.frame)
  frame_env.bind_locally(Symbol.named("self"), env.frame)
  func.apply_to(params, frame_env)
end

.set_slot_impl(args, env) ⇒ Object



104
105
106
107
108
109
110
111
# File 'lib/rubylisp/frame.rb', line 104

def self.set_slot_impl(args, env)
  frame = args.car.evaluate(env)
  return Lisp::Debug.process_error("Frame data must be a frame but was #{frame.type}.", env) unless frame.frame?
  key = args.cadr.evaluate(env)
  return Lisp::Debug.process_error("Frame key (#{key.to_s}) must be a symbol but was #{key.type}.", env) unless key.symbol?
  value = args.caddr.evaluate(env)
  frame.at_put(key, value)
end

.with_map(m) ⇒ Object



165
166
167
# File 'lib/rubylisp/frame.rb', line 165

def self.with_map(m)
  self.new(m)
end

Instance Method Details

#at_put(key, value) ⇒ Object



255
256
257
# File 'lib/rubylisp/frame.rb', line 255

def at_put(key, value)
  return @value[key] = value
end

#carObject



280
281
282
# File 'lib/rubylisp/frame.rb', line 280

def car
  nil
end

#cdrObject



284
285
286
# File 'lib/rubylisp/frame.rb', line 284

def cdr
  nil
end

#cloneObject



174
175
176
# File 'lib/rubylisp/frame.rb', line 174

def clone
  Lisp::Frame.with_map(@value.clone)
end

#empty?Boolean



268
269
270
# File 'lib/rubylisp/frame.rb', line 268

def empty?
  @value.empty?
end

#eq?(other) ⇒ Boolean



288
289
290
291
292
293
294
295
# File 'lib/rubylisp/frame.rb', line 288

def eq?(other)
  return false unless other.frame?
  return false unless @value.length == other.value.length
  @value.each do |k, v|
    return false unless Lisp::Equivalence.equal_check(other.value[k], v).value
  end
  true
end

#frame?Boolean



272
273
274
# File 'lib/rubylisp/frame.rb', line 272

def frame?
  true
end

#get(key) ⇒ Object



243
244
245
# File 'lib/rubylisp/frame.rb', line 243

def get(key)
  get_helper(key, Set.new)
end

#get_helper(key, v) ⇒ Object



231
232
233
234
235
236
237
238
239
240
# File 'lib/rubylisp/frame.rb', line 231

def get_helper(key, v)
  return nil if v.include?(self)
  v << self
  return @value[key] if has_slot_locally?(key)
  parents.each do |p|
    value = p.get_helper(key, v)
    return value unless value.nil?
  end
  nil
end

#has_parent_slots?Boolean



197
198
199
# File 'lib/rubylisp/frame.rb', line 197

def has_parent_slots?
  @value.keys.any? {|k| is_parent_key(k)}
end

#has_slot?(n) ⇒ Boolean



226
227
228
# File 'lib/rubylisp/frame.rb', line 226

def has_slot?(n)
  has_slot_helper(n, Set.new)
end

#has_slot_helper(n, v) ⇒ Object



217
218
219
220
221
222
223
# File 'lib/rubylisp/frame.rb', line 217

def has_slot_helper(n, v)
  return false if v.include?(self)
  v << self
  return true if has_slot_locally?(n)
  return false unless has_parent_slots?
  return parents.any? {|p| p.has_slot_helper(n, v)}
end

#has_slot_locally?(n) ⇒ Boolean



212
213
214
# File 'lib/rubylisp/frame.rb', line 212

def has_slot_locally?(n)
  @value.has_key?(n)
end

#inherited_value_slotsObject



189
190
191
192
193
194
# File 'lib/rubylisp/frame.rb', line 189

def inherited_value_slots
  parent_frames = parent_slots.collect {|pk| get(pk)}
  parent_slots = parent_frames.collect {|p| p.inherited_value_slots}
  local_value_slots = Set[local_slots.reject {|s| is_parent_key(k)}]
  parent_slots.inject(local_value_slots) {|all, s| all + s} 
end

#is_parent_key(k) ⇒ Object



179
180
181
# File 'lib/rubylisp/frame.rb', line 179

def is_parent_key(k)
  k.to_s[-2] == "*"
end

#lengthObject



276
277
278
# File 'lib/rubylisp/frame.rb', line 276

def length
  return @value.length
end

#lisp_object?Boolean



260
261
262
# File 'lib/rubylisp/frame.rb', line 260

def lisp_object?
  true
end

#local_slotsObject



184
185
186
# File 'lib/rubylisp/frame.rb', line 184

def local_slots
  @value.keys
end

#parent_slotsObject



202
203
204
# File 'lib/rubylisp/frame.rb', line 202

def parent_slots
  @value.keys.select {|k| is_parent_key(k)}
end

#parentsObject



207
208
209
# File 'lib/rubylisp/frame.rb', line 207

def parents
  parent_slots.collect {|pk| @value[pk]}
end

#remove(key) ⇒ Object



248
249
250
251
252
# File 'lib/rubylisp/frame.rb', line 248

def remove(key)
  return false unless has_slot_locally?(key)
  @value.delete(key)
  true
end

#to_sObject



297
298
299
300
# File 'lib/rubylisp/frame.rb', line 297

def to_s
  pairs = @value.collect {|k, v| "#{k.to_s} #{v.to_s}"}
  "{#{pairs.join(' ')}}"
end

#typeObject



264
265
266
# File 'lib/rubylisp/frame.rb', line 264

def type
  :frame
end