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,161 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ # In a declaration, a Binding has one or more Reference's.
6
+ # A Binding is for a single ObjectType, normally related to just one Role,
7
+ # and the references (References) to it will normally be the object_type name
8
+ # with the same adjectives (modulo loose binding),
9
+ # or a role name or subscript reference.
10
+ #
11
+ # In some situations a Binding will have some References with the same adjectives,
12
+ # and one or more References with no adjectives - this is called "loose binding".
13
+ class Binding
14
+ attr_reader :player # The ObjectType (object type)
15
+ attr_reader :refs # an array of the References
16
+ attr_reader :role_name
17
+ attr_accessor :rebound_to # Loose binding may set this to another binding
18
+ attr_reader :variable
19
+ attr_accessor :instance # When binding fact instances, the instance goes here
20
+
21
+ def initialize player, role_name = nil
22
+ @player = player
23
+ @role_name = role_name
24
+ @refs = []
25
+ end
26
+
27
+ def inspect
28
+ "#{@player.name}#{@role_name and @role_name.is_a?(Integer) ? " (#{@role_name})" : " (as #{@role_name})"}"
29
+ end
30
+
31
+ def key
32
+ "#{@player.name}#{@role_name && " (as #{@role_name})"}"
33
+ end
34
+
35
+ def <=>(other)
36
+ key <=> other.key
37
+ end
38
+
39
+ def variable= v
40
+ @variable = v # A place for a breakpoint :)
41
+ end
42
+
43
+ def add_ref ref
44
+ @refs << ref
45
+ ref
46
+ end
47
+
48
+ def delete_ref ref
49
+ @refs.delete ref
50
+ end
51
+ end
52
+
53
+ class CompilationContext
54
+ attr_accessor :vocabulary
55
+ attr_accessor :allowed_forward_terms
56
+ attr_accessor :left_contraction_allowed
57
+ attr_accessor :left_contractable_clause
58
+ attr_accessor :left_contraction_conjunction
59
+ attr_reader :bindings # The Bindings in this declaration
60
+ attr_reader :player_by_role_name
61
+
62
+ def initialize vocabulary
63
+ @vocabulary = vocabulary
64
+ @vocabulary_identifier = @vocabulary.identifying_role_values
65
+ @allowed_forward_terms = []
66
+ @bindings = {}
67
+ @player_by_role_name = {}
68
+ @left_contraction_allowed = false
69
+ end
70
+
71
+ # Look up this object_type by its name
72
+ def object_type(name)
73
+ constellation = @vocabulary.constellation
74
+ player = constellation.ObjectType[[@vocabulary_identifier, name]]
75
+
76
+ # Bind to an existing role which has a role name (that's why we bind those first)
77
+ player ||= @player_by_role_name[name]
78
+
79
+ if !player && @allowed_forward_terms.include?(name)
80
+ @vocabulary.valid_entity_type_name(name) # No need for the result here, just no exceptional condition
81
+ player = constellation.EntityType(@vocabulary, name, :concept => :new)
82
+ end
83
+
84
+ player
85
+ end
86
+
87
+ # Pass in an array of clauses or References for player identification and binding (creating the Bindings)
88
+ # It's necessary to identify all players that define a role name first,
89
+ # so those names exist in the context for where they're used.
90
+ def bind *clauses
91
+ cl = clauses.flatten
92
+ cl.each { |clause| clause.identify_players_with_role_name(self) }
93
+ cl.each { |clause| clause.identify_other_players(self) }
94
+ cl.each { |clause| clause.bind(self) }
95
+ end
96
+ end
97
+
98
+ class Definition
99
+ attr_accessor :constellation, :vocabulary, :tree
100
+ def compile
101
+ raise "#{self.class} should implement the compile method"
102
+ end
103
+
104
+ def to_s
105
+ @vocabulary ? "#{vocabulary.to_s}::" : ''
106
+ end
107
+
108
+ def source
109
+ @tree.text_value
110
+ end
111
+ end
112
+
113
+ class Vocabulary < Definition
114
+ def initialize name
115
+ @name = name
116
+ end
117
+
118
+ def compile
119
+ if @constellation.Vocabulary.size > 0
120
+ @constellation.Topic @name
121
+ else
122
+ @constellation.Vocabulary @name
123
+ end
124
+ end
125
+
126
+ def to_s
127
+ @name
128
+ end
129
+ end
130
+
131
+ class Import < Definition
132
+ def initialize parser, name, alias_hash
133
+ @parser = parser
134
+ @name = name
135
+ @alias_hash = alias_hash
136
+ end
137
+
138
+ def to_s
139
+ "#{@vocabulary.to_s} imports #{@alias_hash.map{|k,v| "#{k} as #{v}" }*', '};"
140
+ end
141
+
142
+ def compile
143
+ @parser.compile_import(@name, @alias_hash)
144
+ end
145
+ end
146
+
147
+ class ObjectType < Definition
148
+ attr_reader :name
149
+
150
+ def initialize name
151
+ @name = name
152
+ end
153
+
154
+ def to_s
155
+ "#{super}#{@name}"
156
+ end
157
+ end
158
+
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,174 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class Unit < Definition
6
+ def initialize singular, plural, numerator, denominator, offset, base_units, approximately, ephemera_url
7
+ @singular = singular
8
+ @plural = plural
9
+ @numerator, @denominator = numerator, denominator
10
+ @offset = offset
11
+ @base_units = base_units # An array of pairs, each [unit_name, power]
12
+ @approximately = approximately
13
+ @ephemera_url = ephemera_url
14
+ end
15
+
16
+ def compile
17
+ if (@numerator.to_f / @denominator.to_i != 1.0)
18
+ coefficient = @constellation.Coefficient(
19
+ @numerator,
20
+ @denominator.to_i,
21
+ !@approximately
22
+ # REVISIT: activefacts-api is complaining at present. The following is better and should work:
23
+ # :numerator => @numerator,
24
+ # :denominator => @denominator.to_i,
25
+ # :is_precise => !@approximately
26
+ )
27
+ else
28
+ coefficient = nil
29
+ end
30
+ @offset = nil if @offset.to_f == 0
31
+
32
+ trace :units, "Defining new unit #{@singular}#{@plural ? "/"+@plural : ""}" do
33
+ trace :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
34
+ trace :units, "Offset is #{@offset}" if @offset
35
+ raise "Redefinition of unit #{@singular}" if @constellation.Unit.values.detect{|u| u.name == @singular}
36
+ raise "Redefinition of unit #{@plural}" if @constellation.Unit.values.detect{|u| u.name == @plural}
37
+ unit = @constellation.Unit(:new,
38
+ :name => @singular,
39
+ :plural_name => @plural,
40
+ :coefficient => coefficient,
41
+ :offset => @offset,
42
+ :is_fundamental => @base_units.empty?,
43
+ :ephemera_url => @ephemera_url,
44
+ :vocabulary => @vocabulary
45
+ )
46
+ @base_units.each do |base_unit, exponent|
47
+ base = @constellation.Unit.values.detect{|u| u.name == base_unit || u.plural_name == base_unit }
48
+ trace :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
49
+ base ||= @constellation.Unit(:new, :name => base_unit, :is_fundamental => true, :vocabulary => @vocabulary)
50
+ @constellation.Derivation(:derived_unit => unit, :base_unit => base, :exponent => exponent)
51
+ end
52
+ =begin
53
+ if @plural
54
+ plural_unit = @constellation.Unit(:new,
55
+ :name => @plural,
56
+ :is_fundamental => false,
57
+ :vocabulary => @vocabulary
58
+ )
59
+ @constellation.Derivation(:derived_unit => plural_unit, :base_unit => unit, :exponent => 1)
60
+ end
61
+ =end
62
+ unit
63
+ end
64
+ end
65
+
66
+ def inspect
67
+ to_s
68
+ end
69
+
70
+ def to_s
71
+ super + "Unit(#{
72
+ @singular
73
+ }#{
74
+ @plural ? '/'+@plural : ''
75
+ }) is #{
76
+ @numerator
77
+ }/#{
78
+ @denominator
79
+ }+#{
80
+ @offset
81
+ } #{
82
+ @base_units.map{|b,e|
83
+ b+'^'+e.to_s
84
+ }*'*'
85
+ }"
86
+ end
87
+ end
88
+
89
+ class ValueType < ObjectType
90
+ def initialize name, base, parameters, unit, value_constraint, pragmas, context_note, auto_assigned_at
91
+ super name
92
+ @base_type_name = base
93
+ @parameters = parameters
94
+ @unit = unit
95
+ @value_constraint = value_constraint
96
+ @pragmas = pragmas
97
+ @context_note = context_note
98
+ @auto_assigned_at = auto_assigned_at
99
+ end
100
+
101
+ def compile
102
+ length, scale = *@parameters
103
+
104
+ # Create the base type unless it already exists:
105
+ base_type = nil
106
+ if (@base_type_name != @name)
107
+ unless base_type = @vocabulary.valid_value_type_name(@base_type_name)
108
+ base_type = @constellation.ValueType(@vocabulary, @base_type_name, :concept => :new)
109
+ return base_type if @base_type_name == @name
110
+ end
111
+ end
112
+
113
+ # Create and initialise the ValueType:
114
+ vt = @vocabulary.valid_value_type_name(@name) ||
115
+ @constellation.ValueType(@vocabulary, @name, :concept => :new)
116
+ vt.is_independent = true if @pragmas.delete('independent')
117
+ @pragmas.each do |p|
118
+ @constellation.ConceptAnnotation(:concept => vt.concept, :mapping_annotation => p)
119
+ end if @pragmas
120
+ vt.supertype = base_type if base_type
121
+ vt.length = length if length
122
+ vt.scale = scale if scale
123
+ vt.transaction_phase = @auto_assigned_at
124
+
125
+ unless @unit.empty?
126
+ unit_name, exponent = *@unit[0]
127
+ unit = @constellation.Name[unit_name].unit ||
128
+ @constellation.Name[unit_name].plural_named_unit
129
+ raise "Unit #{unit_name} for value type #{@name} is not defined" unless unit
130
+ if exponent != 1
131
+ base_unit = unit
132
+ unit_name = base_unit.name+"^#{exponent}"
133
+ unless unit = @constellation.Unit.detect{|k,v| v.name == unit_name }
134
+ # Define a derived unit (these are skipped on output)
135
+ unit = @constellation.Unit(:new,
136
+ :vocabulary => @vocabulary,
137
+ :name => unit_name,
138
+ :is_fundamental => false
139
+ )
140
+ @constellation.Derivation(unit, base_unit).exponent = exponent
141
+ end
142
+ end
143
+ vt.unit = unit
144
+ end
145
+
146
+ if @value_constraint
147
+ @value_constraint.constellation = @constellation
148
+ vt.value_constraint = @value_constraint.compile
149
+ end
150
+
151
+ if @context_note
152
+ @context_note.compile(@constellation, vt)
153
+ end
154
+
155
+ vt
156
+ end
157
+
158
+ def to_s
159
+ "ValueType: #{super} is written as #{
160
+ @base_type_name
161
+ }#{
162
+ @parameters.size > 0 ? "(#{ @parameters.map{|p|p.to_s}*', ' })" : ''
163
+ }#{
164
+ @unit && @unit.length > 0 ? " in #{@unit.inspect}" : ''
165
+ }#{
166
+ @value_constraint ? " "+@value_constraint.to_s : ''
167
+ }#{
168
+ @pragmas.size > 0 ? ", pragmas [#{@pragmas*','}]" : ''
169
+ };"
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,234 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # The parser turns CQL strings into abstract syntax trees ready for semantic analysis.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ require 'rubygems'
8
+ require 'treetop'
9
+
10
+ # These are Treetop files, which Polyglot will compile on the fly if precompiled ones aren't found:
11
+
12
+ require 'activefacts/cql/parser/CQLParser'
13
+
14
+ module ActiveFacts
15
+ module CQL
16
+ class Parser < CQLParser
17
+ end
18
+ end
19
+ end
20
+ require 'activefacts/cql/parser/nodes'
21
+
22
+ class Treetop::Runtime::SyntaxNode
23
+ def node_type
24
+ terminal? ? :keyword : :composite
25
+ end
26
+ end
27
+
28
+ module ActiveFacts
29
+ module CQL
30
+ module Terms
31
+ class SavedContext < Treetop::Runtime::SyntaxNode
32
+ attr_accessor :context
33
+ end
34
+ end
35
+
36
+ # Extend the generated parser:
37
+ class Parser
38
+ include ActiveFacts
39
+
40
+ # The Context manages some key information revealed or needed during parsing
41
+ # These methods are semantic predicates; if they return false this parse rule will fail.
42
+ class Context
43
+ attr_reader :term, :global_term
44
+ attr_reader :terms
45
+
46
+ def initialize(parser)
47
+ @parser = parser
48
+ @terms = {}
49
+ @role_names = {}
50
+ @allowed_forward_terms = []
51
+ end
52
+
53
+ def object_type(name, kind)
54
+ index_name(@terms, name) && trace(:context, "new #{kind} '#{name}'")
55
+ true
56
+ end
57
+
58
+ def reset_role_names
59
+ trace :context, "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
60
+ @role_names = {}
61
+ end
62
+
63
+ def allowed_forward_terms(terms)
64
+ @allowed_forward_terms = terms
65
+ end
66
+
67
+ def new_leading_adjective_term(adj, term)
68
+ index_name(@role_names, "#{adj} #{term}", term) && trace(:context, "new compound term '#{adj}- #{term}'")
69
+ true
70
+ end
71
+
72
+ def new_trailing_adjective_term(adj, term)
73
+ index_name(@role_names, "#{term} #{adj}", term) && trace(:context, "new compound term '#{term} -#{adj}'")
74
+ true
75
+ end
76
+
77
+ def role_name(name)
78
+ index_name(@role_names, name) && trace(:context, "new role '#{name}'")
79
+ true
80
+ end
81
+
82
+ def term_starts?(s, context_saver)
83
+ @term = @global_term = nil
84
+
85
+ @term_part = s
86
+ @context_saver = context_saver
87
+ t = @terms[s] || @role_names[s] || system_term(s)
88
+ if t
89
+ # s is a prefix of the keys of t.
90
+ if t[s]
91
+ @global_term = @term = @term_part
92
+ @context_saver.context = {:term => @term, :global_term => @global_term }
93
+ end
94
+ trace :context, "Term #{t[s] ? "is" : "starts"} '#{@term_part}'"
95
+ elsif @allowed_forward_terms.include?(@term_part)
96
+ @term = @term_part
97
+ @context_saver.context = {:term => @term, :global_term => @term }
98
+ trace :context, "Term #{s} is an allowed forward"
99
+ return true
100
+ end
101
+ t
102
+ end
103
+
104
+ def term_continues?(s)
105
+ @term_part = "#{@term_part} #{s}"
106
+ t = @terms[@term_part]
107
+ r = @role_names[@term_part]
108
+ if t && (!r || !r[@term_part]) # Part of a term and not a complete role name
109
+ w = "term"
110
+ else
111
+ t = r
112
+ w = "role_name"
113
+ end
114
+ if t
115
+ trace :context, "Multi-word #{w} #{t[@term_part] ? 'ends at' : 'continues to'} #{@term_part.inspect}"
116
+
117
+ # Record the name of the full term and the underlying global term:
118
+ if t[@term_part]
119
+ @term = @term_part if t[@term_part]
120
+ @global_term = (t = t[@term_part]) == true ? @term_part : t
121
+ trace :context, "saving context #{@term}/#{@global_term}"
122
+ @context_saver.context = {:term => @term, :global_term => @global_term }
123
+ end
124
+ end
125
+ t
126
+ end
127
+
128
+ def term_complete?
129
+ return true if @allowed_forward_terms.include?(@term)
130
+ return true if system_term(@term)
131
+ (t = @terms[@term] and t[@term]) or
132
+ (t = @role_names[@term] and t[@term])
133
+ end
134
+
135
+ def system_term(s)
136
+ false
137
+ end
138
+
139
+ def unit? s
140
+ @parser.unit? s
141
+ end
142
+
143
+ private
144
+ # Index the name by all prefixes
145
+ def index_name(index, name, value = true)
146
+ added = false
147
+ words = name.split(/\s+/)
148
+ words.inject("") do |n, w|
149
+ # Index all prefixes up to the full term
150
+ n = n.empty? ? w : "#{n} #{w}"
151
+ index[n] ||= {}
152
+ added = true unless index[n][name]
153
+ index[n][name] = value # Save all possible completions of this prefix
154
+ n
155
+ end
156
+ added
157
+ end
158
+ end
159
+
160
+ class InputProxy
161
+ attr_reader :context, :parser
162
+
163
+ def initialize(input, context, parser)
164
+ @input = input
165
+ @context = context
166
+ @parser = parser
167
+ end
168
+
169
+ def length
170
+ @input.length
171
+ end
172
+
173
+ def size
174
+ length
175
+ end
176
+
177
+ def [](*a)
178
+ @input[*a]
179
+ end
180
+
181
+ def index(*a)
182
+ @input.index(*a)
183
+ end
184
+
185
+ def line_of(x)
186
+ @input.line_of(x)
187
+ end
188
+
189
+ def column_of(x)
190
+ @input.column_of(x)
191
+ end
192
+ end
193
+
194
+ def context
195
+ @context ||= Context.new(self)
196
+ end
197
+
198
+ def unit?(s)
199
+ # puts "Asking whether #{s.inspect} is a unit"
200
+ true
201
+ end
202
+
203
+ def parse(input, options = {})
204
+ input = InputProxy.new(input, context, self) unless input.respond_to?(:context)
205
+ super(input, options)
206
+ end
207
+
208
+ def parse_all(input, rule_name = nil, &block)
209
+ self.root = rule_name if rule_name
210
+
211
+ @index = 0 # Byte offset to start next parse
212
+ @block = block
213
+ self.consume_all_input = false
214
+ nodes = []
215
+ begin
216
+ node = parse(InputProxy.new(input, context, self), :index => @index)
217
+ unless node
218
+ raise failure_reason unless @index == input.size
219
+ return nil # No input, or no more input
220
+ end
221
+ if @block
222
+ @block.call(node)
223
+ else
224
+ nodes << node
225
+ end
226
+ end until self.index == @input_length
227
+ @block ? true : nodes
228
+ end
229
+ end
230
+
231
+ end
232
+
233
+ Polyglot.register('cql', CQL::Parser)
234
+ end