ruleby 0.8.b9 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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