activefacts 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|