activefacts 1.1.0 → 1.2.0
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.
- checksums.yaml +4 -4
- data/bin/cql +137 -91
- data/css/style.css +3 -3
- data/examples/CQL/Insurance.cql +1 -1
- data/examples/CQL/SeparateSubtype.cql +2 -2
- data/lib/activefacts/cql/Language/English.treetop +9 -0
- data/lib/activefacts/cql/ObjectTypes.treetop +1 -1
- data/lib/activefacts/cql/Terms.treetop +3 -1
- data/lib/activefacts/cql/ValueTypes.treetop +10 -4
- data/lib/activefacts/cql/compiler.rb +1 -0
- data/lib/activefacts/cql/compiler/clause.rb +53 -23
- data/lib/activefacts/cql/compiler/entity_type.rb +0 -4
- data/lib/activefacts/cql/compiler/expression.rb +9 -13
- data/lib/activefacts/cql/compiler/fact.rb +49 -48
- data/lib/activefacts/cql/compiler/fact_type.rb +23 -20
- data/lib/activefacts/cql/compiler/query.rb +49 -121
- data/lib/activefacts/cql/compiler/shared.rb +5 -1
- data/lib/activefacts/cql/compiler/value_type.rb +4 -2
- data/lib/activefacts/generate/rails/schema.rb +138 -108
- data/lib/activefacts/generate/transform/surrogate.rb +1 -2
- data/lib/activefacts/mapping/rails.rb +52 -45
- data/lib/activefacts/persistence/columns.rb +5 -5
- data/lib/activefacts/persistence/tables.rb +6 -4
- data/lib/activefacts/support.rb +0 -2
- data/lib/activefacts/version.rb +1 -1
- data/lib/activefacts/vocabulary/extensions.rb +64 -42
- data/lib/activefacts/vocabulary/metamodel.rb +14 -12
- data/lib/activefacts/vocabulary/verbaliser.rb +98 -92
- data/spec/cql/expressions_spec.rb +8 -3
- data/spec/cql/parser/entity_types_spec.rb +1 -1
- data/spec/cql/parser/expressions_spec.rb +66 -52
- data/spec/cql/parser/fact_types_spec.rb +1 -1
- data/spec/cql/parser/literals_spec.rb +10 -10
- data/spec/cql/parser/pragmas_spec.rb +3 -3
- data/spec/cql/parser/value_types_spec.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ed82ab2bf0c4e8d8dc9fce824d69e9fff87ab0b
|
4
|
+
data.tar.gz: 03644b12bd3d6717a457a7526342b797966fb712
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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 "/
|
95
|
-
|
96
|
-
|
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 "/
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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 "/
|
107
|
-
|
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
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
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
|
-
|
161
|
-
|
162
|
-
puts(result.map{|
|
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
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
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
|
-
|
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
|
-
/
|
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
|
-
/
|
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\
|
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.
|
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
|
-
|
509
|
-
|
510
|
-
|
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
|
-
|
560
|
+
exit if args.empty?
|
513
561
|
end
|
514
562
|
end
|
515
563
|
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
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
|
data/css/style.css
CHANGED
@@ -328,14 +328,14 @@ tt,
|
|
328
328
|
code,
|
329
329
|
pre
|
330
330
|
{
|
331
|
-
font-family :
|
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 */
|
data/examples/CQL/Insurance.cql
CHANGED
@@ -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
|
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
|
26
|
+
Incident was independently witnessed by at most one Witness;
|
27
27
|
|
28
28
|
Driver is a kind of Person;
|
29
|
-
Vehicle Incident
|
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
|
@@ -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:
|
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
|