activefacts 0.8.9 → 0.8.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. data/.gemtest +0 -0
  2. data/Manifest.txt +28 -33
  3. data/Rakefile +11 -12
  4. data/bin/cql +90 -46
  5. data/examples/CQL/Blog.cql +2 -1
  6. data/examples/CQL/CompanyDirectorEmployee.cql +2 -2
  7. data/examples/CQL/Death.cql +1 -1
  8. data/examples/CQL/Diplomacy.cql +9 -9
  9. data/examples/CQL/Genealogy.cql +3 -2
  10. data/examples/CQL/Insurance.cql +10 -7
  11. data/examples/CQL/JoinEquality.cql +2 -2
  12. data/examples/CQL/Marriage.cql +1 -1
  13. data/examples/CQL/Metamodel.cql +73 -53
  14. data/examples/CQL/MetamodelNext.cql +89 -67
  15. data/examples/CQL/OneToOnes.cql +2 -2
  16. data/examples/CQL/ServiceDirector.cql +10 -5
  17. data/examples/CQL/Supervision.cql +3 -3
  18. data/examples/CQL/Tests.Test5.Load.cql +1 -1
  19. data/examples/CQL/Warehousing.cql +4 -2
  20. data/lib/activefacts/cql/CQLParser.treetop +26 -60
  21. data/lib/activefacts/cql/Context.treetop +12 -2
  22. data/lib/activefacts/cql/Expressions.treetop +14 -30
  23. data/lib/activefacts/cql/FactTypes.treetop +165 -110
  24. data/lib/activefacts/cql/Language/English.treetop +167 -54
  25. data/lib/activefacts/cql/LexicalRules.treetop +16 -2
  26. data/lib/activefacts/cql/{Concepts.treetop → ObjectTypes.treetop} +36 -37
  27. data/lib/activefacts/cql/Terms.treetop +57 -27
  28. data/lib/activefacts/cql/ValueTypes.treetop +39 -13
  29. data/lib/activefacts/cql/compiler.rb +5 -3
  30. data/lib/activefacts/cql/compiler/{reading.rb → clause.rb} +407 -285
  31. data/lib/activefacts/cql/compiler/constraint.rb +178 -275
  32. data/lib/activefacts/cql/compiler/entity_type.rb +73 -64
  33. data/lib/activefacts/cql/compiler/expression.rb +418 -0
  34. data/lib/activefacts/cql/compiler/fact.rb +146 -145
  35. data/lib/activefacts/cql/compiler/fact_type.rb +197 -80
  36. data/lib/activefacts/cql/compiler/join.rb +159 -0
  37. data/lib/activefacts/cql/compiler/shared.rb +51 -23
  38. data/lib/activefacts/cql/compiler/value_type.rb +56 -2
  39. data/lib/activefacts/cql/parser.rb +15 -4
  40. data/lib/activefacts/generate/absorption.rb +7 -7
  41. data/lib/activefacts/generate/cql.rb +100 -37
  42. data/lib/activefacts/generate/oo.rb +28 -51
  43. data/lib/activefacts/generate/ordered.rb +60 -36
  44. data/lib/activefacts/generate/ruby.rb +6 -6
  45. data/lib/activefacts/generate/sql/server.rb +4 -4
  46. data/lib/activefacts/input/orm.rb +71 -53
  47. data/lib/activefacts/persistence.rb +1 -1
  48. data/lib/activefacts/persistence/columns.rb +27 -23
  49. data/lib/activefacts/persistence/foreignkey.rb +6 -6
  50. data/lib/activefacts/persistence/index.rb +17 -17
  51. data/lib/activefacts/persistence/{concept.rb → object_type.rb} +9 -9
  52. data/lib/activefacts/persistence/reference.rb +61 -36
  53. data/lib/activefacts/persistence/tables.rb +61 -59
  54. data/lib/activefacts/support.rb +54 -29
  55. data/lib/activefacts/version.rb +1 -1
  56. data/lib/activefacts/vocabulary/extensions.rb +99 -54
  57. data/lib/activefacts/vocabulary/metamodel.rb +43 -37
  58. data/lib/activefacts/vocabulary/verbaliser.rb +134 -109
  59. data/spec/absorption_spec.rb +8 -8
  60. data/spec/cql/comparison_spec.rb +91 -0
  61. data/spec/cql/contractions_spec.rb +251 -0
  62. data/spec/cql/entity_type_spec.rb +319 -0
  63. data/spec/cql/expressions_spec.rb +63 -0
  64. data/spec/cql/fact_type_matching_spec.rb +283 -0
  65. data/spec/cql/french_spec.rb +21 -0
  66. data/spec/cql/parser/bad_literals_spec.rb +86 -0
  67. data/spec/cql/parser/constraints_spec.rb +19 -0
  68. data/spec/cql/parser/entity_types_spec.rb +106 -0
  69. data/spec/cql/parser/expressions_spec.rb +179 -0
  70. data/spec/cql/parser/fact_types_spec.rb +41 -0
  71. data/spec/cql/parser/literals_spec.rb +312 -0
  72. data/spec/cql/parser/pragmas_spec.rb +89 -0
  73. data/spec/cql/parser/value_types_spec.rb +42 -0
  74. data/spec/cql/role_matching_spec.rb +147 -0
  75. data/spec/cql/samples_spec.rb +9 -9
  76. data/spec/cql_cql_spec.rb +1 -1
  77. data/spec/cql_dm_spec.rb +116 -0
  78. data/spec/cql_mysql_spec.rb +1 -1
  79. data/spec/cql_ruby_spec.rb +1 -1
  80. data/spec/cql_sql_spec.rb +3 -3
  81. data/spec/cql_symbol_tables_spec.rb +30 -30
  82. data/spec/cqldump_spec.rb +4 -4
  83. data/spec/helpers/array_matcher.rb +32 -27
  84. data/spec/helpers/diff_matcher.rb +6 -26
  85. data/spec/helpers/file_matcher.rb +41 -32
  86. data/spec/helpers/parse_to_ast_matcher.rb +76 -0
  87. data/spec/helpers/string_matcher.rb +32 -31
  88. data/spec/norma_cql_spec.rb +1 -1
  89. data/spec/norma_ruby_spec.rb +1 -1
  90. data/spec/norma_ruby_sql_spec.rb +1 -1
  91. data/spec/norma_sql_spec.rb +3 -1
  92. data/spec/norma_tables_spec.rb +1 -1
  93. data/spec/ruby_api_spec.rb +23 -0
  94. data/spec/spec_helper.rb +5 -4
  95. metadata +66 -66
  96. data/examples/CQL/OrienteeringER.cql +0 -58
  97. data/lib/activefacts/api.rb +0 -44
  98. data/lib/activefacts/api/concept.rb +0 -410
  99. data/lib/activefacts/api/constellation.rb +0 -128
  100. data/lib/activefacts/api/entity.rb +0 -256
  101. data/lib/activefacts/api/instance.rb +0 -60
  102. data/lib/activefacts/api/instance_index.rb +0 -80
  103. data/lib/activefacts/api/numeric.rb +0 -167
  104. data/lib/activefacts/api/role.rb +0 -80
  105. data/lib/activefacts/api/role_proxy.rb +0 -70
  106. data/lib/activefacts/api/role_values.rb +0 -117
  107. data/lib/activefacts/api/standard_types.rb +0 -87
  108. data/lib/activefacts/api/support.rb +0 -65
  109. data/lib/activefacts/api/value.rb +0 -135
  110. data/lib/activefacts/api/vocabulary.rb +0 -82
  111. data/spec/api/autocounter.rb +0 -82
  112. data/spec/api/constellation.rb +0 -130
  113. data/spec/api/entity_type.rb +0 -103
  114. data/spec/api/instance.rb +0 -461
  115. data/spec/api/roles.rb +0 -124
  116. data/spec/api/value_type.rb +0 -112
  117. data/spec/api_spec.rb +0 -13
  118. data/spec/cql/matching_spec.rb +0 -517
  119. data/spec/cql/unit_spec.rb +0 -394
  120. data/spec/spec.opts +0 -1
@@ -9,9 +9,9 @@ Girl ID is written as Auto Counter;
9
9
  /*
10
10
  * Entity Types
11
11
  */
12
- Boy is identified by its ID [independent];
12
+ Boy is independent identified by its ID;
13
13
 
14
- Girl is identified by its ID [independent];
14
+ Girl is independent identified by its ID;
15
15
  Girl is going out with at most one Boy,
16
16
  Boy is going out with at most one Girl;
17
17
 
@@ -29,7 +29,7 @@ Recurring Schedule Id is written as Auto Counter;
29
29
  Satellite Message Id is written as Unsigned Integer(32);
30
30
  Seconds is written as Unsigned Integer(32);
31
31
  Serial Number is written as String(20);
32
- Service Type is written as String(15) [independent];
32
+ Service Type [independent] is written as String(15);
33
33
  Subscription Nr is written as Signed Integer(32);
34
34
  Switch Id is written as Auto Counter;
35
35
  Transaction Nr is written as Unsigned Integer(32);
@@ -84,7 +84,7 @@ Monitor is identified by its Id;
84
84
  Monitor monitors one Data Store;
85
85
  Monitor is disabled;
86
86
 
87
- Monitoring Application is identified by its Name [independent];
87
+ Monitoring Application is independent identified by its Name;
88
88
  Monitor is owned by one Monitoring Application;
89
89
 
90
90
  Network is identified by its Nr;
@@ -105,13 +105,14 @@ Notification Level is identified by its Nr;
105
105
  Notification Level has one Initial- Delay Duration;
106
106
  Notification Level has one Repeat-Duration;
107
107
 
108
- Notification Type is identified by its Name [independent];
108
+ Notification Type is independent identified by its Name;
109
109
 
110
110
  Provider Type is identified by its Id;
111
111
 
112
112
  Recurring Schedule is identified by its Id;
113
113
  Monitor has All- Exclusion Recurring Schedule,
114
114
  All- Exclusion Recurring Schedule applies to at most one Monitor;
115
+ Monitor (as IntegratingMonitor) has Integration- Exclusion Recurring Schedule; // Avoid ambiguity; this is a new fact type
115
116
  Monitor (as IntegratingMonitor) has Integration- Exclusion Recurring Schedule,
116
117
  Integration- Exclusion Recurring Schedule applies to at most one IntegratingMonitor;
117
118
  Recurring Schedule has one Duration;
@@ -146,6 +147,7 @@ Switch is one Revision-Version;
146
147
  Switch has one monitoring-Port;
147
148
  Switch (as Private Interface Switch) is on private-Network,
148
149
  private-Network connects to at most one Private Interface Switch;
150
+ Switch (as Public Interface Switch) is on public-Network; // Avoid ambiguity; this is a new fact type
149
151
  Switch (as Public Interface Switch) is on at least one public-Network,
150
152
  Network connects to at most one Public Interface Switch;
151
153
  Switch is backup messages;
@@ -208,11 +210,11 @@ Data Store Service has one Subscription;
208
210
  */
209
211
  either Company is client or Company is vendor but not both;
210
212
  for each Credential exactly one of these holds:
211
- Data Store(1) requires Credential,
213
+ Data Store(2) requires Credential,
212
214
  Data Store Service requires Credential,
213
215
  Vendor requires Credential,
214
216
  Data Store File Host System has Internal-Credential,
215
- Data Store(2) has Internal-Credential;
217
+ Data Store(1) has Internal-Credential;
216
218
  for each Network exactly one of these holds:
217
219
  Network is used by Host System,
218
220
  Company has Origin-Network,
@@ -225,6 +227,9 @@ either Host System runs Switch or Data Store has Legacy-Switch but not both;
225
227
  for each Network at most one of these holds:
226
228
  Network is ip_single,
227
229
  Network has Ending-IP;
230
+ Data Store Service (where Service is from Data Store) belongs to Client
231
+ if and only if
232
+ Client has default Data Store;
228
233
  Network has Ending IP
229
234
  if and only if
230
235
  Network is ip_range;
@@ -28,12 +28,12 @@ CEO runs Company,
28
28
  /*
29
29
  * Constraints:
30
30
  */
31
- either Employee reports to Manager(1) or Employee is a Manager(2) that is a CEO that runs Company but not both;
31
+ either Employee reports to Manager(2) or Employee is a Manager(1) that is a CEO that runs Company but not both;
32
32
  Employee is a Manager that is a CEO that runs Company
33
33
  if and only if
34
34
  Employee works for Company;
35
35
 
36
36
  // This constraint cannot be expressed in NORMA until it adds explicit join paths:
37
- Employee(1) reports to Manager that is a kind of Employee(2) that works for Company
37
+ Employee(2) reports to Manager that is a kind of Employee(1) that works for Company
38
38
  if and only if
39
- Employee(1) works for Company;
39
+ Employee(2) works for Company;
@@ -17,7 +17,7 @@ Event Date is identified by ymd where
17
17
  Event Date has one ymd,
18
18
  ymd is of at most one Event Date;
19
19
 
20
- Party is identified by its Id [independent];
20
+ Party is independent identified by its Id;
21
21
 
22
22
  Party Moniker is where
23
23
  Party is called one Party Name;
@@ -72,12 +72,14 @@ Purchase Order is to one Supplier,
72
72
  Transfer Request is identified by its ID;
73
73
  Dispatch Item is for at most one Transfer Request;
74
74
  Received Item is for at most one Transfer Request;
75
+ Transfer Request is for one Product;
76
+ Transfer Request is for one Quantity;
75
77
 
76
78
  Warehouse is identified by its ID;
77
79
  Purchase Order is to one Warehouse;
78
80
  Sales Order is from one Warehouse;
79
- Transfer Request is at most one from-Warehouse;
80
- Transfer Request is at most one to-Warehouse;
81
+ Transfer Request is from one Warehouse (as From Warehouse);
82
+ Transfer Request is to one Warehouse (as To Warehouse);
81
83
  Warehouse contains at least one Bin;
82
84
 
83
85
  Customer is a kind of Party;
@@ -11,7 +11,7 @@ module ActiveFacts
11
11
  include Language # One of the language modules provides this module
12
12
  include Expressions
13
13
  include Terms
14
- include Concepts
14
+ include ObjectTypes
15
15
  include ValueTypes
16
16
  include FactTypes
17
17
  include Context
@@ -47,21 +47,27 @@ module ActiveFacts
47
47
  / prescan # Always fails, but its side-effects are needed in the following
48
48
  / constraint
49
49
  / unit_definition # REVISIT: Move this above the prescan?
50
- / concept
50
+ / object_type
51
+ / query
51
52
  / s ';' s { def ast; nil; end }
52
53
  end
53
54
 
54
55
  rule vocabulary_definition
55
- s vocabulary S id s ';'
56
+ s vocabulary S vocabulary_name s ';'
56
57
  {
57
58
  def ast
58
- Compiler::Vocabulary.new(id.value)
59
+ Compiler::Vocabulary.new(vocabulary_name.value)
59
60
  end
60
61
  }
61
62
  end
62
63
 
64
+ rule vocabulary_name
65
+ id
66
+ { def node_type; :vocabulary; end }
67
+ end
68
+
63
69
  rule import_definition
64
- s import S id alias_list ';'
70
+ s import S vocabulary_name alias_list ';'
65
71
  {
66
72
  def ast
67
73
  Compiler::Import.new(id.value, alias_list.value)
@@ -71,7 +77,7 @@ module ActiveFacts
71
77
 
72
78
  # REVISIT: Need a way to define equivalent readings for fact types here (and in the metamodel)
73
79
  rule alias_list
74
- ( s ',' s alias S aliased_from:id S as S alias_to:id s )*
80
+ ( s ',' s alias S aliased_from:alias_term S as S alias_to:alias_term s )*
75
81
  {
76
82
  def value
77
83
  elements.inject({}){|h, e| h[e.aliased_from.value] = e.alias_to; h }
@@ -79,6 +85,11 @@ module ActiveFacts
79
85
  }
80
86
  end
81
87
 
88
+ rule alias_term
89
+ id
90
+ { def node_type; :term; end }
91
+ end
92
+
82
93
  rule constraint
83
94
  subset_constraint /
84
95
  equality_constraint /
@@ -101,84 +112,39 @@ module ActiveFacts
101
112
 
102
113
  # presence constraint:
103
114
  rule presence_constraint
104
- s 'each' s ('combination' S)? role_list s 'occurs' s quantifier s 'time' 's'? s enforcement 'in' s
105
- readings_list s
106
- c:context_note? ';'
115
+ (each_occurs_in_clauses / either_or)
107
116
  {
108
117
  def ast
109
- context_note = c.empty? ? nil : c.ast
110
- Compiler::PresenceConstraint.new context_note, enforcement.ast, readings_list.ast, role_list.ast, quantifier.ast
118
+ Compiler::PresenceConstraint.new c, enforcement.ast, clauses_ast, role_list_ast, quantifier_ast
111
119
  end
112
120
  }
113
121
  end
114
122
 
115
123
  # set (exclusion, mandatory exclusion, complex equality) constraint
116
124
  rule set_constraint
117
- s 'for' s 'each' s role_list s quantifier s 'of' s 'these' s 'holds' s enforcement ':' s
118
- readings_list s
119
- c:context_note? ';'
125
+ (for_each_how_many / either_or_not_both)
120
126
  {
121
127
  def ast
122
- context_note = c.empty? ? nil : c.ast
123
- Compiler::SetExclusionConstraint.new context_note, enforcement.ast, readings_list.ast, role_list.ast, quantifier.ast
128
+ Compiler::SetExclusionConstraint.new c, enforcement.ast, clauses_ast, role_list_ast, quantifier_ast
124
129
  end
125
130
  }
126
- /
127
- s either? s r1:readings s or s r2:readings exclusion:(but s not s both s)? c:context_note? enforcement ';'
128
- {
129
- def ast
130
- context_note = c.empty? ? nil : c.ast
131
-
132
- if exclusion.text_value.empty?
133
- Compiler::PresenceConstraint.new context_note, enforcement.ast, [r1.ast, r2.ast], nil, Compiler::Quantifier.new(1, nil)
134
- else
135
- quantifier = Compiler::Quantifier.new(*(exclusion.text_value.empty? ? [1, nil] : [1, 1]))
136
- #quantifier = Compiler::Quantifier.new(1, 1)
137
- Compiler::SetExclusionConstraint.new context_note, enforcement.ast, [r1.ast, r2.ast], nil, quantifier
138
- end
139
- end
140
- }
141
131
  end
142
132
 
143
133
  rule subset_constraint
144
- s readings s only s if s r2:readings s
145
- c:context_note? enforcement ';'
134
+ (a_only_if_b / if_b_then_a)
146
135
  {
147
136
  def ast
148
- context_note = c.empty? ? nil : c.ast
149
- Compiler::SubsetConstraint.new context_note, enforcement.ast, [readings.ast, r2.ast]
137
+ Compiler::SubsetConstraint.new c, enforcement.ast, [clauses.ast, r2.ast]
150
138
  end
151
139
  }
152
140
  end
153
141
 
154
142
  rule equality_constraint
155
- s readings s tail:( if s and s only s if s readings s)+
156
- c:context_note? enforcement ';'
157
- {
158
- def ast
159
- context_note = c.empty? ? nil : c.ast
160
- all_readings = [readings.ast, *tail.elements.map{|e| e.readings.ast }]
161
- Compiler::SetEqualityConstraint.new context_note, enforcement.ast, all_readings
162
- end
163
- }
164
- end
165
-
166
- rule readings_list
167
- readings tail:( ',' s readings )*
168
- {
169
- def ast
170
- [readings.ast, *tail.elements.map{|e| e.readings.ast }]
171
- end
172
- }
173
- end
174
-
175
- rule readings
176
- reading s tail:( and s reading s )*
143
+ if_and_only_if
177
144
  {
178
145
  def ast
179
- readings = reading.ast
180
- tail.elements.map{|e| readings += e.reading.ast }
181
- readings
146
+ all_clauses = [clauses.ast, *tail.elements.map{|e| e.clauses.ast }]
147
+ Compiler::SetEqualityConstraint.new c, enforcement.ast, all_clauses
182
148
  end
183
149
  }
184
150
  end
@@ -29,14 +29,24 @@ module ActiveFacts
29
29
  end
30
30
 
31
31
  rule context_type
32
- because { def value; 'because'; end } /
32
+ because s { def value; 'because'; end } /
33
33
  as_opposed_to { def value; 'as_opposed_to'; end } /
34
34
  so_that { def value; 'so_that'; end } /
35
35
  to_avoid { def value; 'to_avoid'; end }
36
36
  end
37
37
 
38
+ # An enforcement action, like SMS, email, log, alarm, etc.
39
+ rule action
40
+ id
41
+ end
42
+
38
43
  rule discussion
39
- '(' discussion ')' / (!( [()] / ',' as_agreed_by) .)*
44
+ (
45
+ '(' discussion ')' / (!( [()] / ',' as_agreed_by) .)*
46
+ )
47
+ {
48
+ def node_type; :linking; end
49
+ }
40
50
  end
41
51
  end
42
52
  end
@@ -7,22 +7,6 @@
7
7
  module ActiveFacts
8
8
  module CQL
9
9
  grammar Expressions
10
- rule comparison
11
- e1:expression s
12
- comparator s e2:expression
13
- {
14
- def ast
15
- Compiler::Comparison.new comparator.text_value, e1.ast, e2.ast
16
- end
17
- }
18
- end
19
-
20
- rule comparator
21
- '<=' / '<' / '=' / '>=' / '>'
22
- # REVISIT: These words occur in readings, find alternates:
23
- # / matches / includes
24
- end
25
-
26
10
  rule expression
27
11
  sum
28
12
  end
@@ -31,7 +15,11 @@ module ActiveFacts
31
15
  t0:product s tail:( op:add_op s t1:product s )*
32
16
  {
33
17
  def ast
34
- Compiler::Sum.new(t0.ast, *tail.elements.map{|e| e.op.text_value == '-' ? Compiler::Negate.new(e.t1.ast) : e.t1.ast})
18
+ if tail.elements.empty?
19
+ t0.ast
20
+ else
21
+ Compiler::Sum.new(t0.ast, *tail.elements.map{|e| e.op.text_value == '-' ? Compiler::Negate.new(e.t1.ast) : e.t1.ast})
22
+ end
35
23
  end
36
24
  }
37
25
  end
@@ -44,7 +32,11 @@ module ActiveFacts
44
32
  f0:factor s tail:( op:mul_op s f1:factor s )*
45
33
  {
46
34
  def ast
47
- Compiler::Product.new(f0.ast, *tail.elements.map{|e| e.op.text_value != '*' ? Compiler::DivideBy.new(e.op.text_value, e.f1.ast) : e.t1.ast})
35
+ if tail.elements.empty?
36
+ f0.ast
37
+ else
38
+ Compiler::Product.new(f0.ast, *tail.elements.map{|e| e.op.text_value != '*' ? Compiler::Reciprocal.new(e.op.text_value, e.f1.ast) : e.f1.ast})
39
+ end
48
40
  end
49
41
  }
50
42
  end
@@ -61,19 +53,11 @@ module ActiveFacts
61
53
  end
62
54
 
63
55
  rule derived_variable
64
- variable:term s p:function_call*
56
+ variable:term s role_id:(role_name / subscript )?
65
57
  {
66
- def ast
67
- Compiler::FunctionCallChain.new(term.ast, *p.elements.each{|f| f.ast})
68
- end
69
- }
70
- end
71
-
72
- rule function_call
73
- '.' s func:id s param_list:( '(' s params:( p0:expression s tail:( ',' s p1:expression s )* )? ')' s )?
74
- {
75
- def ast
76
- Compiler::FunctionCall.new(func.value, *param_list.elements.map{|param| param.ast })
58
+ def ast quantifier = nil, value_constraint = nil, literal = nil, nested_clauses = nil
59
+ role_name = role_id.empty? ? nil : role_id.value
60
+ variable.ast(quantifier, nil, role_name, value_constraint, literal, nested_clauses)
77
61
  end
78
62
  }
79
63
  end
@@ -7,100 +7,93 @@
7
7
  module ActiveFacts
8
8
  module CQL
9
9
  grammar FactTypes
10
+ rule query
11
+ s joined_clauses r:returning_clause? '?'
12
+ {
13
+ def ast
14
+ Compiler::FactType.new nil, [], joined_clauses.ast, (r.empty? ? nil : r)
15
+ end
16
+ }
17
+ end
18
+
10
19
  rule named_fact_type
11
20
  s term_definition_name
12
- s is s mapping_pragmas where # REVISIT: Need a place to put mapping pragmas like [independent]
21
+ mapping_pragmas is_where
13
22
  anonymous_fact_type
14
23
  {
15
24
  def ast
16
25
  ft = anonymous_fact_type.ast
17
26
  ft.name = term_definition_name.value
27
+ pragmas = mapping_pragmas.value
28
+ pragmas << 'independent' if is_where.independent
29
+ ft.pragmas = pragmas
18
30
  ft
19
31
  end
20
32
  }
21
33
  end
22
34
 
23
35
  rule anonymous_fact_type
24
- fact_clause
25
- ftail:( (',' / and ) s fact_clause s )*
26
- ctail:( (':' / where) s c:conditions s)?
36
+ joined_clauses
37
+ ctail:( (':' / where) s a:joined_clauses s)?
27
38
  returning_clause?
28
39
  s ';'
29
40
  {
30
41
  def ast
31
- readings = fact_clause.ast
32
- ftail.elements.each{|e| readings += e.fact_clause.ast }
33
- conditions = !ctail.empty? ? ctail.c.ast : []
42
+ clauses_ast = joined_clauses.ast
43
+ conditions = !ctail.empty? ? ctail.a.ast : []
34
44
  returning = respond_to?(:returning_clause) ? returning_clause.ast : nil
35
- if (conditions.empty? && readings.detect{|r| r.includes_literals})
45
+ value_derivation = clauses_ast.detect{|r| r.is_equality_comparison}
46
+ if !value_derivation and
47
+ conditions.empty? and
48
+ clauses_ast.detect{|r| r.includes_literals}
36
49
  raise "Fact instances may not contain conditions" unless conditions.empty? && !returning
37
- Compiler::Fact.new readings
38
- elsif (readings.size == 1 &&
39
- readings[0].phrases.size == 1 &&
40
- (popname = readings[0].phrases[0]) &&
41
- !popname.is_a?(Compiler::RoleRef) &&
50
+ Compiler::Fact.new clauses_ast
51
+ elsif (clauses_ast.size == 1 &&
52
+ clauses_ast[0].phrases.size == 1 &&
53
+ (popname = clauses_ast[0].phrases[0]) &&
54
+ !popname.is_a?(Compiler::VarRef) &&
42
55
  conditions.detect{|r| r.includes_literals}
43
56
  )
44
57
  Compiler::Fact.new conditions, popname
45
58
  else
46
- Compiler::FactType.new nil, readings, conditions, returning
59
+ Compiler::FactType.new nil, clauses_ast, conditions, returning
47
60
  end
48
61
  end
49
62
  }
50
63
  end
51
64
 
52
- rule fact_clauses
53
- fact_clause
54
- ftail:( (',' / and ) s fact_clause s )*
65
+ rule joined_clauses
66
+ qualified_clauses
67
+ ftail:( conjunction:(',' / and / or ) s qualified_clauses s )*
55
68
  {
56
69
  def ast
57
- readings = fact_clause.ast
58
- ftail.elements.each{|e| readings += e.fact_clause.ast }
59
- readings
70
+ clauses_ast = qualified_clauses.ast
71
+ ftail.elements.each{|e|
72
+ conjunction = e.conjunction.text_value
73
+ # conjunction = 'and' if conjunction == ',' # ',' means AND, but disallows left-contractions
74
+ clauses_ast += e.qualified_clauses.ast(conjunction)
75
+ }
76
+ clauses_ast
60
77
  end
61
78
  }
62
79
  end
63
80
 
64
81
  rule returning_clause
65
- returning return (',' return)*
82
+ returning s return (s ',' s return)*
66
83
  end
67
84
 
68
85
  rule return
69
- by order 'REVISIT: return'
86
+ ordering_prefix? phrase+
70
87
  end
71
88
 
72
- rule conditions
73
- head:condition s tail:( (',' s / and S) next:condition s )*
89
+ rule qualified_clauses
90
+ s q:qualifier? s contracted_clauses s p:post_qualifiers? s c:context_note?
74
91
  {
75
- def ast
76
- conditions = head.ast
77
- tail.elements.each{|i| conditions += i.next.ast}
78
- conditions
79
- end
80
- }
81
- end
82
-
83
- rule condition
84
- head:clause s # tail:(or S alternate:clause s )*
85
- {
86
- def ast
87
- head.ast
88
- # Compiler::Alternates.new(head.ast, *tail.elements.map{|i| i.alternate.ast})
89
- # REVISIT: alternate conditions are not yet implemented
90
- end
91
- }
92
- end
93
-
94
- rule clause
95
- # REVISIT: No context for comparisons, yet
96
- comparison / fact_clause
97
- end
98
-
99
- rule fact_clause
100
- s q:qualifier? s reading s p:post_qualifiers? s c:context_note?
101
- {
102
- def ast
103
- r = reading.ast # An array of readings
92
+ def ast(conjunction = nil)
93
+ r = contracted_clauses.ast # An array of clause asts
94
+ r[0].conjunction = conjunction
95
+ # pre-qualifiers apply to the first clause, post_qualifiers and context_note to the last
96
+ # REVISIT: This may be incorrect where the last is a nested clause
104
97
  r[0].qualifiers << q.text_value unless q.empty?
105
98
  r[-1].qualifiers += p.list unless p.empty?
106
99
  r[-1].context_note = c.ast unless c.empty?
@@ -127,104 +120,173 @@ module ActiveFacts
127
120
  intransitive / transitive / acyclic / symmetric / asymmetric / antisymmetric / reflexive / irreflexive
128
121
  end
129
122
 
130
- rule reading
123
+ rule clauses_list
124
+ clauses tail:( ',' s clauses )*
125
+ {
126
+ def ast
127
+ [clauses.ast, *tail.elements.map{|e| e.clauses.ast }]
128
+ end
129
+ }
130
+ end
131
+
132
+ rule clauses
133
+ contracted_clauses s tail:( and s contracted_clauses s )*
134
+ {
135
+ def ast
136
+ clauses = contracted_clauses.ast
137
+ tail.elements.map{|e| clauses += e.contracted_clauses.ast }
138
+ clauses
139
+ end
140
+ }
141
+ end
142
+
143
+ rule contracted_clauses
144
+ comparison
145
+ /
131
146
  (
132
- contracted_reading
147
+ contraction # A contraction will terminate this repetition by eating to the end
133
148
  /
134
- role # A role reference containing a term, perhaps with attached paraphernalia
135
- / # A hyphenated non-term. Important: no embedded spaces
136
- id tail:('-' !term id)+ s
137
- {
138
- def ast
139
- [id.value, *tail.elements.map{|e| e.id.value}]*"-"
140
- end
141
- }
142
- / # A normal non-term
143
- !non_role_word id s
144
- {
145
- def ast
146
- id.value
147
- end
148
- }
149
+ phrase
149
150
  )+
150
151
  {
151
152
  def ast
152
153
  asts = elements.map{ |r| r.ast }
153
- contraction = []
154
+ contracted_clauses = []
154
155
  qualifiers = []
155
- if asts[-1].is_a?(Array) # A contracted_reading (Array of [role, qualifiers, *readings])
156
- contraction = asts.pop # Pull off the contraction
157
- contracted_role = contraction.shift
158
- qualifiers = contraction.shift
159
- asts.push(contracted_role) # And replace it by the role removed from the contraction
156
+ if asts[-1].is_a?(Array) # A contraction (Array of [role, qualifiers, *clauses])
157
+ contracted_clauses = asts.pop # Pull off the contracted_clauses
158
+ contracted_role = contracted_clauses.shift
159
+ qualifiers = contracted_clauses.shift
160
+ asts.push(contracted_role) # And replace it by the role removed from the contracted_clauses
160
161
  end
161
- reading = Compiler::Reading.new(asts)
162
- reading.qualifiers += qualifiers
163
- [reading] + contraction
162
+ clause_ast = Compiler::Clause.new(asts, qualifiers)
163
+ [clause_ast] + contracted_clauses
164
164
  end
165
165
  }
166
166
  end
167
167
 
168
- # REVISIT: and later, similarly for contracted conditions (including comparisons)
169
- rule contracted_reading
170
- role p:post_qualifiers? that s q:qualifier? s reading s
168
+ rule contraction
169
+ reading_contraction /
170
+ condition_contraction
171
+ end
172
+
173
+ rule reading_contraction
174
+ role p:post_qualifiers? conjunction:(that/who) s q:qualifier? s contracted_clauses s
171
175
  {
172
176
  def ast
173
- # reading.ast will return an array of Readings, but the first reading is special. We must:
174
- # * prepend a new role
177
+ # contracted_clauses.ast will return an array of Clauses, but the first clause is special. We must:
178
+ # * prepend a new role (we get the Role to build *two* ast nodes)
175
179
  # * attach the qualifiers
176
- readings = reading.ast
177
- readings[0].phrases.unshift(role.ast)
178
- readings[0].qualifiers << q.text_value unless q.empty? # Add maybe/definitely
180
+ clauses_ast = contracted_clauses.ast
181
+ clauses_ast[0].conjunction = conjunction.text_value
182
+ clauses_ast[0].phrases.unshift(role.ast)
183
+ clauses_ast[0].qualifiers << q.text_value unless q.empty? # Add maybe/definitely
179
184
 
180
- # A contracted_reading returns an array containing:
185
+ # A contraction returns an array containing:
181
186
  # * a role AST
182
187
  # * a qualifiers array
183
- # * an array of Readings
184
- [role.ast, p.empty? ? [] : p.list] + readings
188
+ # * an array of Clauses
189
+ [role.ast, p.empty? ? [] : p.list] + clauses_ast
190
+ end
191
+ }
192
+ end
193
+
194
+ rule condition_contraction
195
+ role p:post_qualifiers? q:qualifier? s comparator s e2:expression
196
+ !phrase # The contracted_clauses must not continue here!
197
+ {
198
+ def ast
199
+ c = Compiler::Comparison.new(comparator.text_value, role.ast, e2.ast, q.empty? ? [] : [q.text_value])
200
+ c.conjunction = comparator.text_value
201
+ [ role.ast, p.empty? ? [] : p.list, c ]
202
+ end
203
+ }
204
+ end
205
+
206
+ rule comparison
207
+ e1:expression s q:qualifier? s comparator s contraction p:post_qualifiers?
208
+ {
209
+ def ast
210
+ role, qualifiers, *clauses_ast = *contraction.ast
211
+ clauses_ast[0].qualifiers += p.list unless p.empty? # apply post_qualifiers to the contracted clause
212
+ # clauses_ast[0].conjunction = 'and' # AND is implicit for a contraction
213
+ c = Compiler::Comparison.new(comparator.text_value, e1.ast, role)
214
+ c.qualifiers << q.text_value unless q.empty?
215
+ [c] + clauses_ast
185
216
  end
186
217
  }
218
+ /
219
+ q:qualifier? e1:expression s comparator s e2:expression # comparisons have no post-qualifiers: p:post_qualifiers?
220
+ {
221
+ def ast
222
+ [Compiler::Comparison.new(comparator.text_value, e1.ast, e2.ast, q.empty? ? [] : [q.text_value])]
223
+ end
224
+ }
225
+ end
226
+
227
+ rule comparator
228
+ '<=' / '<>' / '<' / '=' / '>=' / '>' / '!='
229
+ end
230
+
231
+ rule phrase
232
+ role # A role reference containing a term, perhaps with attached paraphernalia
233
+ / # A hyphenated non-term. Important: no embedded spaces
234
+ id tail:('-' !term id)+ s
235
+ {
236
+ def ast
237
+ [id.value, *tail.elements.map{|e| e.id.value}]*"-"
238
+ end
239
+ def node_type; :linking; end
240
+ }
241
+ / # A normal non-term
242
+ !non_phrase id s
243
+ {
244
+ def ast
245
+ id.value
246
+ end
247
+ def node_type; :linking; end
248
+ }
187
249
  end
188
250
 
189
251
  # This is the rule that causes most back-tracking. I think you can see why.
190
- # When we have an expression, we will come down here perhaps multiple times,
191
- # but find no way out as soon as we hit the trailing non_role.
192
252
  rule role
193
253
  q:(quantifier enforcement)?
194
- player:term
195
- func:function_call? s
196
- role_id:(role_name / subscript )?
254
+ player:derived_variable
197
255
  lr:(
198
256
  literal u:unit?
199
257
  /
200
258
  value_constraint enforcement
201
259
  )?
202
260
  oj:objectification_join?
203
- !non_role # If we integrate fact_clauses with comparisons, this can go.
204
261
  {
205
262
  def ast
206
263
  if !q.empty? && q.quantifier.value
207
264
  quantifier = Compiler::Quantifier.new(q.quantifier.value[0], q.quantifier.value[1], q.enforcement.ast)
208
265
  end
209
- role_name = role_id.empty? ? nil : role_id.value
210
- function_call = nil
211
266
  if !lr.empty?
212
267
  if lr.respond_to?(:literal)
213
268
  literal = lr.literal.value
214
269
  raise "Literals with units are not yet processed" unless lr.u.empty?
215
270
  end
216
- value_constraint = Compiler::ValueConstraint.new(lr.value_constraint.ranges, lr.enforcement.ast) if lr.respond_to?(:value_constraint)
217
- raise "It is not permitted to provide both a literal value and a value restriction" if value_constraint and literal
271
+ value_constraint = Compiler::ValueConstraint.new(lr.value_constraint.ranges, lr.value_constraint.units, lr.enforcement.ast) if lr.respond_to?(:value_constraint)
272
+ raise "It is not permitted to provide both a literal value and a value constraint" if value_constraint and literal
218
273
  end
219
274
 
220
- objectification_join = oj.empty? ? nil : oj.ast
221
- player.ast(quantifier, function_call, role_name, value_constraint, literal, objectification_join)
275
+ nested_clauses =
276
+ if oj.empty?
277
+ nil
278
+ else
279
+ ast = oj.ast
280
+ ast[0].conjunction = 'where'
281
+ ast
282
+ end
283
+ player.ast(quantifier, value_constraint, literal, nested_clauses)
222
284
  end
223
285
  }
224
286
  end
225
287
 
226
288
  rule objectification_join
227
- '(' s where s facts:fact_clauses s ')' s
289
+ '(' s where s facts:joined_clauses s ')' s
228
290
  {
229
291
  def ast
230
292
  facts.ast
@@ -242,13 +304,6 @@ module ActiveFacts
242
304
  { def value; i.text_value.to_i; end }
243
305
  end
244
306
 
245
- rule non_role
246
- # Any of these is illegal in or following a reading (they indicate a comparison is coming). Later, this will change:
247
- comparator
248
- / add_op
249
- / mul_op
250
- end
251
-
252
307
  end
253
308
  end
254
309
  end