Class: Delorean::Engine

Inherits:
Object
  • Object
show all
Defined in:
lib/delorean/engine.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(module_name, sset = nil) ⇒ Engine

Returns a new instance of Engine.



11
12
13
14
15
16
# File 'lib/delorean/engine.rb', line 11

def initialize(module_name, sset=nil)
  # name of current module
  @module_name = module_name
  @sset = sset
  reset
end

Instance Attribute Details

#comp_setObject (readonly)

Returns the value of attribute comp_set.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def comp_set
  @comp_set
end

#importsObject (readonly)

Returns the value of attribute imports.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def imports
  @imports
end

#last_nodeObject (readonly)

Returns the value of attribute last_node.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def last_node
  @last_node
end

#line_noObject (readonly)

Returns the value of attribute line_no.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def line_no
  @line_no
end

#mObject (readonly)

Returns the value of attribute m.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def m
  @m
end

#module_nameObject (readonly)

Returns the value of attribute module_name.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def module_name
  @module_name
end

#pmObject (readonly)

Returns the value of attribute pm.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def pm
  @pm
end

#ssetObject (readonly)

Returns the value of attribute sset.



8
9
10
# File 'lib/delorean/engine.rb', line 8

def sset
  @sset
end

Class Method Details

.grok_runtime_exception(exc) ⇒ Object



388
389
390
391
392
393
394
395
396
# File 'lib/delorean/engine.rb', line 388

def self.grok_runtime_exception(exc)
  # parse out the delorean-related backtrace records
  bt = exc.backtrace.map{ |x|
    x.match(/^#{MOD}(.+?):(\d+)(|:in `(.+)')$/);
    $1 && [$1, $2.to_i, $4.sub(/#{POST}$/, '')]
  }.reject(&:!)

  {"error" => exc.message, "backtrace" => bt}
end

Instance Method Details

#curr_lineObject



39
40
41
# File 'lib/delorean/engine.rb', line 39

def curr_line
  @multi_no || @line_no
end

#enumerate_attrsObject

enumerate qualified list of all attrs



315
316
317
318
319
# File 'lib/delorean/engine.rb', line 315

def enumerate_attrs
  @node_attrs.keys.each_with_object({}) { |node, h|
    h[node] = enumerate_attrs_by_node(node)
  }
end

#enumerate_attrs_by_node(node) ⇒ Object

enumerate qualified list of attrs by node



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/delorean/engine.rb', line 322

def enumerate_attrs_by_node(node)
  raise "bad node" unless node

  begin
    klass = node.is_a?(String) ? @m.module_eval(node) : node
  rescue NameError
    # FIXME: a little hacky.  Should raise an exception.
    return []
  end

  raise "bad node class #{klass}" unless klass.is_a?(Class)

  klass.methods.map(&:to_s).select { |x|
    x.end_with?(POST)
  }.map { |x|
    x.sub(/#{POST}$/, '')
  }
end

#enumerate_nodesObject

enumerate all nodes



310
311
312
# File 'lib/delorean/engine.rb', line 310

def enumerate_nodes
  SortedSet[* @node_attrs.keys]
end

#enumerate_paramsObject

enumerate all params



342
343
344
# File 'lib/delorean/engine.rb', line 342

def enumerate_params
  @param_set
end

#enumerate_params_by_node(node) ⇒ Object

enumerate params by a single node



347
348
349
350
# File 'lib/delorean/engine.rb', line 347

def enumerate_params_by_node(node)
  attrs = enumerate_attrs_by_node(node)
  Set.new( attrs.select {|a| @param_set.include?(a)} )
end

#err(exc, msg) ⇒ Object

Raises:

  • (exc)


194
195
196
# File 'lib/delorean/engine.rb', line 194

def err(exc, msg)
  raise exc.new(msg, @module_name, curr_line)
end

#eval_to_hash(node, attrs, params = {}) ⇒ Object



383
384
385
386
# File 'lib/delorean/engine.rb', line 383

def eval_to_hash(node, attrs, params={})
  res = evaluate(node, attrs, params)
  Hash[* attrs.zip(res).flatten(1)]
end

#evaluate(node, attrs, params = {}) ⇒ Object

Runtime



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/delorean/engine.rb', line 356

def evaluate(node, attrs, params={})
  raise "bad params" unless params.is_a?(Hash)

  if node.is_a?(Class)
    klass = node
  else
    raise "bad node '#{node}'" unless node =~ /^[A-Z][a-zA-Z0-9_]*$/

    begin
      klass = @m.module_eval(node)
    rescue NameError
      err(UndefinedNodeError, "node #{node} is undefined")
    end
  end

  params[:_engine] = self

  type_arr = attrs.is_a?(Array)
  attrs = [attrs] unless type_arr

  res = attrs.map { |attr|
    raise "bad attribute '#{attr}'" unless attr =~ /^[a-z][A-Za-z0-9_]*$/
    klass.send("#{attr}#{POST}".to_sym, params)
  }
  type_arr ? res : res[0]
end

#gen_import(name) ⇒ Object



58
59
60
61
62
# File 'lib/delorean/engine.rb', line 58

def gen_import(name)
  @imports.merge!(@imports[name].imports)

  @m.const_set("#{MOD}#{name}", @imports[name].m)
end

#generate(t) ⇒ Object



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/delorean/engine.rb', line 230

def generate(t)
  t.check(self)

  begin
    # generate ruby code
    gen = t.rewrite(self)
  rescue RuntimeError => exc
    err(ParseError, "codegen error: " + exc.message)
  end

  # puts gen

  begin
    # evaluate generated code in @m
    @m.module_eval(gen, "#{MOD}#{module_name}", curr_line)
  rescue => exc
    # bad ruby code generated, shoudn't happen
    err(ParseError, "codegen error: " + exc.message)
  end
end

#get_import_engine(name) ⇒ Object



64
65
66
67
# File 'lib/delorean/engine.rb', line 64

def get_import_engine(name)
  err(ParseError, "#{name} not imported") unless @imports[name]
  @imports[name]
end

#hcountObject

used in counting literal hashes



35
36
37
# File 'lib/delorean/engine.rb', line 35

def hcount
  @hcount += 1
end

#is_node_defined(name) ⇒ Object



69
70
71
# File 'lib/delorean/engine.rb', line 69

def is_node_defined(name)
  @pm.constants.member? name.to_sym
end

#parse(source) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/delorean/engine.rb', line 251

def parse(source)
  raise "can't call parse again without reset" if @pm

  # @m module is used at runtime for code evaluation.  @pm module
  # is only used during parsing to check for errors.
  @m, @pm = BaseModule.clone, Module.new

  multi_line, @multi_no = nil, nil

  source.each_line do |line|
    @line_no += 1

    # skip comments
    next if line.match(/^\s*\#/)

    # remove trailing blanks
    line.rstrip!

    next if line.length == 0

    if multi_line
      # if line starts with >4 spaces, assume it's a multline
      # continuation.
      if line =~ /\A {5}/
        multi_line += line
        next
      else
        t = parser.parse(multi_line)
        err(ParseError, "syntax error") unless t

        generate(t)
        multi_line, @multi_no = nil, nil
      end
    end

    t = parser.parse(line)

    if !t
      err(ParseError, "syntax error") unless line =~ /^\s+/

      multi_line = line
      @multi_no = @line_no
    else
      generate(t)
    end
  end

  if multi_line
    t = parser.parse(multi_line)
    err(ParseError, "syntax error") unless t
    generate(t)
  end
end

#parse_call_attr(node_name, attr_name) ⇒ Object

Parse-time check to see if attr is available. If not, error is raised.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/delorean/engine.rb', line 111

def parse_call_attr(node_name, attr_name)
  return [] if comp_set.member?(attr_name)

  # get the class associated with node
  klass = @pm.module_eval(node_name)

  # puts attr_name, "#{attr_name}#{POST}".to_sym, klass.methods.inspect

  begin
    klass.send("#{attr_name}#{POST}".to_sym, [])
  rescue NoMethodError
    err(UndefinedError, "'#{attr_name}' not defined in #{node_name}")
  end
end

#parse_call_last_node_attr(attr_name) ⇒ Object

Parse-time check to see if attr is available on current node.



127
128
129
130
# File 'lib/delorean/engine.rb', line 127

def parse_call_last_node_attr(attr_name)
  err(ParseError, "Not inside a node") unless @last_node
  parse_call_attr(@last_node, attr_name)
end

#parse_check_call_fn(fn, argcount, class_name = nil) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/delorean/engine.rb', line 198

def parse_check_call_fn(fn, argcount, class_name=nil)
  klass = case class_name
          when nil
            @m::BaseClass
          when String
            parse_class(class_name)
          else
            class_name
          end

  err(UndefinedFunctionError, "Function #{fn} not found") unless
    klass.methods.member? fn.to_sym

  # signature methods must be named FUNCTION_NAME_SIG
  sig = "#{fn}#{SIG}".upcase.to_sym

  err(UndefinedFunctionError, "Signature #{sig} not found") unless
    klass.constants.member? sig

  min, max = klass.const_get(sig)

  err(BadCallError, "Too many args to #{fn} (#{argcount} > #{max})") if
    argcount > max

  err(BadCallError, "Too few args to #{fn} (#{argcount} < #{min})") if
    argcount < min
end

#parse_check_defined_mod_node(pname, mname) ⇒ Object



89
90
91
92
# File 'lib/delorean/engine.rb', line 89

def parse_check_defined_mod_node(pname, mname)
  engine = mname ? get_import_engine(mname) : self
  engine.parse_check_defined_node(pname, true)
end

#parse_check_defined_node(name, flag) ⇒ Object

Check to see if node with given name is defined. flag tells the method about our expectation. flag=true means that we make sure that name is defined. flag=false is the opposite.



76
77
78
79
80
81
82
83
# File 'lib/delorean/engine.rb', line 76

def parse_check_defined_node(name, flag)
  isdef = is_node_defined(name)

  if isdef != flag
    isdef ? err(RedefinedError, "#{name} already defined") :
      err(UndefinedError, "#{name} not defined yet")
  end
end

#parse_class(class_name) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/delorean/engine.rb', line 179

def parse_class(class_name)
  begin
    # need the runtime module here (@m) since we need to
    # introspect methods/attrs.
    klass = @m.module_eval(class_name)
  rescue NoMethodError, NameError
    err(UndefinedError, "Can't find class: #{class_name}")
  end

  err(UndefinedError, "Access to non-class: #{class_name}") unless
    klass.instance_of?(Class)

  klass
end

#parse_define_attr(name, spec) ⇒ Object

parse-time attr definition



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
# File 'lib/delorean/engine.rb', line 146

def parse_define_attr(name, spec)
  err(ParseError, "Can't define '#{name}' outside a node") unless
    @last_node

  err(RedefinedError, "Can't redefine '#{name}' in node #{@last_node}") if
    @node_attrs[@last_node].member? name

  @node_attrs[@last_node] << name

  checks = spec.map { |a|
    n = a.index('.') ? a : "#{@last_node}.#{a}"
    "_x.member?('#{n}') ? raise('#{n}') : #{a}#{POST}(_x + ['#{n}'])"
  }.join(';')

  code =
    "class #{@last_node}; def self.#{name}#{POST}(_x); #{checks}; end; end"

  # pp code

  @pm.module_eval(code)

  begin
    parse_call_attr(@last_node, name)
  rescue RuntimeError
    err(RecursionError, "'#{name}' is recursive")
  end
end

#parse_define_node(name, pname, mname = nil) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/delorean/engine.rb', line 94

def parse_define_node(name, pname, mname=nil)
  parse_check_defined_node(name, false)
  parse_check_defined_mod_node(pname, mname) if pname

  sname = pname ? super_name(pname, mname) : 'Object'

  @pm.module_eval("class #{name} < #{sname}; end")

  # latest defined node
  @last_node = name

  # mapping of node name to list of attrs it defines
  @node_attrs[name] = []
end

#parse_define_param(name, spec) ⇒ Object



174
175
176
177
# File 'lib/delorean/engine.rb', line 174

def parse_define_param(name, spec)
  parse_define_attr(name, spec)
  @param_set.add(name)
end

#parse_define_var(var_name) ⇒ Object



132
133
134
135
136
137
138
# File 'lib/delorean/engine.rb', line 132

def parse_define_var(var_name)
  err(RedefinedError,
      "List comprehension can't redefine variable '#{var_name}'") if
    comp_set.member? var_name

  comp_set.add var_name
end

#parse_import(name) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/delorean/engine.rb', line 43

def parse_import(name)
  err(ParseError, "No script set") unless sset

  err(ParseError, "Module #{name} importing itself") if
    name == module_name

  begin
    @imports[name] = sset.get_engine(name)
  rescue => exc
    err(ImportError, exc.to_s)
  end

  @pm.const_set("#{MOD}#{name}", @imports[name].pm)
end

#parse_undef_var(var_name) ⇒ Object



140
141
142
143
# File 'lib/delorean/engine.rb', line 140

def parse_undef_var(var_name)
  err(ParseError, "internal error") unless comp_set.member? var_name
  comp_set.delete var_name
end

#parserObject



226
227
228
# File 'lib/delorean/engine.rb', line 226

def parser
  @@parser ||= DeloreanParser.new
end

#resetObject



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/delorean/engine.rb', line 18

def reset
  @m, @pm = nil, nil
  @last_node, @node_attrs = nil, {}
  @line_no, @multi_no = 0, nil

  # set of comprehension vars
  @comp_set = Set.new

  # set of all params
  @param_set = Set.new

  @imports = {}

  @hcount = 0
end

#super_name(pname, mname) ⇒ Object



85
86
87
# File 'lib/delorean/engine.rb', line 85

def super_name(pname, mname)
  mname ? "#{MOD}#{mname}::#{pname}" : pname
end