activefacts-cql 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +19 -0
  8. data/Rakefile +6 -0
  9. data/activefacts-cql.gemspec +29 -0
  10. data/bin/setup +7 -0
  11. data/lib/activefacts/cql.rb +7 -0
  12. data/lib/activefacts/cql/.gitignore +0 -0
  13. data/lib/activefacts/cql/Rakefile +14 -0
  14. data/lib/activefacts/cql/compiler.rb +156 -0
  15. data/lib/activefacts/cql/compiler/clause.rb +1137 -0
  16. data/lib/activefacts/cql/compiler/constraint.rb +581 -0
  17. data/lib/activefacts/cql/compiler/entity_type.rb +457 -0
  18. data/lib/activefacts/cql/compiler/expression.rb +443 -0
  19. data/lib/activefacts/cql/compiler/fact.rb +390 -0
  20. data/lib/activefacts/cql/compiler/fact_type.rb +421 -0
  21. data/lib/activefacts/cql/compiler/query.rb +106 -0
  22. data/lib/activefacts/cql/compiler/shared.rb +161 -0
  23. data/lib/activefacts/cql/compiler/value_type.rb +174 -0
  24. data/lib/activefacts/cql/parser.rb +234 -0
  25. data/lib/activefacts/cql/parser/CQLParser.treetop +167 -0
  26. data/lib/activefacts/cql/parser/Context.treetop +48 -0
  27. data/lib/activefacts/cql/parser/Expressions.treetop +67 -0
  28. data/lib/activefacts/cql/parser/FactTypes.treetop +358 -0
  29. data/lib/activefacts/cql/parser/Language/English.treetop +315 -0
  30. data/lib/activefacts/cql/parser/Language/French.treetop +315 -0
  31. data/lib/activefacts/cql/parser/Language/Mandarin.treetop +304 -0
  32. data/lib/activefacts/cql/parser/LexicalRules.treetop +253 -0
  33. data/lib/activefacts/cql/parser/ObjectTypes.treetop +210 -0
  34. data/lib/activefacts/cql/parser/Terms.treetop +183 -0
  35. data/lib/activefacts/cql/parser/ValueTypes.treetop +202 -0
  36. data/lib/activefacts/cql/parser/nodes.rb +49 -0
  37. data/lib/activefacts/cql/require.rb +36 -0
  38. data/lib/activefacts/cql/verbaliser.rb +804 -0
  39. data/lib/activefacts/cql/version.rb +5 -0
  40. data/lib/activefacts/input/cql.rb +43 -0
  41. data/lib/rubygems_plugin.rb +12 -0
  42. metadata +167 -0
@@ -0,0 +1,167 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to high-level CQL definitions and constraints.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'activefacts/cql/parser/LexicalRules'
8
+ require 'activefacts/cql/parser/Language/English'
9
+ require 'activefacts/cql/parser/Expressions'
10
+ require 'activefacts/cql/parser/Terms'
11
+ require 'activefacts/cql/parser/ObjectTypes'
12
+ require 'activefacts/cql/parser/ValueTypes'
13
+ require 'activefacts/cql/parser/FactTypes'
14
+ require 'activefacts/cql/parser/Context'
15
+
16
+ module ActiveFacts
17
+ module CQL
18
+ grammar CQL
19
+ include LexicalRules
20
+ include Expressions
21
+ include Terms
22
+ include ObjectTypes
23
+ include ValueTypes
24
+ include FactTypes
25
+ include Context
26
+
27
+ rule cql_file
28
+ s seq:definition*
29
+ {
30
+ def definitions
31
+ seq.elements.map{|e|
32
+ e.value rescue $stderr.puts "Can't call value() on #{e.inspect}"
33
+ }
34
+ end
35
+ }
36
+ end
37
+
38
+ # Each definition has an ast() method that returns an instance of a subclass of Compiler::Definition
39
+ rule definition
40
+ definition_body s
41
+ {
42
+ def ast
43
+ definition_body.ast
44
+ end
45
+
46
+ def body
47
+ definition_body.text_value
48
+ end
49
+ }
50
+ end
51
+
52
+ rule definition_body
53
+ vocabulary_definition
54
+ / import_definition
55
+ / prescan # Always fails, but its side-effects are needed in the following
56
+ / constraint
57
+ / unit_definition # REVISIT: Move this above the prescan?
58
+ / object_type
59
+ / query
60
+ / s ';' s { def ast; nil; end }
61
+ end
62
+
63
+ rule vocabulary_definition
64
+ s vocabulary S vocabulary_name s ';'
65
+ {
66
+ def ast
67
+ Compiler::Vocabulary.new(vocabulary_name.value)
68
+ end
69
+ }
70
+ end
71
+
72
+ rule vocabulary_name
73
+ id
74
+ { def node_type; :vocabulary; end }
75
+ end
76
+
77
+ rule import_definition
78
+ s import S vocabulary_name alias_list ';'
79
+ {
80
+ def ast
81
+ Compiler::Import.new(import.input.parser, vocabulary_name.value, alias_list.value)
82
+ end
83
+ }
84
+ end
85
+
86
+ # REVISIT: Need a way to define equivalent readings for fact types here (and in the metamodel)
87
+ rule alias_list
88
+ ( s ',' s alias S aliased_from:alias_term S as S alias_to:alias_term s )*
89
+ {
90
+ def value
91
+ elements.inject({}){|h, e| h[e.aliased_from.value] = e.alias_to; h }
92
+ end
93
+ }
94
+ end
95
+
96
+ rule alias_term
97
+ id
98
+ { def node_type; :term; end }
99
+ end
100
+
101
+ rule constraint
102
+ subset_constraint /
103
+ equality_constraint /
104
+ set_constraint /
105
+ presence_constraint
106
+ # REVISIT: / value_constraint
107
+ end
108
+
109
+ rule enforcement
110
+ s '(' s otherwise s action s a:agent? s ')' s
111
+ {
112
+ def ast; Compiler::Enforcement.new(action.text_value, a.empty? ? nil : a.text_value); end
113
+ }
114
+ /
115
+ ''
116
+ {
117
+ def ast; nil; end
118
+ }
119
+ end
120
+
121
+ # An enforcement action, like SMS, email, log, alarm, etc.
122
+ rule action
123
+ id
124
+ end
125
+
126
+ # presence constraint:
127
+ rule presence_constraint
128
+ (each_occurs_in_clauses / either_or)
129
+ {
130
+ def ast
131
+ Compiler::PresenceConstraint.new c, enforcement.ast, clauses_ast, role_list_ast, quantifier_ast
132
+ end
133
+ }
134
+ end
135
+
136
+ # set (exclusion, mandatory exclusion, complex equality) constraint
137
+ rule set_constraint
138
+ (for_each_how_many / either_or_not_both)
139
+ {
140
+ def ast
141
+ Compiler::SetExclusionConstraint.new c, enforcement.ast, clauses_ast, role_list_ast, quantifier_ast
142
+ end
143
+ }
144
+ end
145
+
146
+ rule subset_constraint
147
+ (a_only_if_b / if_b_then_a)
148
+ {
149
+ def ast
150
+ Compiler::SubsetConstraint.new c, enforcement.ast, [clauses.ast, r2.ast]
151
+ end
152
+ }
153
+ end
154
+
155
+ rule equality_constraint
156
+ if_and_only_if
157
+ {
158
+ def ast
159
+ all_clauses = [clauses.ast, *tail.elements.map{|e| e.clauses.ast }]
160
+ Compiler::SetEqualityConstraint.new c, enforcement.ast, all_clauses
161
+ end
162
+ }
163
+ end
164
+
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,48 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to definition context.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar Context
10
+ rule context_note
11
+ '('
12
+ s w:who_says? s context_type discussion agreed:(',' a:as_agreed_by)? s
13
+ ')' s
14
+ {
15
+ def value
16
+ [ w.empty? ? nil : w.value, context_type.value, discussion.text_value, agreed.empty? ? [] : agreed.a.value]
17
+ end
18
+ def ast
19
+ who = w.empty? ? nil : w.value
20
+ ag = agreed.empty? ? [] : agreed.a.value
21
+ Compiler::ContextNote.new context_type.value, discussion.text_value, who, ag
22
+ end
23
+ }
24
+ end
25
+
26
+ rule who_says
27
+ according_to agents s ','
28
+ { def value; agents.value; end }
29
+ end
30
+
31
+ rule context_type
32
+ because s { def value; 'because'; end } /
33
+ as_opposed_to { def value; 'as_opposed_to'; end } /
34
+ so_that { def value; 'so_that'; end } /
35
+ to_avoid { def value; 'to_avoid'; end }
36
+ end
37
+
38
+ rule discussion
39
+ (
40
+ '(' discussion ')' / (!( [()] / ',' as_agreed_by) .)*
41
+ )
42
+ {
43
+ def node_type; :linking; end
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,67 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to Expressions
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar Expressions
10
+ rule expression
11
+ sum
12
+ end
13
+
14
+ rule sum
15
+ t0:product s tail:( op:add_op s t1:product s )*
16
+ {
17
+ def 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
23
+ end
24
+ }
25
+ end
26
+
27
+ rule add_op
28
+ '+' / '-'
29
+ end
30
+
31
+ rule product
32
+ f0:factor s tail:( op:mul_op s f1:factor s )*
33
+ {
34
+ def 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
40
+ end
41
+ }
42
+ end
43
+
44
+ rule factor
45
+ literal u:unit? s
46
+ {
47
+ def ast
48
+ Compiler::Literal.new(literal.value, u.empty? ? nil : u.text_value)
49
+ end
50
+ }
51
+ / derived_variable
52
+ / !context_note '(' s sum s ')' s { def ast; sum.ast; end }
53
+ end
54
+
55
+ rule derived_variable
56
+ derived:term s role_id:(role_name / subscript )?
57
+ {
58
+ def ast quantifier = nil, value_constraint = nil, literal = nil, nested_clauses = nil
59
+ role_name = role_id.empty? ? nil : role_id.value
60
+ derived.ast(quantifier, nil, role_name, value_constraint, literal, nested_clauses)
61
+ end
62
+ }
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,358 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to FactType definitions.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar FactTypes
10
+ rule query
11
+ s query_clauses r:returning_clause? '?'
12
+ {
13
+ def ast
14
+ Compiler::FactType.new nil, [], query_clauses.ast, (r.empty? ? nil : r)
15
+ end
16
+ }
17
+ end
18
+
19
+ rule named_fact_type
20
+ s each?
21
+ s term_definition_name
22
+ mapping_pragmas is_where
23
+ anonymous_fact_type
24
+ {
25
+ def ast
26
+ ft = anonymous_fact_type.ast
27
+ ft.name = term_definition_name.value
28
+ pragmas = mapping_pragmas.value
29
+ pragmas << 'independent' if is_where.independent
30
+ ft.pragmas = pragmas
31
+ ft
32
+ end
33
+ }
34
+ end
35
+
36
+ rule anonymous_fact_type
37
+ query_clauses
38
+ ctail:( (':' / where) s a:query_clauses s)?
39
+ returning_clause?
40
+ s ';'
41
+ {
42
+ def ast
43
+ clauses_ast = query_clauses.ast
44
+ conditions = !ctail.empty? ? ctail.a.ast : []
45
+ returning = respond_to?(:returning_clause) ? returning_clause.ast : nil
46
+ value_derivation = clauses_ast.detect{|r| r.is_equality_comparison}
47
+ if !value_derivation and
48
+ conditions.empty? and
49
+ clauses_ast.detect{|r| r.includes_literals}
50
+ raise "Fact instances may not contain conditions" unless conditions.empty? && !returning
51
+ Compiler::Fact.new clauses_ast
52
+ elsif (clauses_ast.size == 1 &&
53
+ clauses_ast[0].phrases.size == 1 &&
54
+ (popname = clauses_ast[0].phrases[0]) &&
55
+ !popname.is_a?(Compiler::Reference) &&
56
+ conditions.detect{|r| r.includes_literals}
57
+ )
58
+ Compiler::Fact.new conditions, popname
59
+ else
60
+ Compiler::FactType.new nil, clauses_ast, conditions, returning
61
+ end
62
+ end
63
+ }
64
+ end
65
+
66
+ rule query_clauses
67
+ qualified_clauses
68
+ # REVISIT: This creates no precedence between and/or, which could cause confusion.
69
+ # Should disallow mixed conjuntions - using a sempred?
70
+ ftail:( conjunction:(',' / and / or ) s qualified_clauses s )*
71
+ {
72
+ def ast
73
+ clauses_ast = qualified_clauses.ast
74
+ ftail.elements.each{|e|
75
+ conjunction = e.conjunction.text_value
76
+ # conjunction = 'and' if conjunction == ',' # ',' means AND, but disallows left-contractions
77
+ clauses_ast += e.qualified_clauses.ast(conjunction)
78
+ }
79
+ clauses_ast
80
+ end
81
+ }
82
+ end
83
+
84
+ rule returning_clause
85
+ returning s return (s ',' s return)*
86
+ end
87
+
88
+ rule return
89
+ ordering_prefix? phrase+
90
+ end
91
+
92
+ rule qualified_clauses
93
+ s certainty s contracted_clauses s p:post_qualifiers? s c:context_note?
94
+ {
95
+ def ast(conjunction = nil)
96
+ r = contracted_clauses.ast # An array of clause asts
97
+ r[0].conjunction = conjunction
98
+ # pre-qualifiers apply to the first clause, post_qualifiers and context_note to the last
99
+ # REVISIT: This may be incorrect where the last is a nested clause
100
+ r[0].certainty = certainty.value
101
+ r[-1].qualifiers += p.list unless p.empty?
102
+ r[-1].context_note = c.ast unless c.empty?
103
+ r
104
+ end
105
+ }
106
+ end
107
+
108
+ rule certainty
109
+ negative_prefix { def value; false; end }
110
+ /
111
+ maybe { def value; nil; end }
112
+ /
113
+ definitely { def value; true; end }
114
+ /
115
+ '' { def value; true; end }
116
+ end
117
+
118
+ rule post_qualifiers
119
+ '[' s q0:post_qualifier tail:( s ',' s q1:post_qualifier )* s ']' s
120
+ {
121
+ def list
122
+ [q0.text_value, *tail.elements.map{|e| e.q1.text_value}]
123
+ end
124
+ }
125
+ end
126
+
127
+ rule post_qualifier
128
+ static / transient /
129
+ intransitive / stronglyintransitive / transitive / acyclic / symmetric / asymmetric / antisymmetric / reflexive / irreflexive
130
+ end
131
+
132
+ rule clauses_list
133
+ clauses tail:( ',' s clauses )*
134
+ {
135
+ def ast
136
+ [clauses.ast, *tail.elements.map{|e| e.clauses.ast }]
137
+ end
138
+ }
139
+ end
140
+
141
+ rule clauses
142
+ contracted_clauses s tail:( and s contracted_clauses s )*
143
+ {
144
+ def ast
145
+ clauses = contracted_clauses.ast
146
+ tail.elements.map{|e| clauses += e.contracted_clauses.ast }
147
+ clauses
148
+ end
149
+ }
150
+ end
151
+
152
+ rule contracted_clauses
153
+ comparison
154
+ /
155
+ (
156
+ contraction # A contraction will terminate this repetition by eating to the end
157
+ /
158
+ phrase
159
+ )+
160
+ {
161
+ def ast
162
+ asts = elements.map{ |r| r.ast }
163
+ contracted_clauses = []
164
+ qualifiers = []
165
+ if asts[-1].is_a?(Array) # A contraction (Array of [role, qualifiers, *clauses])
166
+ contracted_clauses = asts.pop # Pull off the contracted_clauses
167
+ contracted_role = contracted_clauses.shift
168
+ qualifiers = contracted_clauses.shift
169
+ asts.push(contracted_role) # And replace it by the role removed from the contracted_clauses
170
+ end
171
+ clause_ast = Compiler::Clause.new(asts, qualifiers)
172
+ [clause_ast] + contracted_clauses
173
+ end
174
+ }
175
+ end
176
+
177
+ rule contraction
178
+ reading_contraction /
179
+ condition_contraction
180
+ end
181
+
182
+ rule reading_contraction
183
+ role p:post_qualifiers? conjunction:(that/who) s certainty s contracted_clauses s
184
+ {
185
+ def ast
186
+ # contracted_clauses.ast will return an array of Clauses, but the first clause is special. We must:
187
+ # * prepend a new role (we get the Role to build *two* ast nodes)
188
+ # * attach the qualifiers
189
+ clauses_ast = contracted_clauses.ast
190
+ clauses_ast[0].conjunction = conjunction.text_value
191
+ clauses_ast[0].phrases.unshift(role.ast)
192
+ clauses_ast[0].certainty = certainty.value
193
+
194
+ # A contraction returns an array containing:
195
+ # * a role AST
196
+ # * a qualifiers array
197
+ # * an array of Clauses
198
+ [role.ast, p.empty? ? [] : p.list] + clauses_ast
199
+ end
200
+ }
201
+ end
202
+
203
+ rule condition_contraction
204
+ role pq:post_qualifiers? certainty s comparator s e2:expression
205
+ !phrase # The contracted_clauses must not continue here!
206
+ {
207
+ def ast
208
+ c = Compiler::Comparison.new(comparator.text_value, role.ast, e2.ast, certainty.value)
209
+ c.conjunction = comparator.text_value
210
+ [ role.ast, pq.empty? ? [] : pq.list, c ]
211
+ end
212
+ }
213
+ end
214
+
215
+ rule comparison
216
+ e1:expression s certainty s comparator s contraction p:post_qualifiers?
217
+ {
218
+ def ast
219
+ role, qualifiers, *clauses_ast = *contraction.ast
220
+ clauses_ast[0].qualifiers += p.list unless p.empty? # apply post_qualifiers to the contracted clause
221
+ # clauses_ast[0].conjunction = 'and' # AND is implicit for a contraction
222
+ c = Compiler::Comparison.new(comparator.text_value, e1.ast, role, certainty.value)
223
+ [c] + clauses_ast
224
+ end
225
+ }
226
+ /
227
+ certainty e1:expression s comparator s e2:expression # comparisons have no post-qualifiers: p:post_qualifiers?
228
+ {
229
+ def ast
230
+ c = Compiler::Comparison.new(comparator.text_value, e1.ast, e2.ast, certainty.value)
231
+ [c]
232
+ end
233
+ }
234
+ end
235
+
236
+ rule comparator
237
+ '<=' / '<>' / '<' / '=' / '>=' / '>' / '!='
238
+ end
239
+
240
+ rule phrase
241
+ role # A role reference containing a term, perhaps with attached paraphernalia
242
+ / # A hyphenated non-term. Important: no embedded spaces
243
+ id tail:('-' !term id)+ s
244
+ {
245
+ def ast
246
+ [id.value, *tail.elements.map{|e| e.id.value}]*"-"
247
+ end
248
+ def node_type; :linking; end
249
+ }
250
+ / # A normal non-term
251
+ !non_phrase id s
252
+ {
253
+ def ast
254
+ id.value
255
+ end
256
+ def node_type; :linking; end
257
+ }
258
+ end
259
+
260
+ rule role
261
+ aggregate
262
+ /
263
+ simple_role
264
+ end
265
+
266
+ rule aggregate
267
+ aggregate:id s
268
+ agg_of s term_or_unary s agg_in s # REVISIT: this term may need to pre-scanned in the qualified_clauses
269
+ '(' qualified_clauses s ')' # REVISIT: Need to test to verify this is the right level (not query_clauses, etc)
270
+ {
271
+ def ast
272
+ raise "Not implemented: AST for '#{aggregate.text_value} of #{term_or_unary.text_value}'"
273
+ # This returns just the role with the nested clauses, which doesn't even work:
274
+ term.ast(
275
+ nil, # No quantifier
276
+ nil, # No function call
277
+ nil, # No role_name
278
+ nil, # No value_constraint
279
+ nil, # No literal
280
+ qualified_clauses.ast
281
+ )
282
+ end
283
+ }
284
+ end
285
+
286
+ rule role_quantifier
287
+ quantifier mapping_pragmas enforcement cn:context_note?
288
+ {
289
+ def ast
290
+ Compiler::Quantifier.new(
291
+ quantifier.value[0],
292
+ quantifier.value[1],
293
+ enforcement.ast,
294
+ cn.empty? ? nil : cn.ast,
295
+ mapping_pragmas.value
296
+ )
297
+ end
298
+ }
299
+ end
300
+
301
+ # This is the rule that causes most back-tracking. I think you can see why.
302
+ rule simple_role
303
+ q:role_quantifier?
304
+ player:derived_variable
305
+ lr:(
306
+ literal u:unit?
307
+ /
308
+ value_constraint enforcement
309
+ )?
310
+ oj:objectification_step?
311
+ {
312
+ def ast
313
+ if !q.empty? && q.quantifier.value
314
+ quantifier = q.ast
315
+ end
316
+ if !lr.empty?
317
+ if lr.respond_to?(:literal)
318
+ literal = Compiler::Literal.new(lr.literal.value, lr.u.empty? ? nil : lr.u.text_value)
319
+ end
320
+ value_constraint = Compiler::ValueConstraint.new(lr.value_constraint.ast, lr.enforcement.ast) if lr.respond_to?(:value_constraint)
321
+ raise "It is not permitted to provide both a literal value and a value constraint" if value_constraint and literal
322
+ end
323
+
324
+ nested_clauses =
325
+ if oj.empty?
326
+ nil
327
+ else
328
+ ast = oj.ast
329
+ ast[0].conjunction = 'where'
330
+ ast
331
+ end
332
+ player.ast(quantifier, value_constraint, literal, nested_clauses)
333
+ end
334
+ }
335
+ end
336
+
337
+ rule objectification_step
338
+ '(' s in_which s facts:query_clauses s ')' s
339
+ {
340
+ def ast
341
+ facts.ast
342
+ end
343
+ }
344
+ end
345
+
346
+ rule role_name
347
+ '(' s as S r:term s ')' s
348
+ { def value; r.value; end }
349
+ end
350
+
351
+ rule subscript
352
+ '(' s i:([1-9] [0-9]*) s ')' s
353
+ { def value; i.text_value.to_i; end }
354
+ end
355
+
356
+ end
357
+ end
358
+ end