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,210 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to ObjectType definitions.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar ObjectTypes
10
+ rule object_type
11
+ value_type
12
+ / entity_type
13
+ / named_fact_type
14
+ / anonymous_fact_type
15
+ end
16
+
17
+ rule entity_type
18
+ s each?
19
+ s term_definition_name
20
+ m1:mapping_pragmas
21
+ c:context_note?
22
+ sup:(basetype / subtype)
23
+ &{|s|
24
+ # There's an implicit type when we use an identification mode, register it:
25
+ mode = s[6].identification_mode
26
+ if mode
27
+ input.context.object_type(s[3].value+mode, "identification mode type")
28
+ input.context.object_type(s[3].value+' '+mode, "identification mode type")
29
+ end
30
+ true
31
+ }
32
+ m2:mapping_pragmas
33
+ c2:context_note?
34
+ ec:entity_clauses?
35
+ ';'
36
+ {
37
+ def ast
38
+ name = term_definition_name.value
39
+ clauses_ast = ec.empty? ? [] : ec.ast
40
+ pragmas = m1.value+m2.value
41
+ pragmas << 'independent' if sup.independent
42
+ context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
43
+ Compiler::EntityType.new name, sup.supers, sup.ast, pragmas, clauses_ast, context_note
44
+ end
45
+ }
46
+ end
47
+
48
+ rule basetype
49
+ basetype_expression
50
+ {
51
+ def ast; identification.ast; end
52
+ def supers; []; end
53
+ def identification_mode; identification.mode; end
54
+ def independent; !i.empty?; end
55
+ }
56
+ end
57
+
58
+ rule subtype
59
+ subtype_expression
60
+ {
61
+ def ast; ident.empty? ? nil : ident.ast; end
62
+ def supers; supertype_list.value; end
63
+ def identification_mode; ident.empty? ? nil : ident.mode; end
64
+ def independent; !i.empty?; end
65
+ }
66
+ end
67
+
68
+ rule supertype_list
69
+ primary:term s alternate_supertypes:( (','/'and' !alpha) s !identified_by name:term s )*
70
+ {
71
+ def value
72
+ [primary.value, *alternate_supertypes.elements.map { |sup| sup.name.value } ]
73
+ end
74
+ }
75
+ end
76
+
77
+ rule identification
78
+ # REVISIT: Consider distinguishing "-Id" from just "Id", and not prepending the entity type name if no "-"
79
+ identified_by its s i:(term/implicit_value_type_name) value_type_parameters
80
+ r:(value_constraint enforcement)? # Reference Mode; value_constraint may be needed for the ValueType
81
+ {
82
+ def ast
83
+ if r.empty?
84
+ value_constraint = nil
85
+ else
86
+ value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
87
+ end
88
+ Compiler::ReferenceMode.new(i.value, value_constraint, value_type_parameters.values)
89
+ end
90
+
91
+ def mode
92
+ i.value
93
+ end
94
+ }
95
+ /
96
+ identified_by role_list
97
+ &{|s|
98
+ role_list = s[-1]
99
+ forwards = role_list.ast.
100
+ map do |role|
101
+ next nil if role.is_a?(Compiler::Clause) # Can't forward-reference unaries
102
+ next nil if role.leading_adjective or role.trailing_adjective
103
+ role.term
104
+ end.
105
+ compact
106
+ input.context.allowed_forward_terms(forwards)
107
+ true
108
+ }
109
+ {
110
+ def ast
111
+ role_list.ast
112
+ end
113
+
114
+ def mode
115
+ nil
116
+ end
117
+ }
118
+ end
119
+
120
+ # Identified by roles... also used for constraints, beware
121
+ rule role_list
122
+ a:any? s
123
+ head:term_or_unary s
124
+ tail:(
125
+ ( and S / ',' s )
126
+ any? s
127
+ term_or_unary s
128
+ )*
129
+ {
130
+ def ast
131
+ [head.ast, *tail.elements.map{|e| e.term_or_unary.ast}]
132
+ end
133
+ }
134
+ end
135
+
136
+ rule unary_text
137
+ (s !any !non_phrase !term id)*
138
+ {
139
+ def node_type; :linking; end
140
+ }
141
+ end
142
+
143
+ rule term_or_unary
144
+ pre_text:unary_text s term post_text:unary_text s ss:subscript?
145
+ {
146
+ def ast
147
+ t = term.ast
148
+ t.role_name = ss.value if !ss.empty?
149
+ if pre_text.elements.size == 0 && post_text.elements.size == 0
150
+ t
151
+ else
152
+ pre_words = pre_text.elements.map{|w| w.id.text_value}
153
+ post_words = post_text.elements.map{|w| w.id.text_value}
154
+ Compiler::Clause.new(pre_words + [t] + post_words, [], nil)
155
+ end
156
+ end
157
+ }
158
+ /
159
+ s !non_phrase id s &non_phrase s ss:subscript?
160
+ { # A forward-referenced entity type
161
+ # REVISIT: A change in this rule might allow forward-referencing a multi-word term
162
+ def ast
163
+ Compiler::Reference.new(id.text_value, nil, nil, nil, nil, ss.empty? ? nil : ss.value)
164
+ end
165
+ }
166
+ end
167
+
168
+ rule mapping_pragmas
169
+ '[' s h:mapping_pragma t:(s ',' s mapping_pragma)* s ']' s
170
+ {
171
+ def value
172
+ t.elements.inject([h.value*' ']) do |a, e|
173
+ a << e.mapping_pragma.value*' '
174
+ end
175
+ end
176
+ }
177
+ /
178
+ s
179
+ { def value; []; end }
180
+ end
181
+
182
+ # Each mapping_pragma returns an array of words
183
+ rule mapping_pragma
184
+ was s names:(id s)+
185
+ { # Old or previous name of an object type:
186
+ def value
187
+ [ was.text_value ] + names.elements.map{|n|n.text_value}
188
+ end
189
+ }
190
+ /
191
+ head:id tail:(s id)*
192
+ { # A sequence of one or more words denoting a pragma:
193
+ def value
194
+ ([head]+tail.elements.map(&:id)).map(&:text_value)
195
+ end
196
+ }
197
+ end
198
+
199
+ rule entity_clauses
200
+ (':' / where) s query_clauses
201
+ {
202
+ def ast
203
+ query_clauses.ast
204
+ end
205
+ }
206
+ end
207
+
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,183 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to Term names
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar Terms
10
+ rule term_definition_name
11
+ id s t:(!non_term_def id s)*
12
+ <Parser::TermDefinitionNameNode>
13
+ end
14
+
15
+ rule non_term_def
16
+ mapping_pragmas entity_prefix
17
+ / mapping_pragmas written_as # Value type
18
+ / mapping_pragmas is_where # Objectified type
19
+ / non_phrase
20
+ / identified_by # as in: "a kind of X identified by..."
21
+ / in_units
22
+ / auto_assignment
23
+ / value_constraint
24
+ end
25
+
26
+ rule entity_prefix
27
+ is s (independent s )? identified_by
28
+ /
29
+ subtype_prefix (independent s )? term_definition_name
30
+ &{|e| input.context.object_type(e[2].value, "subtype") }
31
+ end
32
+
33
+ rule prescan
34
+ s each?
35
+ s (
36
+ term_definition_name mapping_pragmas entity_prefix
37
+ &{|e| input.context.object_type(e[0].value, "entity type") }
38
+ /
39
+ t1:term_definition_name mapping_pragmas written_as any? s t2:term_definition_name
40
+ &{|e|
41
+ new_term = e[0].value
42
+ input.context.object_type(new_term, "value type")
43
+ base_term = e[5].value
44
+ input.context.object_type(base_term, "value type")
45
+ }
46
+ /
47
+ term_definition_name s mapping_pragmas is_where
48
+ &{|e| input.context.object_type(e[0].value, "objectified_fact_type") }
49
+ )?
50
+ prescan_rest
51
+ &{|s|
52
+ # Wipe any terminal failures that were added:
53
+ @terminal_failures = []
54
+ @max_terminal_failure_index = start_index
55
+
56
+ # puts "========== prescan is complete on #{(s.map{|e|e.text_value}*" ").inspect} =========="
57
+ false
58
+ }
59
+ end
60
+
61
+ # Do a first-pass mainly lexical analysis, looking for role name definitions and adjectives,
62
+ # for use in detecting terms later.
63
+ rule prescan_rest
64
+ &{|s| input.context.reset_role_names }
65
+ (
66
+ context_note # Context notes have different lexical conventions
67
+ / '(' as S term_definition_name s ')' s # Prepare for a Role Name
68
+ &{|s| input.context.role_name(s[3].value) }
69
+ / new_derived_value # Prepare for a derived term
70
+ / new_adjective_term # Prepare for an existing term with new Adjectives
71
+ # The remaining rules exist to correctly eat up anything that doesn't match the above:
72
+ / global_term # If we see A B - C D, don't recognise B as a new adjective for C D.
73
+ / prescan_aggregate
74
+ / id
75
+ # / literal # REVISIT: Literals might contain "(as Foo)" and mess things up
76
+ / range # Covers all numbers and strings
77
+ / comparator # handle two-character operators
78
+ / S # White space and comments, must precede / and *
79
+ / [-+{}\[\].,:^/%*()] # All other punctuation and operators
80
+ )* [?;] s
81
+ end
82
+
83
+ # Not sure this is even needed, but it doesn't seem to hurt:
84
+ rule prescan_aggregate
85
+ aggregate_type:id s agg_of s global_term agg_in s &'('
86
+ end
87
+
88
+ rule new_derived_value
89
+ !global_term id derived_value_continuation? s '='
90
+ &{|s|
91
+ name = [s[1].text_value] + (s[2].empty? ? [] : s[2].value)
92
+ input.context.object_type(name*' ', "derived value type")
93
+ }
94
+ /
95
+ '=' s !global_term id derived_value_continuation? s (that/who)
96
+ &{|s|
97
+ name = [s[3].text_value] + (s[4].empty? ? [] : s[4].value)
98
+ input.context.object_type(name*' ', "derived value type")
99
+ }
100
+ end
101
+
102
+ # Derived values are new terms introduced by an = sign before an expression
103
+ # This rule handles trailing words of a multi-word derived value
104
+ rule derived_value_continuation
105
+ s '-' tail:(s !global_term !(that/who) id)*
106
+ {
107
+ def value
108
+ tail.elements.map{|e| e.id.text_value}
109
+ end
110
+ }
111
+ end
112
+
113
+ # Used during the pre-scan, match a term with new adjective(s)
114
+ rule new_adjective_term
115
+ !global_term adj:id '-' '-'? lead_intervening s global_term # Definitely a new leading adjective for this term
116
+ &{|s| adj = [s[1].text_value, s[4].value].compact*" "; input.context.new_leading_adjective_term(adj, s[6].text_value) }
117
+ /
118
+ global_term s trail_intervening '-' '-'? !global_term adj:id # Definitely a new trailing adjective for this term
119
+ &{|s| adj = [s[2].value, s[6].text_value].compact*" "; input.context.new_trailing_adjective_term(adj, s[0].text_value) }
120
+ end
121
+
122
+ rule lead_intervening # Words intervening between a new adjective and the term
123
+ (S !global_term id)*
124
+ {
125
+ def value
126
+ elements.size == 0 ? nil : elements.map{|e| e.id.text_value}*" "
127
+ end
128
+ }
129
+ end
130
+
131
+ rule trail_intervening # Words intervening between a new adjective and the term
132
+ (!global_term id S)*
133
+ {
134
+ def value
135
+ elements.size == 0 ? nil : elements.map{|e| e.id.text_value}*" "
136
+ end
137
+ }
138
+ end
139
+
140
+ # This is the rule to use after the prescan; it only succeeds on a complete term or role reference
141
+ rule term
142
+ s head:id x &{|s| w = s[1].text_value; input.context.term_starts?(w, s[2]) }
143
+ tail:(
144
+ s '-'? dbl:'-'? s w:id &{|s| w = s[4].text_value; input.context.term_continues?(w) }
145
+ )* &{|s| input.context.term_complete? }
146
+ <Parser::TermNode>
147
+ /
148
+ s head:id '-' '-'? s term &{|s| s[5].ast.leading_adjective == nil }
149
+ <Parser::TermLANode>
150
+ end
151
+
152
+ rule x
153
+ '' <SavedContext>
154
+ end
155
+
156
+ rule global_term
157
+ # This rule shouldn't be used outside the prescan, it will memoize the wrong things.
158
+ head:id x &{|s| input.context.term_starts?(s[0].text_value, s[1]) }
159
+ tail:(s w:id &{|s| input.context.term_continues?(s[1].text_value) } )*
160
+ { def value
161
+ tail.elements.inject(head.value) { |t, e| "#{t} #{e.w.value}" }
162
+ end
163
+ }
164
+ end
165
+
166
+ rule non_phrase
167
+ # These words are illegal in (but maybe ok following) a clause where a phrase is expected:
168
+ and
169
+ / but
170
+ / if
171
+ / role_list_constraint_followers
172
+ / only_if
173
+ / or
174
+ / quantifier
175
+ / returning
176
+ / then
177
+ / value_constraint
178
+ / where
179
+ end
180
+
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,202 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to ValueType definitions.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar ValueTypes
10
+ rule value_type
11
+ s each?
12
+ s term_definition_name
13
+ m1:mapping_pragmas
14
+ # REVISIT: ORM2 would allow (subtype_prefix term)?
15
+ written_as
16
+ any? s
17
+ base:(term/implicit_value_type_name) s
18
+ value_type_parameters
19
+ u:in_units?
20
+ a:auto_assignment?
21
+ c:context_note?
22
+ r:(value_constraint enforcement)?
23
+ m2:mapping_pragmas
24
+ c2:context_note?
25
+ s ';' s
26
+ {
27
+ def ast
28
+ name = term_definition_name.value
29
+ params = value_type_parameters.values
30
+ value_constraint = nil
31
+ unless r.empty?
32
+ value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
33
+ end
34
+ units = u.empty? ? [] : u.units.value
35
+ auto_assigned_at = a.empty? ? nil : a.auto_assigned_at
36
+ pragmas = m1.value+m2.value
37
+ context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
38
+ Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas, context_note, auto_assigned_at
39
+ end
40
+ }
41
+ end
42
+
43
+ rule in_units
44
+ in S units
45
+ end
46
+
47
+ rule implicit_value_type_name
48
+ id
49
+ {
50
+ def node_type; :term; end
51
+ }
52
+ end
53
+
54
+ rule value_type_parameters
55
+ '(' s tpl:type_parameter_list? ')' s
56
+ { def values; tpl.empty? ? [] : tpl.values; end }
57
+ / s
58
+ { def values; []; end }
59
+ end
60
+
61
+ rule type_parameter_list
62
+ head:number s tail:( ',' s number s )*
63
+ {
64
+ def values
65
+ [head.value, *tail.elements.map{|i| i.number.value}]
66
+ end
67
+ }
68
+ end
69
+
70
+ rule unit_definition
71
+ u:(
72
+ s coeff:unit_coefficient? base:units? s o:unit_offset?
73
+ conversion
74
+ singular:unit_name s plural:('/' s p:unit_name s)?
75
+ /
76
+ s singular:unit_name s plural:('/' s p:unit_name s)?
77
+ conversion
78
+ coeff:unit_coefficient? base:units? s o:unit_offset?
79
+ )
80
+ q:(approximately '' / ephemera s url )? s
81
+ ';'
82
+ {
83
+ def ast
84
+ singular = u.singular.text_value
85
+ plural = u.plural.text_value.empty? ? nil : u.plural.p.text_value
86
+ if u.coeff.empty?
87
+ raise "Unit definition requires either a coefficient or an ephemera URL" unless q.respond_to?(:ephemera)
88
+ numerator,denominator = 1, 1
89
+ else
90
+ numerator, denominator = *u.coeff.ast
91
+ end
92
+ offset = u.o.text_value.empty? ? 0 : u.o.value
93
+ bases = u.base.empty? ? [] : u.base.value
94
+ approximately = q.respond_to?(:approximately) || u.conversion.approximate?
95
+ ephemera = q.respond_to?(:ephemera) ? q.url.text_value : nil
96
+ Compiler::Unit.new singular, plural, numerator, denominator, offset, bases, approximately, ephemera
97
+ end
98
+ }
99
+ end
100
+
101
+ rule unit_name
102
+ id
103
+ {
104
+ def node_type; :unit; end
105
+ }
106
+ end
107
+
108
+
109
+ rule unit_coefficient
110
+ numerator:number denominator:(s '/' s number)? s
111
+ {
112
+ def ast
113
+ [ numerator.text_value,
114
+ (denominator.text_value.empty? ? "1" : denominator.number.text_value)
115
+ ]
116
+ end
117
+ }
118
+ end
119
+
120
+ rule unit_offset
121
+ sign:[-+] s number s
122
+ { def value
123
+ sign.text_value == '-' ? "-"+number.text_value : number.text_value
124
+ end
125
+ }
126
+ end
127
+
128
+ # In a unit definition, we may use undefined base units; this is the only way to get fundamental units
129
+ rule units
130
+ !non_unit maybe_unit s tail:(!non_unit maybe_unit s)* div:('/' s maybe_unit s tail:(!non_unit maybe_unit s)*)?
131
+ { def value
132
+ tail.elements.inject([maybe_unit.value]) { |a, e| a << e.maybe_unit.value } +
133
+ (div.text_value.empty? ? [] : div.tail.elements.inject([div.maybe_unit.inverse]) { |a, e| a << e.maybe_unit.inverse })
134
+ end
135
+ }
136
+ end
137
+
138
+ rule non_unit
139
+ restricted_to / conversion / approximately / ephemera / auto_assignment
140
+ end
141
+
142
+ rule unit
143
+ maybe_unit &{|s| input.context.unit?(s[0].unit_name.text_value) }
144
+ end
145
+
146
+ rule maybe_unit
147
+ unit_name pow:('^' '-'? [0-9])?
148
+ { def value
149
+ [unit_name.text_value, pow.text_value.empty? ? 1 : Integer(pow.text_value[1..-1])]
150
+ end
151
+ def inverse
152
+ a = value
153
+ a[1] = -a[1]
154
+ a
155
+ end
156
+ }
157
+ end
158
+
159
+ rule value_constraint
160
+ restricted_to restricted_values c:context_note?
161
+ {
162
+ def ast
163
+ v = restricted_values.values
164
+ c[:context_note] = c.ast unless c.empty?
165
+ v
166
+ end
167
+ }
168
+ # REVISIT: "where the possible value/s of that <Term> is/are value (, ...)"
169
+ end
170
+
171
+ rule restricted_values
172
+ range_list s u:units?
173
+ {
174
+ def values
175
+ { :ranges => range_list.ranges,
176
+ :units => u.empty? ? nil : u.value
177
+ }
178
+ end
179
+ }
180
+ /
181
+ regular_expression
182
+ {
183
+ def values
184
+ { :regular_expression => contents }
185
+ end
186
+ }
187
+ end
188
+
189
+ rule range_list
190
+ '{' s
191
+ head:range s tail:( ',' s range )*
192
+ '}' s
193
+ {
194
+ def ranges
195
+ [head.value, *tail.elements.map{|e| e.range.value }]
196
+ end
197
+ }
198
+ end
199
+
200
+ end
201
+ end
202
+ end