activefacts 0.8.6 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/Manifest.txt +33 -2
  2. data/README.rdoc +30 -36
  3. data/Rakefile +16 -20
  4. data/bin/afgen +17 -11
  5. data/bin/cql +313 -36
  6. data/download.html +43 -19
  7. data/examples/CQL/Address.cql +15 -15
  8. data/examples/CQL/Blog.cql +8 -8
  9. data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
  10. data/examples/CQL/Death.cql +3 -3
  11. data/examples/CQL/Diplomacy.cql +48 -0
  12. data/examples/CQL/Genealogy.cql +41 -41
  13. data/examples/CQL/Insurance.cql +311 -0
  14. data/examples/CQL/JoinEquality.cql +35 -0
  15. data/examples/CQL/Marriage.cql +1 -1
  16. data/examples/CQL/Metamodel.cql +290 -185
  17. data/examples/CQL/MetamodelNext.cql +420 -0
  18. data/examples/CQL/Monogamy.cql +24 -0
  19. data/examples/CQL/MonthInSeason.cql +27 -0
  20. data/examples/CQL/Moon.cql +23 -0
  21. data/examples/CQL/MultiInheritance.cql +4 -4
  22. data/examples/CQL/NonRoleId.cql +14 -0
  23. data/examples/CQL/OddIdentifier.cql +18 -0
  24. data/examples/CQL/OilSupply.cql +24 -24
  25. data/examples/CQL/OneToOnes.cql +17 -0
  26. data/examples/CQL/Orienteering.cql +55 -55
  27. data/examples/CQL/OrienteeringER.cql +58 -0
  28. data/examples/CQL/PersonPlaysGame.cql +2 -2
  29. data/examples/CQL/RedundantDependency.cql +34 -0
  30. data/examples/CQL/SchoolActivities.cql +5 -5
  31. data/examples/CQL/SeparateSubtype.cql +28 -0
  32. data/examples/CQL/ServiceDirector.cql +283 -0
  33. data/examples/CQL/SimplestUnary.cql +2 -2
  34. data/examples/CQL/SubtypePI.cql +11 -11
  35. data/examples/CQL/Supervision.cql +38 -0
  36. data/examples/CQL/Tests.Test5.Load.cql +38 -0
  37. data/examples/CQL/WaiterTips.cql +33 -0
  38. data/examples/CQL/Warehousing.cql +55 -53
  39. data/examples/CQL/WindowInRoomInBldg.cql +9 -9
  40. data/examples/CQL/unit.cql +433 -544
  41. data/examples/index.html +314 -170
  42. data/examples/intro.html +6 -176
  43. data/examples/local.css +8 -4
  44. data/index.html +40 -25
  45. data/lib/activefacts/api/concept.rb +2 -2
  46. data/lib/activefacts/api/constellation.rb +4 -4
  47. data/lib/activefacts/api/instance.rb +2 -2
  48. data/lib/activefacts/api/instance_index.rb +4 -0
  49. data/lib/activefacts/api/numeric.rb +3 -1
  50. data/lib/activefacts/api/role.rb +1 -1
  51. data/lib/activefacts/api/standard_types.rb +23 -16
  52. data/lib/activefacts/api/support.rb +3 -1
  53. data/lib/activefacts/api/vocabulary.rb +4 -0
  54. data/lib/activefacts/cql/CQLParser.treetop +87 -39
  55. data/lib/activefacts/cql/Concepts.treetop +95 -69
  56. data/lib/activefacts/cql/Context.treetop +11 -2
  57. data/lib/activefacts/cql/Expressions.treetop +23 -59
  58. data/lib/activefacts/cql/FactTypes.treetop +141 -95
  59. data/lib/activefacts/cql/Language/English.treetop +33 -21
  60. data/lib/activefacts/cql/LexicalRules.treetop +6 -1
  61. data/lib/activefacts/cql/Terms.treetop +75 -26
  62. data/lib/activefacts/cql/ValueTypes.treetop +52 -54
  63. data/lib/activefacts/cql/compiler.rb +46 -1691
  64. data/lib/activefacts/cql/compiler/constraint.rb +602 -0
  65. data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
  66. data/lib/activefacts/cql/compiler/fact.rb +300 -0
  67. data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
  68. data/lib/activefacts/cql/compiler/reading.rb +832 -0
  69. data/lib/activefacts/cql/compiler/shared.rb +109 -0
  70. data/lib/activefacts/cql/compiler/value_type.rb +104 -0
  71. data/lib/activefacts/cql/parser.rb +132 -81
  72. data/lib/activefacts/generate/cql.rb +397 -274
  73. data/lib/activefacts/generate/oo.rb +13 -12
  74. data/lib/activefacts/generate/ordered.rb +107 -117
  75. data/lib/activefacts/generate/ruby.rb +34 -38
  76. data/lib/activefacts/generate/sql/mysql.rb +62 -45
  77. data/lib/activefacts/generate/sql/server.rb +59 -42
  78. data/lib/activefacts/input/cql.rb +6 -3
  79. data/lib/activefacts/input/orm.rb +991 -557
  80. data/lib/activefacts/persistence/columns.rb +16 -12
  81. data/lib/activefacts/persistence/foreignkey.rb +7 -4
  82. data/lib/activefacts/persistence/index.rb +3 -4
  83. data/lib/activefacts/persistence/reference.rb +5 -2
  84. data/lib/activefacts/support.rb +20 -14
  85. data/lib/activefacts/version.rb +1 -1
  86. data/lib/activefacts/vocabulary.rb +1 -0
  87. data/lib/activefacts/vocabulary/extensions.rb +328 -44
  88. data/lib/activefacts/vocabulary/metamodel.rb +145 -20
  89. data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
  90. data/spec/absorption_spec.rb +4 -4
  91. data/spec/api/value_type.rb +1 -1
  92. data/spec/cql/context_spec.rb +45 -22
  93. data/spec/cql/deontic_spec.rb +88 -0
  94. data/spec/cql/matching_spec.rb +517 -0
  95. data/spec/cql/samples_spec.rb +88 -31
  96. data/spec/cql/unit_spec.rb +58 -37
  97. data/spec/cql_cql_spec.rb +12 -7
  98. data/spec/cql_mysql_spec.rb +3 -7
  99. data/spec/cql_parse_spec.rb +0 -4
  100. data/spec/cql_ruby_spec.rb +1 -4
  101. data/spec/cql_sql_spec.rb +5 -18
  102. data/spec/cql_symbol_tables_spec.rb +3 -0
  103. data/spec/cqldump_spec.rb +0 -2
  104. data/spec/helpers/array_matcher.rb +35 -0
  105. data/spec/helpers/ctrl_c_support.rb +52 -0
  106. data/spec/helpers/diff_matcher.rb +38 -0
  107. data/spec/helpers/file_matcher.rb +5 -3
  108. data/spec/helpers/string_matcher.rb +39 -0
  109. data/spec/helpers/test_parser.rb +13 -0
  110. data/spec/norma_cql_spec.rb +13 -5
  111. data/spec/norma_ruby_spec.rb +11 -3
  112. data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
  113. data/spec/norma_sql_spec.rb +11 -5
  114. data/spec/norma_tables_spec.rb +33 -29
  115. data/spec/spec_helper.rb +4 -1
  116. data/status.html +92 -23
  117. metadata +102 -36
  118. data/lib/activefacts/generate/cql/html.rb +0 -403
@@ -59,11 +59,11 @@ describe "Absorption" do
59
59
  Lodgement is where
60
60
  Claim was lodged by at most one Person;
61
61
  Lodgement was made at at most one DateTime;
62
- Person has exactly one birth-Date;
62
+ Person has exactly one birth-DateTime;
63
63
  },
64
64
  :tables => {
65
65
  "Claim" => [%w{Claim ID}, %w{Lodgement Date Time}, %w{Lodgement Person ID}],
66
- "Party" => [%w{Party ID}, %w{Person Birth Date}]
66
+ "Party" => [%w{Party ID}, %w{Person Birth Date Time}]
67
67
  }
68
68
  },
69
69
 
@@ -74,8 +74,8 @@ describe "Absorption" do
74
74
  cql = test[:cql]
75
75
  expected_tables = test[:tables]
76
76
  it "should #{should}" do
77
- @compiler = ActiveFacts::CQL::Compiler.new(cql, should)
78
- @vocabulary = @compiler.vocabulary
77
+ @compiler = ActiveFacts::CQL::Compiler.new(should)
78
+ @vocabulary = @compiler.compile(cql)
79
79
 
80
80
  # puts cql
81
81
 
@@ -100,7 +100,7 @@ describe "Value Type class definitions" do
100
100
  }
101
101
  end
102
102
 
103
- # REVISIT: role value restrictions
103
+ # REVISIT: role value constraints
104
104
 
105
105
  it "should fail on a non-ValueClass" do
106
106
  lambda{
@@ -5,67 +5,90 @@
5
5
 
6
6
  require 'activefacts/support'
7
7
  require 'activefacts/api/support'
8
- require 'activefacts/cql/parser'
8
+ require 'activefacts/cql/compiler'
9
9
 
10
10
  describe "Business Context Notes" do
11
11
  # (according_to people ',')? (because / as_opposed_to / so_that / to_avoid) discussion (',' as_agreed_by)? s
12
- Prefix = %q{
12
+ ContextNotePrefix = %q{
13
+ vocabulary Test;
13
14
  Person is written as Person;
15
+ Person is employed;
16
+ Person is unemployed;
17
+ Person is a bad credit risk;
18
+ Person is good credit risk;
19
+ Bar is written as Bar;
20
+ Baz is written as Baz;
14
21
  }
15
22
  Notes = [
16
23
  # Constraints:
17
24
  [ 'each Person occurs one time in Person is employed, Person is unemployed (because it can be no other way!);',
18
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :presence, [["Person"]], [1, 1], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"unemployed"}]]], [nil, "because", "it can be no other way!", []], []]]]
25
+ 1, 'because'
19
26
  ],
20
27
  [ 'each Person occurs one time in Person is employed, Person is unemployed (as opposed to blah!);',
21
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :presence, [["Person"]], [1, 1], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"unemployed"}]]], [nil, "as_opposed_to", " blah!", []], []]]]
28
+ 1, 'as_opposed_to'
22
29
  ],
23
30
  [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah);',
24
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", []], []]]]
31
+ 1, 'so_that'
25
32
  ],
26
33
  [ 'Person is good credit risk only if Person is employed (to avoid lending to people who can\'t repay);',
27
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :subset, [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"good"}, {:word=>"credit"}, {:word=>"risk"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]]], [nil, "to_avoid", " lending to people who can't repay", []], []]]]
34
+ 1, 'to_avoid'
28
35
  ],
29
36
  [ 'Person is good credit risk if and only if Person is employed (to avoid lending to people who can\'t repay);',
30
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :equality, [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"good"}, {:word=>"credit"}, {:word=>"risk"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]]], [nil, "to_avoid", " lending to people who can't repay", []], []]]]
37
+ 1, 'to_avoid'
31
38
  ],
39
+ [ 'Person is good credit risk if and only if Person is employed (to avoid lending to people who can\'t repay, as agreed by Jim);',
40
+ 1, 'to_avoid', nil, ['Jim']
41
+ ],
42
+ # Entity and Fact types
32
43
  # Entity and Fact types
33
44
  [ 'Foo is identified by Bar [independent] where Foo has one Bar (so that we have an id);',
34
- [["Person", [:value_type, "Person", [], [], [], [], nil]], ["Foo", [:entity_type, [], {:roles=>[["Bar"]]}, ["independent"], [[:fact_clause, [], [{:word=>"Foo"}, {:word=>"has"}, {:quantifier=>[1, 1], :quantifier_restriction=>[], :word=>"Bar"}], [nil, "so_that", " we have an id", []]]]]]]
45
+ 1, 'so_that'
35
46
  ],
36
- [ 'Foo has one Bar (so that we have an id), Bar is of one Foo (because we need that);',
37
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:fact_type, [[:fact_clause, [], [{:word=>"Foo"}, {:word=>"has"}, {:quantifier=>[1, 1], :quantifier_restriction=>[], :word=>"Bar"}], [nil, "so_that", " we have an id", []]], [:fact_clause, [], [{:word=>"Bar"}, {:word=>"is"}, {:word=>"of"}, {:quantifier=>[1, 1], :quantifier_restriction=>[], :word=>"Foo"}], [nil, "because", "we need that", []]]], []]]]
47
+ [ 'Baz has one Bar (so that we have an id), Bar is of one Baz (because we need that);',
48
+ 2
38
49
  ],
39
50
  # REVISIT: No context notes on quantifiers yet
40
51
  # As agreed by:
41
52
  [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed by Jim);',
42
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", [nil, ["Jim"]]], []]]]
53
+ 1, 'so_that', nil, ['Jim']
43
54
  ],
44
55
  # REVISIT: Populate an "as agreed by" with a date
45
- [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed on 29 March by Jim);',
46
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [nil, "so_that", " blah", ["29 March", ["Jim"]]], []]]]
56
+ [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (so that blah, as agreed on 10-04-10 by Jim);',
57
+ 1, 'so_that', nil, ['Jim'], '10-04-10'
47
58
  ],
48
59
  # According to:
49
60
  [ 'for each Person at least one of these holds: Person is employed, Person is a bad credit risk (according to jim, so that blah);',
50
- [["Person", [:value_type, "Person", [], [], [], [], nil]], [nil, [:constraint, :set, [["Person"]], [1, nil], [[[{:word=>"Person"}, {:word=>"is"}, {:word=>"employed"}]], [[{:word=>"Person"}, {:word=>"is"}, {:word=>"a"}, {:word=>"bad"}, {:word=>"credit"}, {:word=>"risk"}]]], [["jim"], "so_that", " blah", []], []]]]
61
+ 1, 'so_that', ['jim']
51
62
  ],
52
63
  ]
53
64
 
54
65
  before :each do
55
- @parser = ActiveFacts::CQL::Parser.new
66
+ @compiler = ActiveFacts::CQL::Compiler.new('Test')
56
67
  end
57
68
 
58
69
  Notes.each do |c|
59
- source, ast = *c
70
+ source, count, kind, according_to, agreed_by, agreed_date = *c
60
71
  it "should parse #{source.inspect}" do
61
- #debugger
62
- result = @parser.parse_all(Prefix+source, :definition)
63
72
 
64
- puts @parser.failure_reason unless result
73
+ result =
74
+ begin
75
+ @compiler.compile(ContextNotePrefix+source)
76
+ rescue => e
77
+ puts "#{e}:\n\t#{e.backtrace*"\n\t"}"
78
+ end
79
+ puts @compiler.failure_reason unless result
65
80
  result.should_not be_nil
66
- result.map{|d| d.value}.should == ast if ast
67
- # Uncomment this to see what should replace "nil" in the cases above:
68
- #puts result.map{|d| d.value}.inspect unless ast
81
+ constellation = @compiler.vocabulary.constellation
82
+
83
+ # constellation.ContextNote.each{|k,cn| puts "#{k.inspect} => #{cn.inspect}" }
84
+ constellation.ContextNote.size.should == (count || 1)
85
+ context_note = constellation.ContextNote.values[0]
86
+ context_note.context_note_kind.should == kind if kind
87
+ context_note.all_context_according_to.map{|cat| cat.agent.agent_name }.sort.should == according_to if according_to
88
+ # context_note.discussion.should == discussion if discussion
89
+ context_note.agreement.should_not be_nil if agreed_by || agreed_date
90
+ context_note.agreement.all_context_agreed_by.map{|cab| cab.agent.agent_name}.sort.should == agreed_by if agreed_by
91
+ context_note.agreement.date.should == Date.parse(agreed_date) if agreed_date
69
92
  end
70
93
  end
71
94
  end
@@ -0,0 +1,88 @@
1
+ #
2
+ # ActiveFacts CQL Deontic Constraints tests
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'activefacts/support'
7
+ require 'activefacts/api/support'
8
+ require 'activefacts/cql/compiler'
9
+
10
+ describe "Deontic Constraints" do
11
+ DeonticPrefix = %q{
12
+ vocabulary Test;
13
+ Person is written as Person;
14
+ Person is employed;
15
+ Person is unemployed;
16
+ Person has borrowed cash;
17
+ Person is a bad credit risk;
18
+ Nr is written as Nr;
19
+ Person is good credit risk;
20
+ Bar is written as Bar;
21
+ Baz is written as Baz;
22
+ }
23
+ Cases = [
24
+ # Constraints:
25
+ [ 'each Person occurs one time (otherwise alert fraud dept) in Person is employed, Person is unemployed;',
26
+ 'alert', 'fraud dept'
27
+ ],
28
+ [ 'for each Person at most one of these holds ( otherwise email auditors ) : Person has borrowed cash, Person is a bad credit risk;',
29
+ 'email', 'auditors'
30
+ ],
31
+ [ 'either Person has borrowed cash or Person is a bad credit risk ( otherwise email auditors );',
32
+ 'email', 'auditors'
33
+ ],
34
+ [ 'either Person has borrowed cash or Person is a bad credit risk but not both ( otherwise email auditors );',
35
+ 'email', 'auditors'
36
+ ],
37
+ [ 'Person is good credit risk only if Person is employed (otherwise consider foreclosure);',
38
+ 'consider', 'foreclosure'
39
+ ],
40
+ [ 'Person is good credit risk if and only if Person is employed (otherwise log event);',
41
+ 'log', 'event'
42
+ ],
43
+ [ 'Foo is written as Nr restricted to {1..10} (otherwise log);',
44
+ 'log', ''
45
+ ],
46
+ [ 'Foo is identified by its Nr restricted to {1..10} (otherwise log);',
47
+ 'log', ''
48
+ ],
49
+ [ 'Baz has at most one (otherwise notify security) Bar, Bar is of one Baz restricted to {1..10};',
50
+ 'notify', 'security'
51
+ ],
52
+ [ 'Baz has at most one Bar, Bar is of one Baz restricted to {1..10} (otherwise log exception);',
53
+ 'log', 'exception'
54
+ ],
55
+ ]
56
+
57
+ before :each do
58
+ @compiler = ActiveFacts::CQL::Compiler.new('Test')
59
+ end
60
+
61
+ Cases.each do |c|
62
+ source, action, agent = *c
63
+ it "should parse #{source.inspect}" do
64
+ result = @compiler.compile(DeonticPrefix+source)
65
+ puts @compiler.failure_reason unless result
66
+ result.should_not be_nil
67
+ constellation = @compiler.vocabulary.constellation
68
+
69
+ enforcements =
70
+ constellation.Constraint.values.map do |c|
71
+ c.enforcement
72
+ end.compact
73
+ enforcements.size.should == 1
74
+ enforcements[0].enforcement_code.should == action if action
75
+ if agent
76
+ if agent != ''
77
+ enforcements[0].agent.agent_name.should == agent
78
+ else
79
+ enforcements[0].agent.should be_nil
80
+ end
81
+ end
82
+
83
+ #result.map{|d| d.value}.should == ast if ast
84
+ # Uncomment this to see what should replace "nil" in the cases above:
85
+ #puts result.map{|d| d.value}.inspect unless ast
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,517 @@
1
+ #
2
+ # ActiveFacts CQL Fact Type matching tests
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ require 'activefacts/support'
7
+ require 'activefacts/api/support'
8
+ require 'activefacts/cql/compiler'
9
+ # require File.dirname(__FILE__) + '/../helpers/compiler_helper' # Can't see how to include/extend these methods correctly
10
+
11
+ describe "Fact Type Role Matching" do
12
+ MatchingPrefix = %q{
13
+ vocabulary Tests;
14
+ Boy is written as String;
15
+ Girl is written as String;
16
+ }
17
+ BaseConcepts = 3 # String, Boy, Girl
18
+
19
+ def self.SingleFact &b
20
+ lambda {|c|
21
+ real_fact_types = c.FactType.values-c.ImplicitFactType.values
22
+ real_fact_types.size.should == 1
23
+ @fact_type = real_fact_types[0]
24
+ b.call(@fact_type) if b
25
+ @fact_type
26
+ }
27
+ end
28
+
29
+ def self.FactHavingPlayers(*a, &b)
30
+ lambda {|c|
31
+ @fact_type = c.FactType.detect do |key, ft|
32
+ ft.all_role.map{|r| r.concept.name}.sort == a.sort
33
+ end
34
+ b.call(@fact_type) if b
35
+ @fact_type
36
+ }
37
+ end
38
+
39
+ def self.PresenceConstraints fact_type, &b
40
+ @presence_constraints =
41
+ fact_type.all_role.map{|r|
42
+ r.all_role_ref.map{|rr|
43
+ rr.role_sequence.all_presence_constraint.to_a
44
+ }
45
+ }.flatten.uniq
46
+ b.call(@presence_constraints) if b
47
+ @presence_constraints
48
+ end
49
+
50
+ def self.Readings fact_type, &b
51
+ @readings = fact_type.all_reading.sort_by{|r| r.ordinal}
52
+ b.call(@readings) if b
53
+ @readings
54
+ end
55
+
56
+ def self.ReadingCount n
57
+ lambda {|c|
58
+ unless @fact_type.all_reading.size == n
59
+ puts "SPEC FAILED, wrong number of readings (should be #{n}):\n\t#{
60
+ @fact_type.all_reading.map{ |r| r.expand}*"\n\t"
61
+ }"
62
+ end
63
+ @fact_type.all_reading.size.should == n
64
+ }
65
+ end
66
+
67
+ def self.PresenceConstraintCount n
68
+ lambda{ |c|
69
+ @fact_type.all_role.map{|r|
70
+ r.all_role_ref.map{|rr|
71
+ rr.role_sequence.all_presence_constraint.to_a
72
+ }
73
+ }.flatten.uniq.size.should == n
74
+ }
75
+ end
76
+
77
+ def self.ConceptCount n
78
+ lambda {|c|
79
+ @constellation = c
80
+ c.Concept.values.size.should == n
81
+ }
82
+ end
83
+
84
+ def self.Concept name, &b
85
+ lambda {|c|
86
+ @concept = c.Concept[[["Tests"], name]]
87
+ @concept.should_not == nil
88
+ b.call(@concept) if b
89
+ @concept
90
+ }
91
+ end
92
+
93
+ def self.WrittenAs name
94
+ lambda {|c|
95
+ @base_type = c.Concept[[["Tests"], name]]
96
+ @base_type.class.should == ActiveFacts::Metamodel::ValueType
97
+ @concept.class.should == ActiveFacts::Metamodel::ValueType
98
+ @concept.supertype.should == @base_type
99
+ }
100
+ end
101
+
102
+ def self.PreferredIdentifier num_roles
103
+ lambda {|c|
104
+ @preferred_identifier = @concept.preferred_identifier
105
+ @preferred_identifier.should_not == nil
106
+ @preferred_identifier.role_sequence.all_role_ref.size.should == num_roles
107
+ #@preferred_identifier.min_frequency.should == 1
108
+ @preferred_identifier.max_frequency.should == 1
109
+ @preferred_identifier.is_preferred_identifier.should == true
110
+ }
111
+ end
112
+
113
+ def self.PreferredIdentifierRolePlayedBy name, num = 0
114
+ lambda {|c|
115
+ @preferred_identifier.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}[num].role.concept.name.should == name
116
+ }
117
+ end
118
+
119
+ class BlackHole
120
+ def method_missing(m,*a,&b)
121
+ self
122
+ end
123
+ end
124
+ class PendingSilencer
125
+ def STDOUT; BlackHole.new; end
126
+ def puts; BlackHole.new; end
127
+ def p; BlackHole.new; end
128
+ end
129
+
130
+ def self.pending(msg = "TODO", &b)
131
+ lambda {|*c|
132
+ raised = nil
133
+ begin
134
+ example = b.call
135
+ eval(lambda { example.call(*c) }, BlackHole.new)
136
+ rescue => raised
137
+ end
138
+ raise Spec::Example::PendingExampleFixedError.new(msg) unless raised
139
+ raise Spec::Example::ExamplePendingError.new(msg)
140
+ }
141
+ end
142
+
143
+ def self.ReadingContainsHyphenatedWord reading_num
144
+ lambda {|c|
145
+ hyphenated_reading =
146
+ c.FactType.values[0].all_reading.select {|reading|
147
+ reading.ordinal == reading_num
148
+ }[0]
149
+ hyphenated_reading.should_not == nil
150
+ (hyphenated_reading.text =~ /[a-z]-[a-z]/).should_not == nil
151
+ }
152
+ end
153
+
154
+ SimpleBinaryFactTypeTests = [
155
+ [ # Simple create
156
+ %q{Girl is going out with at most one Boy; },
157
+ SingleFact() do |fact_type|
158
+ Readings(fact_type).size.should == 1
159
+ PresenceConstraints(fact_type) do |pcs|
160
+ pcs.size.should == 1
161
+ end
162
+ end
163
+ ],
164
+ [ # Create with explicit adjective
165
+ %q{Girl is going out with at most one ugly-Boy;},
166
+ SingleFact() do |fact_type|
167
+ Readings(fact_type).size.should == 1
168
+ PresenceConstraints(fact_type).size.should == 1
169
+ end
170
+ ],
171
+ [ # Simple match
172
+ %q{Girl is going out with at most one Boy; },
173
+ %q{
174
+ Girl is going out with Boy,
175
+ Boy is going out with Girl;
176
+ },
177
+ SingleFact() do |fact_type|
178
+ Readings(fact_type).size.should == 2
179
+ PresenceConstraints(fact_type).size.should == 1
180
+ end
181
+ ],
182
+ [ # Simple match with repetition
183
+ %q{Girl is going out with at most one Boy; },
184
+ %q{
185
+ Girl is going out with Boy,
186
+ Girl is going out with Boy,
187
+ Boy is going out with Girl,
188
+ Boy is going out with Girl;
189
+ },
190
+ SingleFact() do |fact_type|
191
+ PresenceConstraints(fact_type).size.should == 1
192
+ pending("duplicate new clauses are not eliminated") do
193
+ Readings(fact_type).size.should == 2
194
+ end.call # Must call the pending block
195
+ end,
196
+ # pending("duplicate new clauses are not eliminated") do
197
+ # @readings.size.should == 2
198
+ # end
199
+ ],
200
+ [ # Simple match with a new presence Constraint
201
+ %q{Girl is going out with at most one Boy; },
202
+ %q{
203
+ Girl is going out with Boy,
204
+ Boy is going out with at most one Girl;
205
+ },
206
+ SingleFact() do |fact_type|
207
+ Readings(fact_type).size.should == 2
208
+ PresenceConstraints(fact_type).size.should == 2
209
+ end
210
+ ],
211
+ [ # RoleName matching
212
+ %q{Girl is going out with at most one Boy;},
213
+ %q{
214
+ Boy is going out with Girlfriend,
215
+ Girl (as Girlfriend) is going out with at most one Boy;
216
+ },
217
+ SingleFact() do |fact_type|
218
+ Readings(fact_type).size.should == 3
219
+ PresenceConstraints(fact_type).size.should == 1
220
+ end
221
+ ],
222
+ [ # Match with explicit adjective
223
+ %q{Girl is going out with at most one ugly-Boy;},
224
+ %q{Girl is going out with at most one ugly-Boy,
225
+ ugly-Boy is best friend of Girl;
226
+ },
227
+ SingleFact() do |fact_type|
228
+ Readings(fact_type).size.should == 2
229
+ PresenceConstraints(fact_type).size.should == 1
230
+ end
231
+ ],
232
+ [ # Match with implicit adjective
233
+ %q{Girl is going out with at most one ugly-Boy;},
234
+ %q{Girl is going out with ugly Boy,
235
+ Boy is going out with Girl;
236
+ },
237
+ SingleFact() do |fact_type|
238
+ Readings(fact_type).size.should == 2
239
+ PresenceConstraints(fact_type).size.should == 1
240
+ end
241
+ ],
242
+ [ # Match with explicit trailing adjective
243
+ %q{Girl is going out with at most one Boy-monster;},
244
+ %q{Girl is going out with Boy-monster,
245
+ Boy is going out with Girl;
246
+ },
247
+ SingleFact() do |fact_type|
248
+ Readings(fact_type).size.should == 2
249
+ PresenceConstraints(fact_type).size.should == 1
250
+ end
251
+ ],
252
+ [ # Match with implicit trailing adjective
253
+ %q{Girl is going out with at most one Boy-monster;},
254
+ %q{Girl is going out with Boy monster,
255
+ Boy is going out with Girl;
256
+ },
257
+ SingleFact() do |fact_type|
258
+ Readings(fact_type).size.should == 2
259
+ PresenceConstraints(fact_type).size.should == 1
260
+ end
261
+ ],
262
+ [ # Match with two explicit adjectives
263
+ %q{Girl is going out with at most one ugly- bad Boy;},
264
+ %q{Girl is going out with ugly- bad Boy,
265
+ ugly- bad Boy is going out with Girl;
266
+ },
267
+ SingleFact() do |fact_type|
268
+ Readings(fact_type).size.should == 2
269
+ PresenceConstraints(fact_type).size.should == 1
270
+ end
271
+ ],
272
+ [ # Match with two implicit adjective
273
+ %q{Girl is going out with at most one ugly- bad Boy;},
274
+ %q{Girl is going out with ugly bad Boy,
275
+ Boy is going out with Girl;
276
+ },
277
+ SingleFact(),
278
+ ReadingCount(2),
279
+ PresenceConstraintCount(1)
280
+ ],
281
+ [ # Match with two explicit trailing adjective
282
+ %q{Girl is going out with at most one Boy real -monster;},
283
+ %q{Girl is going out with Boy real -monster,
284
+ Boy is going out with Girl;
285
+ },
286
+ SingleFact() do |fact_type|
287
+ Readings(fact_type).size.should == 2
288
+ PresenceConstraints(fact_type).size.should == 1
289
+ end
290
+ ],
291
+ [ # Match with two implicit trailing adjectives
292
+ %q{Girl is going out with at most one Boy real -monster;},
293
+ %q{Girl is going out with Boy real monster,
294
+ Boy is going out with Girl;
295
+ },
296
+ SingleFact() do |fact_type|
297
+ Readings(fact_type).size.should == 2
298
+ PresenceConstraints(fact_type).size.should == 1
299
+ end
300
+ ],
301
+ [ # Match with hyphenated word
302
+ %q{Girl is going out with at most one Boy; },
303
+ %q{
304
+ Girl is going out with Boy,
305
+ Boy is out driving a semi-trailer with Girl;
306
+ },
307
+ SingleFact() do |fact_type|
308
+ (readings = Readings(fact_type)).size.should == 2
309
+ ## REVISIT: Refactor test
310
+ #ReadingContainsHyphenatedWord(readings[1])
311
+ ReadingContainsHyphenatedWord(1)
312
+ PresenceConstraintCount(1)
313
+ end
314
+ ],
315
+ [ # Match with implicit leading ignoring explicit trailing adjective
316
+ %q{Girl is going out with at most one ugly-Boy;},
317
+ %q{Girl is going out with ugly Boy-monster,
318
+ Boy is going out with Girl;
319
+ },
320
+ SingleFact() do |fact_type|
321
+ Readings(fact_type).size.should == 3
322
+ PresenceConstraints(fact_type).size.should == 1
323
+ end
324
+ ],
325
+ [ # Match with implicit leading ignoring implicit trailing adjective
326
+ %q{Girl is going out with at most one ugly-Boy;},
327
+ %q{Girl is going out with ugly Boy monster,
328
+ Boy-monster is going out with Girl;
329
+ },
330
+ SingleFact(),
331
+ ReadingCount(3),
332
+ PresenceConstraintCount(1)
333
+ ],
334
+ [ # Match with implicit trailing ignoring explicit leading adjective
335
+ %q{Girl is going out with at most one Boy-monster;},
336
+ %q{Girl is going out with ugly-Boy monster,
337
+ Boy is going out with Girl;
338
+ },
339
+ SingleFact() do |fact_type|
340
+ Readings(fact_type).size.should == 3
341
+ PresenceConstraints(fact_type).size.should == 1
342
+ end
343
+ ],
344
+ [ # Match with implicit trailing ignoring implicit leading adjective
345
+ %q{Girl is going out with at most one Boy-monster;},
346
+ %q{Girl is going out with ugly Boy monster,
347
+ ugly-Boy is going out with Girl;
348
+ },
349
+ SingleFact() do |fact_type|
350
+ Readings(fact_type).size.should == 3
351
+ PresenceConstraints(fact_type).size.should == 1
352
+ end
353
+ ],
354
+ ]
355
+
356
+ EntityIdentificationTests = [
357
+ [
358
+ # REVISIT: At present, this doesn't add the minimum frequency constraint that a preferred identifier requires.
359
+ %q{Thong is written as String;},
360
+ %q{Thing is identified by Thong where Thing has one Thong;},
361
+ SingleFact() do |fact_type|
362
+ Readings(fact_type).size.should == 1
363
+ PresenceConstraints(fact_type).size.should == 2
364
+ end,
365
+ Concept('Thong') do |concept|
366
+ concept.class.should == ActiveFacts::Metamodel::ValueType
367
+ # REVISIT: Figure out how WrittenAs can access the constellation.
368
+ WrittenAs('String')
369
+ end,
370
+ ConceptCount(2+BaseConcepts),
371
+ Concept('Thing'),
372
+ PreferredIdentifier(1),
373
+ PreferredIdentifierRolePlayedBy('Thong'),
374
+ ],
375
+
376
+ [ # Auto-create Id and Thing Id:
377
+ %q{Thing is identified by its Id;},
378
+ SingleFact() do |fact_type|
379
+ Readings(fact_type).size.should == 2
380
+ PresenceConstraints(fact_type).size.should == 2
381
+ end,
382
+ ConceptCount(3+BaseConcepts),
383
+ Concept('Thing'),
384
+ PreferredIdentifier(1),
385
+ PreferredIdentifierRolePlayedBy('Thing Id'),
386
+ ],
387
+
388
+ [ # Auto-create Thing Id:
389
+ %q{Id is written as String;},
390
+ %q{Thing is identified by its Id;},
391
+ SingleFact() do |fact_type|
392
+ Readings(fact_type).size.should == 2
393
+ PresenceConstraints(fact_type).size.should == 2
394
+ end,
395
+ ConceptCount(3+BaseConcepts),
396
+ Concept('Thing'),
397
+ PreferredIdentifier(1),
398
+ PreferredIdentifierRolePlayedBy('Thing Id'),
399
+ ],
400
+
401
+ [ # Auto-create nothing (identifying value type exists already)
402
+ %q{Thing Id is written as String;},
403
+ %q{Thing is identified by its Id;},
404
+ SingleFact() do |fact_type|
405
+ Readings(fact_type).size.should == 2
406
+ PresenceConstraints(fact_type).size.should == 2
407
+ end,
408
+ ConceptCount(2+BaseConcepts),
409
+ Concept('Thing'),
410
+ PreferredIdentifier(1),
411
+ PreferredIdentifierRolePlayedBy('Thing Id'),
412
+ ],
413
+
414
+ [ # Auto-create nothing (identifying entity type exists already so don't create a VT)
415
+ %q{Id is written as Id;},
416
+ %q{Thing Id is identified by Id where Thing Id has one Id, Id is of one Thing Id;},
417
+ %q{Thing is identified by its Id;},
418
+ FactHavingPlayers("Thing", "Thing Id") do |fact_type|
419
+ Readings(fact_type).size.should == 2
420
+ PresenceConstraints(fact_type).size.should == 2
421
+ end,
422
+ ConceptCount(3+BaseConcepts),
423
+ Concept('Thing'),
424
+ PreferredIdentifier(1),
425
+ PreferredIdentifierRolePlayedBy('Thing Id'),
426
+ ],
427
+
428
+ [
429
+ %q{Thong is written as String;},
430
+ %q{Thing is identified by Thong where Thing has one Thong, Thong is of one Thing;},
431
+ SingleFact() do |fact_type|
432
+ Readings(fact_type).size.should == 2
433
+ PresenceConstraints(fact_type).size.should == 2
434
+ end,
435
+ ConceptCount(2+BaseConcepts),
436
+ Concept('Thing'),
437
+ PreferredIdentifier(1),
438
+ PreferredIdentifierRolePlayedBy('Thong'),
439
+ ],
440
+
441
+ [ # Objectified fact type with internal identification
442
+ %q{Relationship is where Boy relates to Girl;},
443
+ SingleFact() do |fact_type|
444
+ Readings(fact_type).size.should == 1
445
+ PresenceConstraints(fact_type).size.should == 1
446
+ end,
447
+ ConceptCount(1+BaseConcepts),
448
+ Concept('Relationship'),
449
+ PreferredIdentifier(2),
450
+ # PreferredIdentifierRolePlayedBy('Thong'),
451
+ ],
452
+
453
+ [ # Objectified fact type with external identification
454
+ %q{Relationship is identified by its Id where Boy relates to Girl;},
455
+ ConceptCount(3+BaseConcepts),
456
+ Concept('Relationship'),
457
+ PreferredIdentifier(1), # 1 role in PI
458
+ PreferredIdentifierRolePlayedBy('Relationship Id'),
459
+ FactHavingPlayers('Relationship', 'Relationship Id') do |fact_type|
460
+ Readings(fact_type).size.should == 2
461
+ PresenceConstraints(fact_type).size.should == 2
462
+ fact_type.all_reading.detect{|r| r.text == '{0} has {1}'}.should_not == nil
463
+ fact_type.all_reading.detect{|r| r.text == '{0} is of {1}'}.should_not == nil
464
+ end,
465
+ FactHavingPlayers('Boy', 'Girl') do |fact_type|
466
+ fact_type.entity_type.should == @concept
467
+ end,
468
+ ],
469
+
470
+ [ # Objectified fact type with external identification and explicit reading
471
+ %q{Relationship is identified by its Id where Boy relates to Girl, Relationship is known by Relationship Id;},
472
+ ConceptCount(3+BaseConcepts),
473
+ Concept('Relationship'),
474
+ PreferredIdentifier(1),
475
+ PreferredIdentifierRolePlayedBy('Relationship Id'),
476
+ FactHavingPlayers('Relationship', 'Relationship Id') do |fact_type|
477
+ Readings(fact_type).size.should == 2
478
+ PresenceConstraints(fact_type).size.should == 2
479
+ fact_type.all_reading.detect{|r| r.text == '{0} is known by {1}'}.should_not == nil
480
+ fact_type.all_reading.detect{|r| r.text == '{0} is of {1}'}.should_not == nil
481
+ end,
482
+ FactHavingPlayers('Boy', 'Girl') do |fact_type|
483
+ fact_type.entity_type.should == @concept
484
+ end,
485
+ ],
486
+
487
+ ]
488
+ AllTests =
489
+ # SimpleBinaryFactTypeTests +
490
+ EntityIdentificationTests
491
+
492
+ before :each do
493
+ @compiler = ActiveFacts::CQL::Compiler.new('Test')
494
+ end
495
+
496
+ AllTests.each do |tests|
497
+ it "should process '#{(tests.select{|t| t.is_a?(String)}*' ').gsub(/\s+/m,' ')}' correctly" do
498
+ tests.each do |test|
499
+ case test
500
+ when String
501
+ result = @compiler.compile(MatchingPrefix+test)
502
+ puts @compiler.failure_reason unless result
503
+ result.should_not be_nil
504
+ when Proc
505
+ begin
506
+ test.call(@compiler.vocabulary.constellation)
507
+ rescue Spec::Example::ExamplePendingError
508
+ raise
509
+ rescue => e
510
+ puts "Failed on\n\t"+tests.select{|t| t.is_a?(String)}*" "
511
+ raise
512
+ end
513
+ end
514
+ end
515
+ end
516
+ end
517
+ end