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,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