activefacts 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/bin/cql +137 -91
  3. data/css/style.css +3 -3
  4. data/examples/CQL/Insurance.cql +1 -1
  5. data/examples/CQL/SeparateSubtype.cql +2 -2
  6. data/lib/activefacts/cql/Language/English.treetop +9 -0
  7. data/lib/activefacts/cql/ObjectTypes.treetop +1 -1
  8. data/lib/activefacts/cql/Terms.treetop +3 -1
  9. data/lib/activefacts/cql/ValueTypes.treetop +10 -4
  10. data/lib/activefacts/cql/compiler.rb +1 -0
  11. data/lib/activefacts/cql/compiler/clause.rb +53 -23
  12. data/lib/activefacts/cql/compiler/entity_type.rb +0 -4
  13. data/lib/activefacts/cql/compiler/expression.rb +9 -13
  14. data/lib/activefacts/cql/compiler/fact.rb +49 -48
  15. data/lib/activefacts/cql/compiler/fact_type.rb +23 -20
  16. data/lib/activefacts/cql/compiler/query.rb +49 -121
  17. data/lib/activefacts/cql/compiler/shared.rb +5 -1
  18. data/lib/activefacts/cql/compiler/value_type.rb +4 -2
  19. data/lib/activefacts/generate/rails/schema.rb +138 -108
  20. data/lib/activefacts/generate/transform/surrogate.rb +1 -2
  21. data/lib/activefacts/mapping/rails.rb +52 -45
  22. data/lib/activefacts/persistence/columns.rb +5 -5
  23. data/lib/activefacts/persistence/tables.rb +6 -4
  24. data/lib/activefacts/support.rb +0 -2
  25. data/lib/activefacts/version.rb +1 -1
  26. data/lib/activefacts/vocabulary/extensions.rb +64 -42
  27. data/lib/activefacts/vocabulary/metamodel.rb +14 -12
  28. data/lib/activefacts/vocabulary/verbaliser.rb +98 -92
  29. data/spec/cql/expressions_spec.rb +8 -3
  30. data/spec/cql/parser/entity_types_spec.rb +1 -1
  31. data/spec/cql/parser/expressions_spec.rb +66 -52
  32. data/spec/cql/parser/fact_types_spec.rb +1 -1
  33. data/spec/cql/parser/literals_spec.rb +10 -10
  34. data/spec/cql/parser/pragmas_spec.rb +3 -3
  35. data/spec/cql/parser/value_types_spec.rb +1 -1
  36. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e791a369dca69a80ccd666e7eec9441e9f00483
4
- data.tar.gz: d894b07ab82cfdf942a8e93561fa86a8a3bedd8a
3
+ metadata.gz: 8ed82ab2bf0c4e8d8dc9fce824d69e9fff87ab0b
4
+ data.tar.gz: 03644b12bd3d6717a457a7526342b797966fb712
5
5
  SHA512:
6
- metadata.gz: c5f05a7b8b3b116f478b55819baba8162b12c8807122f9825474c93819a9f0f3255dd5c2040eaf54dd59e6e445c5572bcd1d82267d3b9312af140ae8b51d8ce5
7
- data.tar.gz: e6d40c1c950bbca20c96700002c88fa640c1a019c3dfe72b4e88da5a549d401d6d5d87eead9c20d6ced53c688750f7458f2ae598181a6a9c02d84775c6407ca4
6
+ metadata.gz: 07c66c621a9009612baa4e90abfb03dfee833e8daba654089f75f80ee14fe2881aab1e7e9e90c0b55109f80e5472cc7a05bde2f9dc1d1534ae3357e4097bcf8a
7
+ data.tar.gz: d10faaa64f3c03eacc742888310a0ddb88158e9de646c80c3176040e970bf61e5e67f6a81d014b7acc794b8c91bf27f874391ef0c6b7a66d6210176b10bf0171
data/bin/cql CHANGED
@@ -9,6 +9,7 @@ require 'readline'
9
9
 
10
10
  require 'activefacts'
11
11
  require 'activefacts/cql/compiler'
12
+ require 'activefacts/vocabulary/query_evaluator'
12
13
 
13
14
  class InteractiveCQL < ActiveFacts::CQL::Compiler
14
15
  def initialize *a
@@ -16,6 +17,7 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
16
17
  @show_highlight = false # Show the highlighted CQL text from the parse tree
17
18
  super *a
18
19
  self.root = :definition
20
+ @population_name = '' # Use the default population
19
21
  end
20
22
 
21
23
  def add_logger logger
@@ -70,20 +72,20 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
70
72
 
71
73
  def metacommand(line)
72
74
  # meta-commands start with /
73
- words = line.split
74
- case cmd = words.shift
75
- when "/trace"
76
- if words.empty?
77
- puts trace_keys*", "
78
- else
79
- toggle_trace words
80
- end
81
- when "/tree"
82
- @show_tree = !@show_tree
83
- puts "Will #{@show_tree ? "" : "not "}show the parse tree"
75
+ cmd = line.sub(/(\A\/\p{Alpha}*).*/,'\1')
76
+ words = line[cmd.size..-1].split
77
+ case cmd
78
+ when "/about"
79
+ list_connotations (words*' ')
80
+ when "/cql"
81
+ process(words*' ')
84
82
  when "/highlight"
85
83
  @show_highlight = !@show_highlight
86
84
  puts "Will #{@show_highlight ? "" : "not "}show the highlighted parse"
85
+ when "/list"
86
+ list_object_types
87
+ when "/load"
88
+ load_file(words*' ')
87
89
  when "/log"
88
90
  @logger ||= proc {|*a|
89
91
  unless @undoing
@@ -91,26 +93,42 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
91
93
  end
92
94
  }
93
95
  add_logger @logger
94
- when "/timings"
95
- $show_timings = !$show_timings
96
- puts "Will #{$show_timings ? "" : "not "}show command timings"
96
+ when "/population"
97
+ # REVISIT: Set default population for queries
98
+ if words == ['?']
99
+ puts "Defined populations are: "+@constellation.Population.keys.map{|v, p| p.empty? ? "<default>" : p}*', '
100
+ else
101
+ @population_name = words[0] || ''
102
+ @vocabulary = @constellation.Vocabulary.values.first
103
+ @population =
104
+ if @vocabulary
105
+ @constellation.Population[[[@vocabulary.name], @population_name]]
106
+ else
107
+ nil
108
+ end
109
+ puts "Using #{@population ? 'existing ' : ''}population #{@population_name}"
110
+ end
97
111
  when "/root"
98
112
  self.root = words[0] && words[0].to_sym || :definition
99
113
  puts "Looking for a #{self.root}"
100
- when "/list"
101
- list_object_types
102
- when "/about"
103
- words.each do |word|
104
- list_connotations word
114
+ when "/timings"
115
+ $show_timings = !$show_timings
116
+ puts "Will #{$show_timings ? "" : "not "}show command timings"
117
+ when "/trace"
118
+ if words.empty?
119
+ puts trace_keys*", "
120
+ else
121
+ toggle_trace words
105
122
  end
106
- when "/load"
107
- load_file(words*' ')
123
+ when "/tree"
124
+ @show_tree = !@show_tree
125
+ puts "Will #{@show_tree ? "" : "not "}show the parse tree"
108
126
  when "/quit"
109
127
  exit
110
128
  when "/help", "/"
111
129
  help words
112
130
  else
113
- puts "Unknown metacommand #{line}?"
131
+ puts "Unknown metacommand #{line}"
114
132
  help
115
133
  end
116
134
  end
@@ -130,53 +148,67 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
130
148
  end
131
149
  end
132
150
 
133
- def process_query(query)
134
- result_projection = {} # A set of {hash of Variable to Instance}
135
- query_block =
136
- proc do |unbound_variables = [], bindings = {}|
137
- # unbound_variables is an array of the remaining dimensions of the search
138
- # bindings is a hash of Varable to Instance
139
- next_variable = unbound_variables[0]
140
- if next_variable
141
- ins = next_variable.object_type.all_instance
142
- trace :result, "Query search through #{ins.size} #{next_variable.role_name || next_variable.object_type.name}" do
143
- next_variable.object_type.all_instance.each do |instance|
144
- trace :result, "Considering #{instance.verbalise}"
145
- # REVISIT: Check whether this instance satisfies the query steps that contain the bound variables
146
- (b = bindings.dup).store(next_variable, instance)
147
- result_projection[b] = true
148
- query_block.call(unbound_variables[1..-1], b)
149
- end
150
- end
151
- end
151
+ def query(query)
152
+ @population = @constellation.Population[[[@vocabulary.name], @population_name]]
153
+ unless @population
154
+ puts "Population #{@population_name.inspect} has not yet been instantiated"
155
+ return
156
+ end
157
+
158
+ q = ActiveFacts::Metamodel::QueryEvaluator.new(query, @population)
159
+ results = q.evaluate
160
+ verbalise_results(q, results)
161
+ end
162
+
163
+ def verbalise_results(q, results)
164
+ trace :result, "Found #{results.size} results:"
165
+ if q.free_variables.size == 0
166
+ if results.size > 0
167
+ puts "Yes."
168
+ else
169
+ puts "No."
152
170
  end
171
+ return
172
+ end
153
173
 
154
- unbound_variables = query.all_variable.to_a
155
- bound_variables = {}
156
- unbound_variables.reject!{|v| v.value != nil ? bound_variables[v] = v.value : nil }
157
- print 'bound variables: '; p bound_variables.keys.map{|v| [v.object_type.name, v.value.inspect] }
158
- print 'unbound variables: '; p unbound_variables.map{|v| v.object_type.name}
174
+ return if verbalise_single_fact_result(q, results)
159
175
 
160
- query_block.call(unbound_variables, {})
161
- result_projection.map do |result, _|
162
- puts(result.map{|v, i| i.verbalise }.join(', '))
176
+ # No smart verbalisation for this scenario, just show the variable values:
177
+ results.map do |result, _|
178
+ puts(result.map{|var, i| i.verbalise }.join(', '))
163
179
  end
180
+ end
164
181
 
165
- =begin
166
- if query.all_variable.size == 1 and
167
- (v = query.all_variable.single).all_play.map{|p| p.all_step.to_a}.flatten.uniq.size == 0
168
- if v.object_type.all_instance.size == 0
169
- puts "There are no instances of #{v.object_type.name}"
170
- else
171
- list_instances(v.object_type)
182
+ def verbalise_single_fact_result(q, results)
183
+ # If all unbound variables play in the same step, just expand the fact type's reading with the values
184
+ # REVISIT: Ignore steps over facts that are existential for other variable's object types
185
+ trace :result, "Attempting single fact verbalisation" do
186
+ common_steps =
187
+ q.free_variables.inject(nil) do |memo, var|
188
+ steps = (var.all_play.map{|p| p.step} + Array(var.step)).uniq
189
+ trace :result, "variable #{var.object_type.name} spans steps #{steps.map(&:fact_type).map(&:default_reading).inspect}"
190
+ steps.reject! do |step|
191
+ step.fact_type.is_existential
192
+ end
193
+ memo ? memo & steps : steps
194
+ end
195
+ trace :result, "There are #{common_steps.size} common steps for attempted single fact verbalisation"
196
+ if step = common_steps[0]
197
+ reading = step.fact_type.preferred_reading
198
+ reading_roles = reading.role_sequence.all_role_ref_in_order.map(&:role)
199
+ reading_variables = reading_roles.map { |role| step.all_play.detect{|p| p.role == role}.variable }
200
+
201
+ results.map do |result, _|
202
+ # Verbalise result the fact, don't just dump the variable values
203
+ literals = reading_variables.map{|v| [nil, result[v].verbalise] }
204
+ puts (step.is_disallowed ? 'it is not the case that ' : '') +
205
+ reading.expand(literals) +
206
+ ';'
207
+ end
208
+ return true
172
209
  end
173
- else
174
- # REVISIT: Identify all the unbound Variables, and do a nested-loops iteration checking all Steps
175
- puts "Can't yet process complex queries (involving #{
176
- query.all_variable.map{|v| v.object_type.name}
177
- }*', '})"
210
+ return false
178
211
  end
179
- =end
180
212
  end
181
213
 
182
214
  def process(statement)
@@ -184,7 +216,7 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
184
216
  @results = []
185
217
  compile(statement)
186
218
  if @results.size == 1 && @results[0].is_a?(ActiveFacts::Metamodel::Query)
187
- process_query(@results[0])
219
+ query(@results[0])
188
220
  else
189
221
  puts(@results.map{|r| "\t"+r.inspect}*"\n")
190
222
  end
@@ -278,16 +310,21 @@ class InteractiveCQL < ActiveFacts::CQL::Compiler
278
310
  puts %Q{
279
311
  Meta-commands are:
280
312
  /about term\t\tDisplay the fact types in which term plays a part
281
- /help\t\t\tThis help message
313
+ /cql text\t\tProcess this single CQL definition or query
282
314
  /help topic\t\thelp on a specific topic
283
315
  /help topics\t\tList the available help topics
284
- /highlight\t\t\tRe-display the parsed CQL with HTML highlighting markup
316
+ /help\t\t\tThis help message
317
+ /highlight\t\tRe-display the parsed CQL with HTML highlighting markup
285
318
  /list\t\t\tList all object type names (terms)
286
319
  /load file.cql\tLoad a CQL file
320
+ /population [? | name] List or set the active population
321
+ /quit\t\t\tExit CQL
287
322
  /root rule\t\tParse just a fragment of a CQL statement, matching syntax rule only
288
- /timings\t\t\tDisplay the elapsed time to execute each command
289
- /tree\t\t\tDisplay the abstract syntax tree from each statement parsed
323
+ /timings\t\tDisplay the elapsed time to execute each command
290
324
  /trace key\t\tToggle tracing key, or list available keys
325
+ /tree\t\t\tDisplay the abstract syntax tree from each statement parsed
326
+
327
+ Meta-commands are available on the shell command-line - use -- instead of /
291
328
  }
292
329
  else
293
330
  words.each do |word|
@@ -503,34 +540,43 @@ end
503
540
  compiler = InteractiveCQL.new
504
541
  statement = nil
505
542
  loaded_files = false
506
- ARGV.each do |arg|
543
+ args = ARGV.dup
544
+ while !args.empty?
545
+ arg = args.shift # Gather up the arguments until the next one starting with -
507
546
  if arg =~ /^--(.*)/
508
- compiler.metacommand('/'+$1)
509
- else
510
- loaded_files = false or break if arg == '-'
547
+ arg = '/'+$1
548
+ while args[0] and args[0] !~ /^-/
549
+ arg = arg+' '+args.shift
550
+ end
551
+ compiler.metacommand arg
552
+ elsif arg == '-' # Either "stdin" or immediate-command
553
+ unless args.empty?
554
+ compiler.process args*' '
555
+ exit
556
+ end
557
+ break
558
+ else # Just a file
511
559
  compiler.load_file(arg)
512
- loaded_files = true
560
+ exit if args.empty?
513
561
  end
514
562
  end
515
563
 
516
- if !loaded_files
517
- puts "Enter / for help on special commands"
518
-
519
- while line = Readline::readline(statement ? "CQL+ " : "CQL? ", [])
520
- statement = statement ? statement + "\n"+line : line
521
- start = Time.now
522
- case
523
- when line =~ %r{\A/}
524
- compiler.metacommand(line)
525
- statement = nil
526
- when compiler.root != :definition || line.gsub(/(['"])([^\1\\]|\\.)*\1/,'') =~ /[;?]/
527
- # After stripping string literals the line contains a ';' or /?', we've found the last line of the command:
528
- compiler.process(statement)
529
- statement = nil
530
- end
531
- if $show_timings && statement == nil
532
- puts "Done in #{((Time.now.to_f-start.to_f)*1000000).to_i} usec"
533
- end
564
+ puts "Enter / for help on special commands"
565
+
566
+ while line = Readline::readline(statement ? "CQL+ " : "CQL? ", [])
567
+ statement = statement ? statement + "\n"+line : line
568
+ start = Time.now
569
+ case
570
+ when line =~ %r{\A/}
571
+ compiler.metacommand(line)
572
+ statement = nil
573
+ when compiler.root != :definition || line.gsub(/(['"])([^\1\\]|\\.)*\1/,'') =~ /[;?]/
574
+ # After stripping string literals the line contains a ';' or /?', we've found the last line of the command:
575
+ compiler.process(statement)
576
+ statement = nil
577
+ end
578
+ if $show_timings && statement == nil
579
+ puts "Done in #{((Time.now.to_f-start.to_f)*1000000).to_i} usec"
534
580
  end
535
- puts
536
581
  end
582
+ puts
@@ -328,14 +328,14 @@ tt,
328
328
  code,
329
329
  pre
330
330
  {
331
- font-family : Consolas, "Lucida Console", monospace;
331
+ font-family : monospace;
332
332
  }
333
333
 
334
334
  tt
335
335
  {
336
336
  font-weight : bold;
337
- color : #A52A2A;
338
- background-color : #FFFAF0;
337
+ /* color : #A52A2A; */
338
+ /* background-color : #FFFAF0; */
339
339
  }
340
340
 
341
341
  /* output of syntax colorizer */
@@ -162,7 +162,7 @@ Vehicle Type is identified by Make and Model and Badge where
162
162
  Vehicle is of one Vehicle Type;
163
163
 
164
164
  Witness is identified by Incident and Name where
165
- Incident was seen by Witness,
165
+ Incident was independently witnessed by Witness,
166
166
  Witness saw one Incident,
167
167
  Witness is called one Name;
168
168
  Witness has at most one contact-Phone;
@@ -23,8 +23,8 @@ Vehicle Incident is a kind of Incident [separate];
23
23
 
24
24
  Witness is a kind of Person;
25
25
  Witness saw Incident,
26
- Incident was seen by at most one Witness;
26
+ Incident was independently witnessed by at most one Witness;
27
27
 
28
28
  Driver is a kind of Person;
29
- Vehicle Incident occured while at most one Driver was in charge;
29
+ Vehicle Incident occurred while at most one Driver was in charge;
30
30
 
@@ -14,6 +14,15 @@ module ActiveFacts
14
14
  s 'is' s 'written' S as s
15
15
  end
16
16
 
17
+ rule auto_assignment
18
+ 'auto-assigned' S at s time:('assert' / 'commit') !alphanumeric s
19
+ {
20
+ def auto_assigned_at
21
+ time.text_value
22
+ end
23
+ }
24
+ end
25
+
17
26
  # The pattern to introduce an Entity Type
18
27
  rule identified_by
19
28
  identified s by s
@@ -160,7 +160,7 @@ module ActiveFacts
160
160
  { # A forward-referenced entity type
161
161
  # REVISIT: A change in this rule might allow forward-referencing a multi-word term
162
162
  def ast
163
- Compiler::Reference.new(id.text_value, nil, nil, nil, ss.empty? ? nil : ss.value)
163
+ Compiler::Reference.new(id.text_value, nil, nil, nil, nil, ss.empty? ? nil : ss.value)
164
164
  end
165
165
  }
166
166
  end
@@ -18,7 +18,9 @@ module ActiveFacts
18
18
  / mapping_pragmas is_where # Objectified type
19
19
  / non_phrase
20
20
  / identified_by # as in: "a kind of X identified by..."
21
- / unit
21
+ / in_units
22
+ / auto_assignment
23
+ / value_constraint
22
24
  end
23
25
 
24
26
  rule entity_prefix
@@ -16,7 +16,8 @@ module ActiveFacts
16
16
  any? s
17
17
  base:(term/implicit_value_type_name) s
18
18
  value_type_parameters
19
- u:units?
19
+ u:in_units?
20
+ a:auto_assignment?
20
21
  c:context_note?
21
22
  r:(value_constraint enforcement)?
22
23
  m2:mapping_pragmas
@@ -30,14 +31,19 @@ module ActiveFacts
30
31
  unless r.empty?
31
32
  value_constraint = Compiler::ValueConstraint.new(r.value_constraint.ast, r.enforcement.ast)
32
33
  end
33
- units = u.empty? ? [] : u.value
34
+ units = u.empty? ? [] : u.units.value
35
+ auto_assigned_at = a.empty? ? nil : a.auto_assigned_at
34
36
  pragmas = m1.value+m2.value
35
37
  context_note = !c.empty? ? c.ast : (!c2.empty? ? c2.ast : nil)
36
- Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas, context_note
38
+ Compiler::ValueType.new name, base.value, params, units, value_constraint, pragmas, context_note, auto_assigned_at
37
39
  end
38
40
  }
39
41
  end
40
42
 
43
+ rule in_units
44
+ in S units
45
+ end
46
+
41
47
  rule implicit_value_type_name
42
48
  id
43
49
  {
@@ -130,7 +136,7 @@ module ActiveFacts
130
136
  end
131
137
 
132
138
  rule non_unit
133
- restricted_to / conversion / approximately / ephemera
139
+ restricted_to / conversion / approximately / ephemera / auto_assignment
134
140
  end
135
141
 
136
142
  rule unit
@@ -65,6 +65,7 @@ module ActiveFacts
65
65
  # parse_all returns an array of the block's non-nil return values.
66
66
  ok = parse_all(@string, :definition) do |node|
67
67
  trace :parse, "Parsed '#{node.text_value.gsub(/\s+/,' ').strip}'" do
68
+ trace :lex, (proc { node.inspect })
68
69
  begin
69
70
  ast = node.ast
70
71
  next unless ast