activefacts-cql 1.7.1

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