ruleby 0.8.b9 → 0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,17 +14,17 @@ module Ruleby
14
14
  module Core
15
15
 
16
16
  class Atom
17
- attr_reader :tag, :proc, :method, :deftemplate
17
+ attr_reader :tag, :proc, :slot, :template
18
18
 
19
- def initialize(tag, method, deftemplate, &block)
19
+ def initialize(tag, slot, template, block)
20
20
  @tag = tag
21
- @method = method
22
- @deftemplate = deftemplate
23
- @proc = Proc.new(&block) if block_given?
21
+ @slot = slot
22
+ @template = template
23
+ @proc = block
24
24
  end
25
25
 
26
26
  def to_s
27
- return "#{self.class},#{@tag},#{@method},#{@deftemplate}"
27
+ "#{self.class},#{@tag},#{@slot},#{@template}"
28
28
  end
29
29
  end
30
30
 
@@ -34,16 +34,25 @@ module Ruleby
34
34
  # a.person{ |p| p.is_a? Person }
35
35
  #
36
36
  # So there are no references to other atoms.
37
- class PropertyAtom < Atom
37
+ class PropertyAtom < Atom
38
+
39
+ attr_reader :value
40
+
41
+ def initialize(tag, slot, template, value, block)
42
+ super(tag,slot,template, block)
43
+ @value = value
44
+ end
45
+
38
46
  def ==(atom)
39
- return shareable?(atom) && @tag == atom.tag
47
+ shareable?(atom) && @tag == atom.tag
40
48
  end
41
49
 
42
50
  def shareable?(atom)
43
- return PropertyAtom === atom &&
44
- @method == atom.method &&
45
- @deftemplate == atom.deftemplate &&
46
- @proc == atom.proc
51
+ PropertyAtom === atom &&
52
+ @slot == atom.slot &&
53
+ @template == atom.template &&
54
+ @proc == atom.proc &&
55
+ @value == atom.value
47
56
  end
48
57
  end
49
58
 
@@ -53,30 +62,21 @@ module Ruleby
53
62
 
54
63
  def initialize(tag, template, arguments, block)
55
64
  @tag = tag
56
- @method = nil
57
- @deftemplate = template
65
+ @slot = nil
66
+ @template = template
58
67
  @arguments = arguments
59
68
  @proc = block
60
69
  end
61
70
 
62
71
  def shareable?(atom)
63
72
  FunctionAtom === atom &&
64
- @deftemplate == atom.deftemplate &&
73
+ @template == atom.template &&
65
74
  @arguments == atom.arguments &&
66
75
  @proc == atom.proc
67
76
  end
68
77
 
69
78
  def to_s
70
- return "#{self.class},#{@deftemplate},#{@arguments.inspect}"
71
- end
72
- end
73
-
74
- # TODO use this
75
- class BlockAtom < PropertyAtom
76
- def shareable?(atom)
77
- return super &&
78
- BlockAtom === atom &&
79
- @proc == atom.proc
79
+ "#{self.class},#{@template},#{@arguments.inspect}"
80
80
  end
81
81
  end
82
82
 
@@ -87,16 +87,16 @@ module Ruleby
87
87
  #
88
88
  # So there are no references to other atoms.
89
89
  class EqualsAtom < PropertyAtom
90
- attr_reader :value
91
- def initialize(tag, method, deftemplate, value)
92
- super(tag,method,deftemplate)
93
- @value = value
90
+ EQUAL_PROC = lambda {|x, y| x == y}
91
+
92
+ def initialize(tag, slot, template, value)
93
+ super(tag,slot,template, value, EQUAL_PROC)
94
94
  end
95
-
95
+
96
96
  def shareable?(atom)
97
- return EqualsAtom === atom &&
98
- @method == atom.method &&
99
- @deftemplate == atom.deftemplate
97
+ EqualsAtom === atom &&
98
+ @slot == atom.slot &&
99
+ @template == atom.template
100
100
  end
101
101
  end
102
102
 
@@ -105,17 +105,20 @@ module Ruleby
105
105
  # 'For each Person as :p'
106
106
  #
107
107
  # It is only used at the start of a pattern.
108
- class HeadAtom < Atom
109
- def initialize(tag, deftemplate)
110
- if deftemplate.mode == :equals
111
- super tag, :class, deftemplate do |t| t == deftemplate.clazz end
112
- elsif deftemplate.mode == :inherits
113
- super tag, :class, deftemplate do |t| t === deftemplate.clazz end
108
+ class HeadAtom < PropertyAtom
109
+ HEAD_EQUAL_PROC = lambda {|t, c| t == c}
110
+ HEAD_INHERITS_PROC = lambda {|t, c| t === c}
111
+
112
+ def initialize(tag, template)
113
+ if template.mode == :equals
114
+ super tag, :class, template, template.clazz, HEAD_EQUAL_PROC
115
+ elsif template.mode == :inherits
116
+ super tag, :class, template, template.clazz, HEAD_INHERITS_PROC
114
117
  end
115
118
  end
116
119
 
117
120
  def shareable?(atom)
118
- return HeadAtom === atom && @deftemplate == atom.deftemplate
121
+ HeadAtom === atom && @template == atom.template
119
122
  end
120
123
  end
121
124
 
@@ -128,8 +131,8 @@ module Ruleby
128
131
  class ReferenceAtom < Atom
129
132
  attr_reader :vars
130
133
 
131
- def initialize(tag, method, vars, deftemplate, &block)
132
- super(tag, method, deftemplate, &block)
134
+ def initialize(tag, slot, vars, template, block)
135
+ super(tag, slot, template, block)
133
136
  @vars = vars # list of referenced variable names
134
137
  end
135
138
 
@@ -138,21 +141,21 @@ module Ruleby
138
141
  end
139
142
 
140
143
  def ==(atom)
141
- return ReferenceAtom === atom &&
144
+ ReferenceAtom === atom &&
142
145
  @proc == atom.proc &&
143
146
  @tag == atom.tag &&
144
147
  @vars == atom.vars &&
145
- @deftemplate == atom.deftemplate
148
+ @template == atom.template
146
149
  end
147
150
 
148
151
  def to_s
149
- return super + ", vars=#{vars.join(',')}"
152
+ super + ", vars=#{vars.join(',')}"
150
153
  end
151
154
  end
152
155
 
153
156
  # This is an atom that references another atom that is in the same pattern.
154
157
  # Note that in a SelfReferenceAtom, the 'vars' argument must be a list of the
155
- # *methods* that this atom references (not the variable names)!
158
+ # *slots* that this atom references (not the variable names)!
156
159
  class SelfReferenceAtom < ReferenceAtom
157
160
  end
158
161
 
@@ -179,4 +182,4 @@ module Ruleby
179
182
  end
180
183
 
181
184
  end
182
- end
185
+ end
@@ -222,27 +222,23 @@ module Ruleby
222
222
  end
223
223
  @atom_nodes.each {|n| return n if n.shareable? node}
224
224
  @atom_nodes.push node
225
- return node
225
+ node
226
226
  end
227
227
 
228
228
  def create_self_reference_node(atom)
229
229
  node = SelfReferenceNode.new(@bucket, atom)
230
230
  @atom_nodes.push node
231
- return node
231
+ node
232
232
  end
233
233
 
234
234
  def create_reference_node(atom)
235
235
  node = ReferenceNode.new(@bucket, atom)
236
236
  @atom_nodes.push node
237
- return node
237
+ node
238
238
  end
239
239
 
240
240
  def create_adapter_node(side)
241
- if side == :left
242
- return LeftAdapterNode.new(@bucket)
243
- else
244
- return RightAdapterNode.new(@bucket)
245
- end
241
+ side == :left ? LeftAdapterNode.new(@bucket) : RightAdapterNode.new(@bucket)
246
242
  end
247
243
 
248
244
  # This method is used to update each TypeNode based on the facts in
@@ -294,7 +290,7 @@ module Ruleby
294
290
  end
295
291
  end
296
292
  end
297
- return true
293
+ true
298
294
  end
299
295
  end
300
296
 
@@ -370,15 +366,15 @@ module Ruleby
370
366
  end
371
367
 
372
368
  def ==(node)
373
- return AtomNode === node && @atom == node.atom
369
+ AtomNode === node && @atom == node.atom
374
370
  end
375
371
 
376
372
  def shareable?(node)
377
- return @atom.shareable?(node.atom)
373
+ @atom.shareable?(node.atom)
378
374
  end
379
375
 
380
376
  def to_s
381
- super + " - #{@atom.method}"
377
+ super + " - #{@atom.slot}"
382
378
  end
383
379
  end
384
380
 
@@ -406,7 +402,7 @@ module Ruleby
406
402
  end
407
403
 
408
404
  def assert(assertable)
409
- k = assertable.fact.object.send(@atom.method)
405
+ k = assertable.fact.object.send(@atom.slot)
410
406
 
411
407
  # TODOwe need to do this for ALL tags if this node is shared
412
408
  assertable.add_tag(@atom.tag, k)
@@ -424,7 +420,7 @@ module Ruleby
424
420
  # is matched exactly (ignoring inheritance).
425
421
  class TypeNode < HashedNode
426
422
  def hash_by(atom)
427
- atom.deftemplate.clazz
423
+ atom.template.clazz
428
424
  end
429
425
 
430
426
  def retract(fact)
@@ -432,7 +428,7 @@ module Ruleby
432
428
  end
433
429
 
434
430
  def assert(fact)
435
- k = fact.object.send(@atom.method)
431
+ k = fact.object.send(@atom.slot)
436
432
  # TODO we should create the Assertion object here, not in propogate
437
433
  propagate_assert fact, (@values[k] ? @values[k] : {})
438
434
  rescue NoMethodError => e
@@ -468,8 +464,8 @@ module Ruleby
468
464
  class PropertyNode < AtomNode
469
465
  def assert(assertable)
470
466
  begin
471
- val = assertable.fact.object.send(@atom.method)
472
- assertable.add_tag(@atom.tag, val)
467
+ v = assertable.fact.object.send(@atom.slot)
468
+ assertable.add_tag(@atom.tag, v)
473
469
  rescue NoMethodError => e
474
470
  @bucket.add_error Error.new(:no_method, :warn, {
475
471
  :object => assertable.fact.object.to_s,
@@ -479,12 +475,16 @@ module Ruleby
479
475
  return
480
476
  end
481
477
  begin
482
- super if @atom.proc.call(val)
478
+ if @atom.proc.arity == 1
479
+ super if @atom.proc.call(v)
480
+ else
481
+ super if @atom.proc.call(v, @atom.value)
482
+ end
483
483
  rescue Exception => e
484
484
  @bucket.add_error Error.new(:proc_call, :error, {
485
485
  :object => assertable.fact.object.to_s,
486
- :method => @atom.method,
487
- :value => val.to_s,
486
+ :method => @atom.slot,
487
+ :value => v.to_s,
488
488
  :message => e.message
489
489
  })
490
490
  end
@@ -500,7 +500,7 @@ module Ruleby
500
500
  end
501
501
  end
502
502
 
503
- # This node class is used for matching properties of a fact.
503
+ # This node class is used conditions that are simply a function, which returns true or false.
504
504
  class FunctionNode < AtomNode
505
505
  def assert(assertable)
506
506
  begin
@@ -522,7 +522,7 @@ module Ruleby
522
522
  # invoked by the two input node.
523
523
  class ReferenceNode < AtomNode
524
524
  def match(left_context,right_fact)
525
- val = right_fact.object.send(@atom.method)
525
+ val = right_fact.object.send(@atom.slot)
526
526
  args = [val]
527
527
  match = left_context.match
528
528
  @atom.vars.each do |var|
@@ -558,7 +558,7 @@ module Ruleby
558
558
  end
559
559
 
560
560
  def match(fact)
561
- args = [fact.object.send(@atom.method)]
561
+ args = [fact.object.send(@atom.slot)]
562
562
  @atom.vars.each do |var|
563
563
  args.push fact.object.send(var)
564
564
  end
@@ -596,11 +596,11 @@ module Ruleby
596
596
  if atom == @pattern.head
597
597
  # TODO once we fix up TypeNode, we won't need this
598
598
  mr[atom.tag] = fact.object
599
- elsif !mr.key?(atom.tag) and atom.method
599
+ elsif !mr.key?(atom.tag) and atom.slot
600
600
  # this is a big hack for the sake of performance. should really do whats described below
601
601
  unless atom.tag.is_a?(GeneratedTag)
602
602
  # TODO it should be possible to get rid of this, and just capture it in the Nodes above
603
- mr[atom.tag] = fact.object.send(atom.method)
603
+ mr[atom.tag] = fact.object.send(atom.slot)
604
604
  end
605
605
  end
606
606
  end
@@ -1002,4 +1002,4 @@ module Ruleby
1002
1002
  end
1003
1003
 
1004
1004
  end
1005
- end
1005
+ end
@@ -301,7 +301,7 @@ module Ruleby
301
301
  public_instance_methods.each do |m|
302
302
  # maybe we shouldn't be undefing object_id. What are the implications? Can we make object_id a
303
303
  # pass through to the underlying object's object_id?
304
- a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__]
304
+ a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__, :object_id]
305
305
  undef_method m.to_sym unless a.include? m.to_sym
306
306
  end
307
307
 
@@ -316,7 +316,7 @@ module Ruleby
316
316
  puts args.class.to_s + ' --- ' + args.to_s
317
317
  raise 'Arguments not supported for short-hand conditions'
318
318
  end
319
- return ab
319
+ ab
320
320
  end
321
321
  end
322
322
 
@@ -361,46 +361,59 @@ module Ruleby
361
361
 
362
362
  class AtomBuilder
363
363
  attr_accessor :tag, :name, :bindings, :deftemplate, :block
364
-
364
+
365
+ EQ_PROC = lambda {|x,y| x and x == y}
366
+ GT_PROC = lambda {|x,y| x and x > y}
367
+ LT_PROC = lambda {|x,y| x and x < y}
368
+ MATCH_PROC = lambda {|x,y| x and x =~ y}
369
+ LTE_PROC = lambda {|x,y| x and x <= y}
370
+ GTE_PROC = lambda {|x,y| x and x >= y}
371
+ TRUE_PROC = lambda {|x| true}
372
+
365
373
  def initialize(method_id)
366
374
  @name = method_id
367
375
  @deftemplate = nil
368
376
  @tag = GeneratedTag.new
369
377
  @bindings = []
370
- @block = lambda {|x| true}
378
+ @block = TRUE_PROC
371
379
  @child_atom_builders = []
372
380
  end
373
381
 
374
382
  def method_missing(method_id, *args, &block)
375
383
  if method_id == :not
376
- return NotOperatorBuilder.new(@name)
384
+ NotOperatorBuilder.new(@name)
377
385
  end
378
386
  end
379
387
 
380
388
  def ==(value)
381
389
  @atom_type = :equals
382
- @value = value
383
- create_block value, lambda {|x,y| x == y}, lambda {|x| x == value}; self
390
+ create_block value, EQ_PROC
391
+ self
384
392
  end
385
393
 
386
394
  def >(value)
387
- create_block value, lambda {|x,y| x > y}, lambda {|x| x > value}; self
395
+ create_block value, GT_PROC
396
+ self
388
397
  end
389
398
 
390
399
  def <(value)
391
- create_block value, lambda {|x,y| x < y}, lambda {|x| x < value}; self
400
+ create_block value, LT_PROC
401
+ self
392
402
  end
393
403
 
394
404
  def =~(value)
395
- create_block value, lambda {|x,y| x =~ y}, lambda {|x| x =~ value}; self
405
+ create_block value, MATCH_PROC
406
+ self
396
407
  end
397
408
 
398
409
  def <=(value)
399
- create_block value, lambda {|x,y| x <= y}, lambda {|x| x <= value}; self
410
+ create_block value, LTE_PROC
411
+ self
400
412
  end
401
413
 
402
414
  def >=(value)
403
- create_block value, lambda {|x,y| x >= y}, lambda {|x| x >= value}; self
415
+ create_block value, GTE_PROC
416
+ self
404
417
  end
405
418
 
406
419
  def build_atoms(tags,methods,when_id)
@@ -414,16 +427,16 @@ module Ruleby
414
427
  if @atom_type == :equals
415
428
  return atoms << Core::EqualsAtom.new(@tag, @name, @deftemplate, @value)
416
429
  else
417
- return atoms << Core::PropertyAtom.new(@tag, @name, @deftemplate, &@block)
430
+ return atoms << Core::PropertyAtom.new(@tag, @name, @deftemplate, @value, @block)
418
431
  end
419
432
  end
420
433
 
421
434
  if references_self?(tags,when_id)
422
435
  bind_methods = @bindings.collect{ |bb| methods[bb.tag] }
423
- atoms << Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@deftemplate,&@block)
436
+ atoms << Core::SelfReferenceAtom.new(@tag,@name,bind_methods,@deftemplate,@block)
424
437
  else
425
438
  bind_tags = @bindings.collect{ |bb| bb.tag }
426
- atoms << Core::ReferenceAtom.new(@tag,@name,bind_tags,@deftemplate,&@block)
439
+ atoms << Core::ReferenceAtom.new(@tag,@name,bind_tags,@deftemplate,@block)
427
440
  end
428
441
  end
429
442
 
@@ -440,26 +453,27 @@ module Ruleby
440
453
  raise 'Binding to self and another pattern in the same condition is not yet supported.'
441
454
  end
442
455
 
443
- return ref_self > 0
456
+ ref_self > 0
444
457
  end
445
458
 
446
- def create_block(value, ref_block, basic_block)
459
+ def create_block(value, block)
460
+ @block = block
447
461
  if value && value.kind_of?(BindingBuilder)
448
462
  @bindings = [value]
449
- @block = ref_block
450
463
  elsif value && value.kind_of?(AtomBuilder)
451
464
  @child_atom_builders << value
452
465
  @bindings = [BindingBuilder.new(value.tag)]
453
- @block = ref_block
454
466
  else
455
- @block = basic_block
467
+ @value = value
456
468
  end
457
469
  end
458
470
  end
459
471
 
460
472
  class NotOperatorBuilder < AtomBuilder
473
+ NOT_PROC = lambda {|x,y| x != y}
461
474
  def ==(value)
462
- create_block value, lambda {|x,y| x != y}, lambda {|x| x != value}; self
475
+ create_block value, NOT_PROC
476
+ self
463
477
  end
464
478
  end
465
479
 
@@ -12,8 +12,6 @@
12
12
  require 'ruleby'
13
13
  require 'rule_helper'
14
14
  require 'dsl/ferrari'
15
- require 'dsl/letigre'
16
- require 'dsl/steel'
17
15
 
18
16
  module Ruleby
19
17
  class Rulebook
@@ -36,15 +34,10 @@ module Ruleby
36
34
  @engine.modify fact
37
35
  end
38
36
  def rule(*args, &block)
39
- unless args.empty?
40
- name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
41
- end
42
-
43
37
  if args.empty?
44
- # use steel DSL
45
- r = Steel::RulebookHelper.new @engine
46
- r.rule name, &block
38
+ raise 'Must provide arguments to rule'
47
39
  else
40
+ name = args[0].kind_of?(Symbol) ? args.shift : GeneratedTag.new
48
41
  i = args[0].kind_of?(Hash) ? 1 : 0
49
42
  if [Array, Ruleby::Ferrari::OrBuilder, Ruleby::Ferrari::AndBuilder].include? args[i].class
50
43
  # use ferrari DSL
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruleby
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: 4
5
- version: 0.8.b9
4
+ prerelease:
5
+ version: "0.8"
6
6
  platform: ruby
7
7
  authors:
8
8
  - Joe Kutner
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2011-06-03 00:00:00 -05:00
14
+ date: 2011-06-16 00:00:00 -05:00
15
15
  default_executable:
16
16
  dependencies: []
17
17
 
@@ -38,8 +38,6 @@ files:
38
38
  - lib/core/patterns.rb
39
39
  - lib/core/utils.rb
40
40
  - lib/dsl/ferrari.rb
41
- - lib/dsl/letigre.rb
42
- - lib/dsl/steel.rb
43
41
  - tests/test.rb
44
42
  has_rdoc: true
45
43
  homepage: http://ruleby.org
@@ -59,9 +57,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
59
57
  required_rubygems_version: !ruby/object:Gem::Requirement
60
58
  none: false
61
59
  requirements:
62
- - - ">"
60
+ - - ">="
63
61
  - !ruby/object:Gem::Version
64
- version: 1.3.1
62
+ version: "0"
65
63
  requirements: []
66
64
 
67
65
  rubyforge_project: ruleby
@@ -1,238 +0,0 @@
1
- # This file is part of the Ruleby project (http://ruleby.org)
2
- #
3
- # This application is free software; you can redistribute it and/or
4
- # modify it under the terms of the Ruby license defined in the
5
- # LICENSE.txt file.
6
- #
7
- # Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
8
- #
9
- # * Authors: Joe Kutner
10
- #
11
-
12
- module Ruleby
13
- module LeTigre
14
- class RulebookHelper
15
-
16
- def initialize(engine, rulebook)
17
- @rulebook = rulebook
18
- @engine = engine
19
- end
20
-
21
- attr_reader :engine
22
-
23
- def rule(name, *args, &then_block)
24
- if args.empty?
25
- raise 'No conditions supplied.'
26
- end
27
-
28
- options = args[0].kind_of?(Hash) ? args.shift : {}
29
-
30
- pb = PatternParser.new @rulebook
31
- pattern = pb.parse args
32
-
33
- rb = RuleBuilder.new name
34
-
35
- rb.when(pattern)
36
- rb.then(&then_block)
37
- rb.priority = options[:priority] if options[:priority]
38
-
39
- @engine.assert_rule rb.build_rule
40
- end
41
-
42
- end
43
-
44
- class RuleBuilder
45
-
46
- def initialize(name, pattern=nil, action=nil, priority=0)
47
- @name = name
48
- @pattern = pattern
49
- @action = action
50
- @priority = priority
51
-
52
- @tags = {}
53
- @when_counter = 0
54
- end
55
-
56
- def when(pattern)
57
- @pattern = pattern
58
- end
59
-
60
- def then(&block)
61
- @action = Core::Action.new(&block)
62
- @action.name = @name
63
- @action.priority = @priority
64
- end
65
-
66
- def priority
67
- return @priority
68
- end
69
-
70
- def priority=(p)
71
- @priority = p
72
- @action.priority = @priority
73
- end
74
-
75
- def build_rule
76
- Core::Rule.new @name, @pattern, @action, @priority
77
- end
78
- end
79
-
80
- class PatternParser
81
- @@head_error = 'Invalid type specification.'
82
- @@method_error = "No #method in expression: "
83
-
84
- @@base_re = /(For each|\w*\s*exists\??|not\??)(.*)/
85
- @@mode_re = /( (is a|kind of|instance of) )(.*)/
86
- @@where_re = /(.*)where (.*)/
87
- @@head_re = /(\w*)( as :(.*))?/
88
-
89
- @@method_re = /#((\w|\d|\_)*)\??/
90
- @@bind_re = /#:(\w*|\d*\_*)*/
91
- @@and_re = /#&&/
92
- @@tag_re = /(.*) as :(.*)/
93
-
94
- def initialize(rulebook)
95
- @rulebook = rulebook
96
- end
97
-
98
- def parse(lhs_strs)
99
- pattern = nil
100
- lhs_strs.each do |lhs|
101
- # match the quantifier
102
- if lhs =~ @@base_re
103
- base = $1
104
- tail = $2
105
- else
106
- base = 'For each'
107
- tail = lhs
108
- end
109
-
110
- raise 'The \'exists\' quantifier is not yet supported.' if base =~ /exists/
111
-
112
- if tail =~ @@mode_re
113
- mode = :inherits
114
- tail = $3
115
- else
116
- mode = :equals
117
- end
118
-
119
- # check if there is a where clause
120
- if tail =~ @@where_re
121
- head = $1.strip
122
- tail = $2
123
- else
124
- head = tail.strip
125
- tail = nil
126
- end
127
-
128
- # match the class type and tag
129
- if head != ''
130
- head =~ @@head_re
131
- clazz = @rulebook.__eval__ $1
132
- tag = $3 ? $3.to_sym : GeneratedTag.new
133
- else
134
- clazz = Object
135
- tag = GeneratedTag.new
136
- mode = :inherits
137
- end
138
-
139
- deftemplate = Core::Template.new clazz, mode
140
- head = Core::HeadAtom.new tag, deftemplate
141
-
142
- atoms = []
143
- atom_strs = tail ? tail.split(@@and_re) : []
144
- atom_strs.each do |a|
145
- # BUG we also need to pass in the head_tag with atoms!
146
- atoms.push parse_atom(a, deftemplate, atoms)
147
- end
148
-
149
- if base =~ /not\??/
150
- p = mode==:inherits ? Core::NotInheritsPattern.new(head, atoms) :
151
- Core::NotPattern.new(head, atoms)
152
- else
153
- p = mode==:inherits ? Core::InheritsPattern.new(head, atoms) :
154
- Core::ObjectPattern.new(head, atoms)
155
- end
156
-
157
- pattern = pattern ? Core::AndPattern.new(pattern, p) : p
158
- end
159
- return pattern
160
- end
161
-
162
- private
163
- def parse_atom(str, deftemplate, atoms)
164
- expression, tag = nil, nil
165
- if str =~ @@tag_re
166
- expression, tag = $1, $2.strip.to_sym
167
- else
168
- expression, tag = str, GeneratedTag.new
169
- end
170
-
171
- bindings = []
172
- uniq_binds = []
173
- expression.scan(@@bind_re).each do |b|
174
- # HACK how can we create a truely unique variable name?
175
- uniq_bind = "ruleby_unique_variable_name_#{b[0]}"
176
- uniq_binds.push uniq_bind
177
- expression.sub!(/#:#{b[0]}/, uniq_bind)
178
- bindings.push b[0].strip.to_sym
179
- end
180
-
181
- raise @@method_error + expression unless expression =~ @@method_re
182
- method = $1
183
- expression.gsub!(/##{method}/, method)
184
- expression = "true" if expression.strip == method
185
-
186
- proc = "lambda {|#{method}"
187
-
188
- uniq_binds.each do |b|
189
- # TODO make sure 'b' is not equal to 'method' or other b's
190
- proc += ",#{b}"
191
- end
192
-
193
- proc += "| #{expression} }"
194
-
195
- block = eval proc
196
-
197
- if bindings.empty?
198
- return Core::PropertyAtom.new(tag, method, deftemplate, &block)
199
- elsif references_self?(bindings, atoms)
200
- bound_methods = resolve_bindings(bindings, atoms)
201
- return Core::SelfReferenceAtom.new(tag, method, bound_methods, deftemplate, &block)
202
- else
203
- return Core::ReferenceAtom.new(tag, method, bindings, deftemplate, &block)
204
- end
205
- end
206
-
207
- def references_self?(bindings, atoms)
208
- ref_self = 0
209
- bindings.each do |b|
210
- atoms.each do |a|
211
- if (a.tag == b)
212
- ref_self += 1
213
- end
214
- end
215
- end
216
-
217
- if ref_self > 0 and ref_self != bindings.size
218
- raise 'Binding to self and another pattern in the same expression is not yet supported.'
219
- end
220
-
221
- return ref_self > 0
222
- end
223
-
224
- def resolve_bindings(bindings, atoms)
225
- bound_methods = []
226
- bindings.each do |b|
227
- atoms.each do |a|
228
- if a.tag == b
229
- bound_methods.push a.method
230
- end
231
- end
232
- end
233
- return bound_methods
234
- end
235
-
236
- end
237
- end
238
- end
@@ -1,314 +0,0 @@
1
- # This file is part of the Ruleby project (http://ruleby.org)
2
- #
3
- # This application is free software; you can redistribute it and/or
4
- # modify it under the terms of the Ruby license defined in the
5
- # LICENSE.txt file.
6
- #
7
- # Copyright (c) 2007 Joe Kutner and Matt Smith. All rights reserved.
8
- #
9
- # * Authors: Matt Smith
10
- #
11
-
12
- module Ruleby
13
- module Steel
14
- class RulebookHelper
15
-
16
- include Ruleby
17
- def initialize(engine)
18
- raise 'This DSL is deprecated'
19
- @engine = engine
20
- end
21
-
22
- attr_reader :engine
23
-
24
- def rule(name, &block)
25
- r = Steel::RuleBuilder.new name
26
- yield r if block_given?
27
- @engine.assert_rule r.build_rule
28
- r
29
- end
30
-
31
- end
32
-
33
- class RuleBuilder
34
-
35
- def initialize(name, pattern=nil, action=nil, priority=0)
36
- @name = name
37
- @pattern = pattern
38
- @action = action
39
- @priority = priority
40
- end
41
-
42
- def when(&block)
43
- wb = WhenBuilder.new
44
- yield wb
45
- @pattern = wb.pattern
46
- end
47
-
48
- def then(&block)
49
- @action = Core::Action.new(&block)
50
- @action.name = @name
51
- @action.priority = @priority
52
- end
53
-
54
- def when=(pattern)
55
- @pattern = pattern
56
- end
57
-
58
- def then=(action)
59
- @action = action
60
- @action.name = @name
61
- @action.priority = @priority
62
- end
63
-
64
- def priority
65
- return @priority
66
- end
67
-
68
- def priority=(p)
69
- @priority = p
70
- @action.priority = @priority
71
- end
72
-
73
- def build_rule
74
- r = Ruleby::Core::Rule.new @name, @pattern, @action, @priority
75
- end
76
- end
77
-
78
- class WhenBuilder #< RulebookHelper
79
- def initialize()
80
- @pattern_hash = Hash.new
81
- @pattern_keys = []
82
- end
83
-
84
- def method_missing(method_id, *args, &block)
85
- method = method_id.to_sym
86
- wi = nil
87
- if @pattern_hash.key? method
88
- wi = @pattern_hash[method]
89
- elsif :not == method
90
- @pattern_keys.push method
91
- return self
92
- else
93
- wi = WhenInternal.new method, args[0]
94
- @pattern_hash[method] = wi
95
- @pattern_keys.push method
96
- end
97
- return wi
98
- end
99
-
100
- def pattern
101
- operands = []
102
- nt = false
103
- @pattern_keys.each do |key|
104
- if :not != key
105
- wi = @pattern_hash[key]
106
- tag = wi.tag
107
- type = wi.type
108
- atoms = wi.to_atoms
109
- p = nil
110
- if nt
111
- p = Ruleby::Core::NotPattern.new(tag, type, atoms)
112
- nt = false
113
- else
114
- p = Ruleby::Core::ObjectPattern.new(tag, type, atoms)
115
- end
116
- operands = operands + [p]
117
- else
118
- nt = true
119
- end
120
- end
121
- return and_pattern(operands)
122
- end
123
-
124
- def and_pattern(operands)
125
- # TODO raise exception if referenceAtoms from the right do not
126
- # have the values they referenece in the left
127
- # TODO raise exception if there are repeated tags?
128
- left = nil
129
- operands.each do |operand|
130
- if left.nil?
131
- left = operand
132
- else
133
- right = operand
134
- left = Ruleby::Core::AndPattern.new(left, right)
135
- end
136
- end
137
- left
138
- end
139
-
140
- def or_pattern(operands)
141
- # TODO raise exception if referenceAtoms from the right do not
142
- # have the values they referenece in the left
143
- # TODO raise exception if there are repeated tags?
144
- left = nil
145
- operands.each do |operand|
146
- if left.nil?
147
- left = operand
148
- else
149
- right = operand
150
- left = Ruleby::Core::OrPattern.new(left, right)
151
- end
152
- end
153
- left
154
- end
155
- end
156
-
157
- class WhenInternal
158
- public_instance_methods.each do |m|
159
- a = [:method_missing, :new, :public_instance_methods, :__send__, :__id__]
160
- undef_method m.to_sym unless a.include? m.to_sym
161
- end
162
-
163
- attr_reader :tag, :type
164
- def initialize(tag, type)
165
- @tag = tag
166
- @type = type
167
- @builder = WhenPropertyBuilder.new self
168
- end
169
-
170
- def to_atoms
171
- atoms = []
172
- tags = {@tag => :class}
173
- @builder.property_hash.each_value do |wp|
174
- tags[wp.tag] = wp.name if wp.tag
175
- end
176
- @builder.property_keys.each do |key|
177
- wp = @builder.property_hash[key]
178
- atoms = atoms + [wp.to_atom(tags)]
179
- end
180
- return atoms
181
- end
182
-
183
- def &
184
- return self
185
- end
186
-
187
- def method_missing(method_id, *args, &block)
188
- m = method_id.to_s
189
- suffix = m.to_s[-1..-1]
190
- if suffix == '='
191
- new_m = m[0,m.size-1]
192
- if args[0].class == Array && args[0].size > 1 && args[0][1] == :%
193
- wp = @builder.create new_m do |x,y| x == y end
194
- wp.references args[0][0]
195
- return wp
196
- else
197
- wp = @builder.create new_m do |x| x == args[0] end
198
- return wp
199
- end
200
- else
201
- wp = @builder.create(m, &block)
202
- if args.size > 0 && args[0]
203
- if block_given?
204
- wp.references args[0]
205
- else
206
- wp.tag = args[0]
207
- end
208
- end
209
- return wp
210
- end
211
- end
212
- end
213
-
214
- class WhenPropertyBuilder
215
- attr_reader:property_hash
216
- attr_reader:property_keys
217
-
218
- def initialize(parent)
219
- @parent = parent
220
- @property_hash = Hash.new
221
- @property_keys = []
222
- end
223
-
224
- def create(method_id,&block)
225
- method = method_id.to_sym
226
- wp = nil
227
- if @property_hash.key? method
228
- wp = @property_hash[method]
229
- else
230
- wp = WhenProperty.new @parent, method do |p| true end
231
- @property_hash[method] = wp
232
- @property_keys.push method
233
- end
234
- if block_given?
235
- wp.block = block
236
- end
237
- return wp
238
- end
239
- end
240
-
241
- class WhenProperty
242
-
243
- def initialize(parent,name, &block)
244
- @tag = nil
245
- @name = name
246
- @references = nil
247
- @block = block
248
- @parent = parent
249
- end
250
- attr:tag,true
251
- attr:type,true
252
- attr:value,true
253
- attr_reader:name
254
- attr_accessor:block
255
-
256
- def &
257
- return @parent
258
- end
259
-
260
- def bind(n)
261
- @tag = n
262
- end
263
-
264
- def not=(value,ref=nil)
265
- if ref && ref == :%
266
- raise 'Using \'not=\' for references is not yet supported'
267
- set_block do |x,y| x != y end
268
- references value
269
- else
270
- set_block do |s| s != value end
271
- end
272
-
273
- end
274
- def set_block(&block)
275
- @block = block
276
- end
277
- private:set_block
278
-
279
- def references(refs)
280
- @references = refs
281
- end
282
-
283
- def to_atom(pattern_tags)
284
- unless @tag
285
- @tag = GeneratedTag.new
286
- end
287
- if @references
288
- @references = [@references] unless @references.kind_of?(Array)
289
- i = includes_how_many(@references, pattern_tags.keys)
290
- if i == 0
291
- return Ruleby::Core::ReferenceAtom.new(@tag, @name, @references, @parent.type, &@block)
292
- elsif i == @references.size
293
- refs = @references.collect{|r| pattern_tags[r] }
294
- return Ruleby::Core::SelfReferenceAtom.new(@tag, @name, refs, @parent.type, &@block)
295
- else
296
- raise 'Referencing self AND other patterns in the same atom is not yet supported'
297
- end
298
- else
299
- return Ruleby::Core::PropertyAtom.new(@tag, @name, @parent.type, &@block)
300
- end
301
- end
302
-
303
- private
304
- def includes_how_many(list1, list2)
305
- i = 0
306
- list2.each do |a|
307
- i += 1 if list1.include?(a)
308
- end
309
- return i
310
- end
311
- end
312
-
313
- end
314
- end