activefacts 0.7.0 → 0.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.
- data/README.rdoc +0 -3
- data/Rakefile +7 -5
- data/bin/afgen +5 -2
- data/bin/cql +3 -2
- data/examples/CQL/Genealogy.cql +3 -3
- data/examples/CQL/Metamodel.cql +10 -7
- data/examples/CQL/MultiInheritance.cql +2 -1
- data/examples/CQL/OilSupply.cql +4 -4
- data/examples/CQL/Orienteering.cql +2 -2
- data/lib/activefacts.rb +2 -1
- data/lib/activefacts/api.rb +21 -2
- data/lib/activefacts/api/concept.rb +52 -39
- data/lib/activefacts/api/constellation.rb +8 -6
- data/lib/activefacts/api/entity.rb +41 -37
- data/lib/activefacts/api/instance.rb +5 -3
- data/lib/activefacts/api/numeric.rb +28 -21
- data/lib/activefacts/api/role.rb +29 -43
- data/lib/activefacts/api/standard_types.rb +8 -3
- data/lib/activefacts/api/support.rb +4 -4
- data/lib/activefacts/api/value.rb +9 -3
- data/lib/activefacts/api/vocabulary.rb +17 -7
- data/lib/activefacts/cql.rb +10 -7
- data/lib/activefacts/cql/CQLParser.treetop +6 -0
- data/lib/activefacts/cql/Concepts.treetop +32 -26
- data/lib/activefacts/cql/DataTypes.treetop +6 -0
- data/lib/activefacts/cql/Expressions.treetop +6 -0
- data/lib/activefacts/cql/FactTypes.treetop +6 -0
- data/lib/activefacts/cql/Language/English.treetop +9 -3
- data/lib/activefacts/cql/LexicalRules.treetop +6 -0
- data/lib/activefacts/cql/Rakefile +8 -0
- data/lib/activefacts/cql/parser.rb +4 -2
- data/lib/activefacts/generate/absorption.rb +20 -28
- data/lib/activefacts/generate/cql.rb +28 -16
- data/lib/activefacts/generate/cql/html.rb +327 -321
- data/lib/activefacts/generate/null.rb +7 -3
- data/lib/activefacts/generate/oo.rb +19 -15
- data/lib/activefacts/generate/ordered.rb +457 -461
- data/lib/activefacts/generate/ruby.rb +12 -4
- data/lib/activefacts/generate/sql/server.rb +42 -10
- data/lib/activefacts/generate/text.rb +7 -3
- data/lib/activefacts/input/cql.rb +55 -28
- data/lib/activefacts/input/orm.rb +32 -22
- data/lib/activefacts/persistence.rb +5 -0
- data/lib/activefacts/persistence/columns.rb +66 -32
- data/lib/activefacts/persistence/foreignkey.rb +29 -5
- data/lib/activefacts/persistence/index.rb +57 -25
- data/lib/activefacts/persistence/reference.rb +65 -30
- data/lib/activefacts/persistence/tables.rb +28 -17
- data/lib/activefacts/support.rb +8 -0
- data/lib/activefacts/version.rb +7 -1
- data/lib/activefacts/vocabulary.rb +4 -2
- data/lib/activefacts/vocabulary/extensions.rb +12 -10
- data/lib/activefacts/vocabulary/metamodel.rb +24 -23
- data/spec/api/autocounter.rb +2 -2
- data/spec/api/entity_type.rb +2 -2
- data/spec/api/instance.rb +61 -30
- data/spec/api/roles.rb +9 -9
- data/spec/cql_parse_spec.rb +1 -0
- data/spec/norma_tables_spec.rb +3 -3
- metadata +8 -4
@@ -1,14 +1,22 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate Ruby classes for the ActiveFacts API from an ActiveFacts vocabulary.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
require 'activefacts/vocabulary'
|
6
8
|
require 'activefacts/generate/oo'
|
7
9
|
|
8
10
|
module ActiveFacts
|
9
|
-
|
10
11
|
module Generate
|
12
|
+
# Generate Ruby module containing classes for an ActiveFacts vocabulary.
|
13
|
+
# Invoke as
|
14
|
+
# afgen --ruby[=options] <file>.cql
|
15
|
+
# Options are comma or space separated:
|
16
|
+
# * help list available options
|
17
|
+
# * sql Emit the sql mapping for tables/columns (REVISIT: not functional at present)
|
11
18
|
class RUBY < OO
|
19
|
+
private
|
12
20
|
|
13
21
|
def set_option(option)
|
14
22
|
@sql ||= false
|
@@ -131,7 +139,7 @@ module ActiveFacts
|
|
131
139
|
end
|
132
140
|
|
133
141
|
def role_dump(role)
|
134
|
-
other_role = role.fact_type.all_role
|
142
|
+
other_role = role.fact_type.all_role.select{|r| r != role}[0] || role
|
135
143
|
if @ar_by_role and @ar_by_role[other_role] and @sql
|
136
144
|
puts " # role #{role.fact_type.describe(role)}: absorbs in through #{preferred_role_name(other_role)}: "+@ar_by_role[other_role].map(&:column_name)*", "
|
137
145
|
end
|
@@ -1,15 +1,25 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate SQL for SQL Server from an ActiveFacts vocabulary.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
require 'activefacts/vocabulary'
|
6
8
|
require 'activefacts/persistence'
|
7
9
|
|
8
10
|
module ActiveFacts
|
9
11
|
module Generate
|
10
|
-
class SQL
|
12
|
+
class SQL #:nodoc:
|
13
|
+
# Generate SQL for SQL Server for an ActiveFacts vocabulary.
|
14
|
+
# Invoke as
|
15
|
+
# afgen --sql/server[=options] <file>.cql
|
16
|
+
# Options are comma or space separated:
|
17
|
+
# * delay_fks Leave all foreign keys until the end, not just those that contain forward-references
|
18
|
+
# * norma Translate datatypes from NORMA to SQL Server
|
11
19
|
class SERVER
|
20
|
+
private
|
12
21
|
include Metamodel
|
22
|
+
include Persistence
|
13
23
|
ColumnNameMax = 40
|
14
24
|
|
15
25
|
RESERVED_WORDS = %w{
|
@@ -98,7 +108,8 @@ module ActiveFacts
|
|
98
108
|
[sql_type, length]
|
99
109
|
end
|
100
110
|
|
101
|
-
|
111
|
+
public
|
112
|
+
def generate(out = $>) #:nodoc:
|
102
113
|
@out = out
|
103
114
|
#go "CREATE SCHEMA #{@vocabulary.name}"
|
104
115
|
|
@@ -116,7 +127,9 @@ module ActiveFacts
|
|
116
127
|
column.references[0].is_simple_reference
|
117
128
|
end
|
118
129
|
|
119
|
-
columns = table.columns.
|
130
|
+
columns = table.columns.sort_by do |column|
|
131
|
+
column.name(nil)
|
132
|
+
end.map do |column|
|
120
133
|
name = escape column.name("")
|
121
134
|
padding = " "*(name.size >= ColumnNameMax ? 1 : ColumnNameMax-name.size)
|
122
135
|
type, params, restrictions = column.type
|
@@ -145,11 +158,22 @@ module ActiveFacts
|
|
145
158
|
")"
|
146
159
|
|
147
160
|
inline_fks = []
|
148
|
-
table.foreign_keys.
|
161
|
+
table.foreign_keys.sort_by do |fk|
|
162
|
+
# Put the Foreign keys in a defined order:
|
163
|
+
[ fk.to.name,
|
164
|
+
fk.to_columns.map{|col| col.name(nil).sort},
|
165
|
+
fk.from_columns.map{|col| col.name(nil).sort}
|
166
|
+
]
|
167
|
+
end.each do |fk|
|
168
|
+
# Put the column pairs in a defined order, sorting key pairs by to-name:
|
169
|
+
froms, tos = fk.from_columns.zip(fk.to_columns).sort_by { |pair|
|
170
|
+
pair[1].name(nil)
|
171
|
+
}.transpose
|
172
|
+
|
149
173
|
fk_text = "FOREIGN KEY (" +
|
150
|
-
|
174
|
+
froms.map{|column| column.name}*", " +
|
151
175
|
") REFERENCES #{escape fk.to.name} (" +
|
152
|
-
|
176
|
+
tos.map{|column| column.name}*", " +
|
153
177
|
")"
|
154
178
|
if !@delay_fks and # We don't want to delay all Fks
|
155
179
|
(tables_emitted[fk.to] or # The target table has been emitted
|
@@ -163,7 +187,10 @@ module ActiveFacts
|
|
163
187
|
indices = table.indices
|
164
188
|
inline_indices = []
|
165
189
|
delayed_indices = []
|
166
|
-
indices.
|
190
|
+
indices.sort_by do |index|
|
191
|
+
# Put the indices in a defined order:
|
192
|
+
index.columns.map(&:name)
|
193
|
+
end.each do |index|
|
167
194
|
next if index.over == table && index.is_primary # Already did the primary keys
|
168
195
|
abbreviated_column_names = index.abbreviated_column_names*""
|
169
196
|
column_names = index.column_names
|
@@ -203,11 +230,16 @@ CREATE UNIQUE CLUSTERED INDEX #{escape index.name} ON dbo.#{view_name}(#{index.c
|
|
203
230
|
end
|
204
231
|
end
|
205
232
|
|
233
|
+
private
|
206
234
|
def check_clause(column_name, restrictions)
|
207
235
|
return "" if restrictions.empty?
|
208
236
|
# REVISIT: Merge all restrictions (later; now just use the first)
|
209
237
|
" CHECK(" +
|
210
|
-
restrictions[0].all_allowed_range.
|
238
|
+
restrictions[0].all_allowed_range.sort_by do |ar|
|
239
|
+
# Put the allowed ranges into a defined order:
|
240
|
+
((min = ar.value_range.minimum_bound) && min.value) ||
|
241
|
+
((max = ar.value_range.maximum_bound) && max.value)
|
242
|
+
end.map do |ar|
|
211
243
|
vr = ar.value_range
|
212
244
|
min = vr.minimum_bound
|
213
245
|
max = vr.maximum_bound
|
@@ -1,12 +1,16 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# ActiveFacts Generators.
|
3
|
+
# Generate text output (verbalise the meta-vocabulary) for ActiveFacts vocabularies.
|
3
4
|
#
|
4
|
-
# Copyright (c)
|
5
|
-
# Author: Clifford Heath.
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
6
6
|
#
|
7
7
|
module ActiveFacts
|
8
8
|
module Generate
|
9
|
+
# Generate a text verbalisation of the metamodel constellation created for an ActiveFacts vocabulary.
|
10
|
+
# Invoke as
|
11
|
+
# afgen --text <file>.cql
|
9
12
|
class TEXT
|
13
|
+
private
|
10
14
|
def initialize(vocabulary)
|
11
15
|
@vocabulary = vocabulary
|
12
16
|
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
3
|
-
#
|
2
|
+
# ActiveFacts Vocabulary Input.
|
3
|
+
# Compile a CQL file into an ActiveFacts vocabulary.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
4
6
|
#
|
5
7
|
require 'activefacts/vocabulary'
|
6
8
|
require 'activefacts/cql/parser'
|
@@ -9,10 +11,11 @@ require 'ruby-debug'
|
|
9
11
|
|
10
12
|
module ActiveFacts
|
11
13
|
module Input #:nodoc:
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
14
|
+
# Compile CQL to an ActiveFacts vocabulary.
|
15
|
+
# Invoke as
|
16
|
+
# afgen --<generator> <file>.cql
|
15
17
|
class CQL
|
18
|
+
private
|
16
19
|
include ActiveFacts
|
17
20
|
include ActiveFacts::Metamodel
|
18
21
|
|
@@ -44,8 +47,9 @@ module ActiveFacts
|
|
44
47
|
@filename = filename
|
45
48
|
end
|
46
49
|
|
50
|
+
public
|
47
51
|
# Read the input, returning a new Vocabulary:
|
48
|
-
def read
|
52
|
+
def read #:nodoc:
|
49
53
|
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
50
54
|
|
51
55
|
@parser = ActiveFacts::CQLParser.new
|
@@ -86,6 +90,7 @@ module ActiveFacts
|
|
86
90
|
@vocabulary
|
87
91
|
end
|
88
92
|
|
93
|
+
private
|
89
94
|
def value_type(name, base_type_name, parameters, unit, ranges)
|
90
95
|
length, scale = *parameters
|
91
96
|
|
@@ -167,7 +172,7 @@ module ActiveFacts
|
|
167
172
|
clauses_by_fact_type(clauses).each do |clauses_for_fact_type|
|
168
173
|
fact_type = nil
|
169
174
|
@symbols.embedded_presence_constraints = [] # Clear embedded_presence_constraints for each fact type
|
170
|
-
debug "New Fact Type for entity #{name}" do
|
175
|
+
debug :entity, "New Fact Type for entity #{name}" do
|
171
176
|
clauses_for_fact_type.each do |clause|
|
172
177
|
type, qualifiers, reading = *clause
|
173
178
|
debug :reading, "Clause: #{clause.inspect}" do
|
@@ -179,6 +184,7 @@ module ActiveFacts
|
|
179
184
|
end
|
180
185
|
|
181
186
|
# Find the role that this entity type plays in the fact type, if any:
|
187
|
+
debug :reading, "Roles are: #{fact_type.all_role.map{|role| (role.concept == entity_type ? "*" : "") + role.concept.name }*", "}"
|
182
188
|
player_roles = fact_type.all_role.select{|role| role.concept == entity_type }
|
183
189
|
raise "#{role.concept.name} may only play one role in each identifying fact type" if player_roles.size > 1
|
184
190
|
if player_role = player_roles[0]
|
@@ -187,11 +193,14 @@ module ActiveFacts
|
|
187
193
|
raise "#{name} cannot be identified by a role in a non-binary fact type" if non_player_roles.size > 1
|
188
194
|
elsif identification
|
189
195
|
# This situation occurs when an objectified fact type has an entity identifier
|
190
|
-
raise "Entity type #{name}
|
196
|
+
#raise "Entity type #{name} cannot objectify fact type #{fact_type.describe}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
|
197
|
+
raise "Entity type #{name} cannot objectify fact type #{identification.inspect}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
|
198
|
+
debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe} with distinct identifier"
|
191
199
|
|
192
200
|
entity_type.fact_type = fact_type
|
193
201
|
fact_type_identification(fact_type, name, false)
|
194
202
|
else
|
203
|
+
debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe}"
|
195
204
|
# it's an objectified fact type, such as a subtype
|
196
205
|
entity_type.fact_type = fact_type
|
197
206
|
end
|
@@ -255,13 +264,9 @@ module ActiveFacts
|
|
255
264
|
|
256
265
|
debug :mode, "Processing Reference Mode for #{name}#{mode_fact_type ? " with existing '#{mode_fact_type.default_reading}'" : ""}"
|
257
266
|
|
258
|
-
# WARNING: Several things here depend on the method and order of creation of roles and role sequences above
|
259
|
-
# But heck, that's what tests are for, right?
|
260
|
-
|
261
267
|
# Fact Type:
|
262
268
|
if (ft = mode_fact_type)
|
263
|
-
entity_role = ft.all_role
|
264
|
-
value_role = ft.all_role[1-n]
|
269
|
+
entity_role, value_role = ft.all_role.partition{|role| role.concept == entity_type}.flatten
|
265
270
|
else
|
266
271
|
ft = @constellation.FactType(:new)
|
267
272
|
entity_role = @constellation.Role(ft, 0, entity_type)
|
@@ -271,23 +276,28 @@ module ActiveFacts
|
|
271
276
|
|
272
277
|
# Forward reading, if it doesn't already exist:
|
273
278
|
rss = entity_role.all_role_ref.map{|rr| rr.role_sequence.all_role_ref.size == 2 ? rr.role_sequence : nil }.compact
|
274
|
-
|
275
|
-
|
279
|
+
# Find or create RoleSequences for the forward and reverse readings:
|
280
|
+
rs01 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [entity_role, value_role] }[0]
|
276
281
|
if !rs01
|
277
282
|
rs01 = @constellation.RoleSequence(:new)
|
278
283
|
@constellation.RoleRef(rs01, 0, :role => entity_role)
|
279
284
|
@constellation.RoleRef(rs01, 1, :role => value_role)
|
285
|
+
end
|
286
|
+
if rs01.all_reading.empty?
|
280
287
|
@constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs01, :reading_text => "{0} has {1}")
|
281
288
|
debug :mode, "Creating new forward reading '#{name} has #{vt.name}'"
|
282
289
|
else
|
283
|
-
debug :mode, "Using existing forward reading
|
290
|
+
debug :mode, "Using existing forward reading"
|
284
291
|
end
|
285
292
|
|
286
293
|
# Reverse reading:
|
294
|
+
rs10 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [value_role, entity_role] }[0]
|
287
295
|
if !rs10
|
288
296
|
rs10 = @constellation.RoleSequence(:new)
|
289
297
|
@constellation.RoleRef(rs10, 0, :role => value_role)
|
290
298
|
@constellation.RoleRef(rs10, 1, :role => entity_role)
|
299
|
+
end
|
300
|
+
if rs10.all_reading.empty?
|
291
301
|
@constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs10, :reading_text => "{0} is of {1}")
|
292
302
|
debug :mode, "Creating new reverse reading '#{vt.name} is of #{name}'"
|
293
303
|
else
|
@@ -552,6 +562,9 @@ module ActiveFacts
|
|
552
562
|
|
553
563
|
# Create the role sequences and their role references.
|
554
564
|
# Each role sequence contain one RoleRef for each common binding
|
565
|
+
# REVISIT: This results in ordering all RoleRefs according to the order of the common_bindings.
|
566
|
+
# This for example means that a set constraint having joins might have the join order changed so they all match.
|
567
|
+
# When you create e.g. a subset constraint in NORMA, make sure that the subset roles are created in the order of the preferred readings.
|
555
568
|
role_sequences = readings_list.map{|r| @constellation.RoleSequence(:new) }
|
556
569
|
common_bindings.each_with_index do |binding, index|
|
557
570
|
role_sequences.each_with_index do |rs, rsi|
|
@@ -572,6 +585,8 @@ module ActiveFacts
|
|
572
585
|
|
573
586
|
#puts "subset_constraint:\n\t#{subset_readings.inspect}\n\t#{superset_readings.inspect}"
|
574
587
|
#puts "\t#{role_sequences.map{|rs| rs.describe}.inspect}"
|
588
|
+
#puts "subset_role_sequence = #{role_sequences[0].describe}"
|
589
|
+
#puts "superset_role_sequence = #{role_sequences[1].describe}"
|
575
590
|
|
576
591
|
# create the constraint:
|
577
592
|
constraint = @constellation.SubsetConstraint(:new)
|
@@ -591,8 +606,8 @@ module ActiveFacts
|
|
591
606
|
# Create the constraint:
|
592
607
|
constraint = @constellation.SetExclusionConstraint(:new)
|
593
608
|
constraint.vocabulary = @vocabulary
|
594
|
-
role_sequences.
|
595
|
-
@constellation.SetComparisonRoles(constraint, rs)
|
609
|
+
role_sequences.each_with_index do |rs, i|
|
610
|
+
@constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
|
596
611
|
end
|
597
612
|
constraint.is_mandatory = quantifier[0] == 1
|
598
613
|
end
|
@@ -605,8 +620,8 @@ module ActiveFacts
|
|
605
620
|
# Create the constraint:
|
606
621
|
constraint = @constellation.SetEqualityConstraint(:new)
|
607
622
|
constraint.vocabulary = @vocabulary
|
608
|
-
role_sequences.
|
609
|
-
@constellation.SetComparisonRoles(constraint, rs)
|
623
|
+
role_sequences.each_with_index do |rs, i|
|
624
|
+
@constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
|
610
625
|
end
|
611
626
|
end
|
612
627
|
|
@@ -666,6 +681,9 @@ module ActiveFacts
|
|
666
681
|
rr.role = role
|
667
682
|
end
|
668
683
|
#puts "New external PresenceConstraint with quantifier = #{quantifier.inspect} over #{rs.describe}"
|
684
|
+
|
685
|
+
# REVISIT: Check that no existing PC spans the same roles (nor a superset nor subset?)
|
686
|
+
|
669
687
|
@constellation.PresenceConstraint(
|
670
688
|
:new,
|
671
689
|
:name => '',
|
@@ -679,6 +697,8 @@ module ActiveFacts
|
|
679
697
|
)
|
680
698
|
end
|
681
699
|
|
700
|
+
# Search the supertypes of 'subtype' looking for an inheritance path to 'supertype',
|
701
|
+
# and returning the array of TypeInheritance fact types from supertype to subtype.
|
682
702
|
def inheritance_path(subtype, supertype)
|
683
703
|
direct_inheritance = subtype.all_supertype_inheritance.select{|ti| ti.supertype == supertype}
|
684
704
|
return direct_inheritance if (direct_inheritance[0])
|
@@ -692,12 +712,16 @@ module ActiveFacts
|
|
692
712
|
# For a given reading from the parser, find the matching declared reading, and return
|
693
713
|
# the array of Role object in the same order as they occur in the reading.
|
694
714
|
def invoked_fact_roles(reading)
|
715
|
+
# REVISIT: Possibly this special reading from the parser can be removed now?
|
695
716
|
if (reading[0] == "!SUBTYPE!")
|
696
717
|
subtype = reading[1][:binding].concept
|
697
718
|
supertype = reading[2][:binding].concept
|
698
719
|
raise "#{subtype.name} is not a subtype of #{supertype.name}" unless subtype.supertypes_transitive.include?(supertype)
|
699
720
|
ip = inheritance_path(subtype, supertype)
|
700
|
-
return [
|
721
|
+
return [
|
722
|
+
ip[-1].all_role.detect{|r| r.concept == subtype},
|
723
|
+
ip[0].all_role.detect{|r| r.concept == supertype}
|
724
|
+
]
|
701
725
|
end
|
702
726
|
|
703
727
|
bindings = reading.select{|p| Hash === p}
|
@@ -721,7 +745,7 @@ module ActiveFacts
|
|
721
745
|
to_match = reading.clone
|
722
746
|
players_to_match = players.clone
|
723
747
|
candidate_reading.words_and_role_refs.each do |wrr|
|
724
|
-
if (RoleRef
|
748
|
+
if (wrr.is_a?(RoleRef))
|
725
749
|
break unless Hash === to_match.first
|
726
750
|
break unless binding = to_match[0][:binding]
|
727
751
|
# REVISIT: May need to match super- or sub-types here too!
|
@@ -745,7 +769,7 @@ module ActiveFacts
|
|
745
769
|
# we need to accumulate all possible matches to be sure
|
746
770
|
# there's only one, or the match is exact, or risk ambiguity.
|
747
771
|
debug "Reading match was #{to_match.size == 0 ? "ok" : "bad"}"
|
748
|
-
return candidate_reading.role_sequence.all_role_ref.map{|rr| rr.role} if to_match.size == 0
|
772
|
+
return candidate_reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role} if to_match.size == 0
|
749
773
|
end
|
750
774
|
end
|
751
775
|
end
|
@@ -753,7 +777,7 @@ module ActiveFacts
|
|
753
777
|
|
754
778
|
# Hmm, that didn't work, try the subtypes of the first player.
|
755
779
|
# When a fact type matches like this, there is an implied join to the subtype.
|
756
|
-
players[0].subtypes.
|
780
|
+
players[0].subtypes.each do |subtype|
|
757
781
|
players[0] = subtype
|
758
782
|
fr = invoked_fact_roles_by_players(reading, players)
|
759
783
|
return fr if fr
|
@@ -831,7 +855,9 @@ module ActiveFacts
|
|
831
855
|
|
832
856
|
def fact_type_identification(fact_type, name, prefer)
|
833
857
|
if !@symbols.embedded_presence_constraints.detect{|pc| pc.max_frequency == 1}
|
834
|
-
|
858
|
+
# Provide a default identifier for a fact type that's lacking one (over all roles):
|
859
|
+
first_role_sequence = fact_type.preferred_reading.role_sequence
|
860
|
+
#puts "Creating PC for #{name}: #{fact_type.describe}"
|
835
861
|
identifier = @constellation.PresenceConstraint(
|
836
862
|
:new,
|
837
863
|
:vocabulary => @vocabulary,
|
@@ -887,6 +913,7 @@ module ActiveFacts
|
|
887
913
|
constraint = find_pc_over_roles(constrained_roles)
|
888
914
|
if constraint
|
889
915
|
debug "Setting max frequency to #{quantifier[1]} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
|
916
|
+
raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != quantifier[1]
|
890
917
|
constraint.max_frequency = quantifier[1]
|
891
918
|
else
|
892
919
|
role_sequence = @constellation.RoleSequence(:new)
|
@@ -917,11 +944,11 @@ module ActiveFacts
|
|
917
944
|
ring_constraints, qualifiers = qualifiers.partition{|q| RingTypes.include?(q) }
|
918
945
|
unless ring_constraints.empty?
|
919
946
|
# A Ring may be over a supertype/subtype pair, and this won't find that.
|
920
|
-
role_refs = role_sequence.all_role_ref
|
947
|
+
role_refs = Array(role_sequence.all_role_ref)
|
921
948
|
role_pairs = []
|
922
949
|
player_supertypes_by_role = role_refs.map{|rr|
|
923
950
|
concept = rr.role.concept
|
924
|
-
EntityType
|
951
|
+
concept.is_a?(EntityType) ? supertypes(concept) : [concept]
|
925
952
|
}
|
926
953
|
role_refs.each_with_index{|rr1, i|
|
927
954
|
player1 = rr1.role.concept
|
@@ -967,7 +994,7 @@ module ActiveFacts
|
|
967
994
|
return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
|
968
995
|
roles[0].all_role_ref.each do |role_ref|
|
969
996
|
next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
|
970
|
-
pc = role_ref.role_sequence.all_presence_constraint
|
997
|
+
pc = role_ref.role_sequence.all_presence_constraint.only # Will throw an exception if there's more than one.
|
971
998
|
#puts "Existing PresenceConstraint matches those roles!" if pc
|
972
999
|
return pc if pc
|
973
1000
|
end
|
@@ -1,7 +1,8 @@
|
|
1
1
|
#
|
2
|
-
#
|
2
|
+
# ActiveFacts Vocabulary Input.
|
3
|
+
# Read a NORMA file into an ActiveFacts vocabulary
|
3
4
|
#
|
4
|
-
# Copyright (c)
|
5
|
+
# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
|
5
6
|
#
|
6
7
|
# This code uses variables prefixed with x_ when they refer to Rexml nodes.
|
7
8
|
# Every node having an id="..." is indexed in @x_by_id[] hash before processing.
|
@@ -13,10 +14,12 @@ require 'activefacts/vocabulary'
|
|
13
14
|
|
14
15
|
module ActiveFacts
|
15
16
|
module Input
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
17
|
+
# Compile a NORMA (.orm) file to an ActiveFacts vocabulary.
|
18
|
+
# Invoke as
|
19
|
+
# afgen --<generator> <file>.orm
|
20
|
+
# This parser uses Rexml so it's very slow.
|
19
21
|
class ORM
|
22
|
+
private
|
20
23
|
def self.readfile(filename)
|
21
24
|
File.open(filename) {|file|
|
22
25
|
self.read(file, filename)
|
@@ -32,7 +35,8 @@ module ActiveFacts
|
|
32
35
|
@filename = filename
|
33
36
|
end
|
34
37
|
|
35
|
-
|
38
|
+
public
|
39
|
+
def read #:nodoc:
|
36
40
|
begin
|
37
41
|
@document = REXML::Document.new(@file)
|
38
42
|
rescue => e
|
@@ -56,6 +60,8 @@ module ActiveFacts
|
|
56
60
|
@vocabulary
|
57
61
|
end
|
58
62
|
|
63
|
+
private
|
64
|
+
|
59
65
|
def read_vocabulary
|
60
66
|
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
61
67
|
@vocabulary = @constellation.Vocabulary(@x_model.attributes['Name'])
|
@@ -420,8 +426,9 @@ module ActiveFacts
|
|
420
426
|
end
|
421
427
|
|
422
428
|
def extract_adjectives(reading_text, role_sequence)
|
423
|
-
|
424
|
-
|
429
|
+
all_role_refs = role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
|
430
|
+
(0...all_role_refs.size).each{|i|
|
431
|
+
role_ref = all_role_refs[i]
|
425
432
|
role = role_ref.role
|
426
433
|
|
427
434
|
word = '\b([A-Za-z][A-Za-z0-9_]*)\b'
|
@@ -455,7 +462,7 @@ module ActiveFacts
|
|
455
462
|
# REVISIT: This searches all role sequences. Perhaps we could narrow it down first instead?
|
456
463
|
role_sequence = @constellation.RoleSequence.values.detect{|c|
|
457
464
|
#puts "Checking RoleSequence [#{c.all_role_ref.map{|rr| rr.role.concept.name}*", "}]"
|
458
|
-
role_array == c.all_role_ref.map{|rr| rr.role }
|
465
|
+
role_array == c.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role }
|
459
466
|
}
|
460
467
|
# puts "Found matching RoleSequence!" if role_sequence
|
461
468
|
return role_sequence if role_sequence
|
@@ -622,12 +629,15 @@ module ActiveFacts
|
|
622
629
|
# (pi ? " (preferred id for #{pi.name})" : "") +
|
623
630
|
# (mc ? " (mandatory)" : "") if pi && !mc
|
624
631
|
|
625
|
-
# A TypeInheritance fact type has a
|
626
|
-
# If
|
627
|
-
|
628
|
-
|
629
|
-
role
|
630
|
-
role.fact_type
|
632
|
+
# A TypeInheritance fact type has a uniqueness constraint on each role.
|
633
|
+
# If this UC is on the supertype and identifies the subtype, it's preferred:
|
634
|
+
is_supertype_constraint =
|
635
|
+
roles.all_role_ref.size == 1 &&
|
636
|
+
(role = roles.all_role_ref.only.role) &&
|
637
|
+
(fact_type = role.fact_type) &&
|
638
|
+
fact_type.is_a?(ActiveFacts::Metamodel::TypeInheritance) &&
|
639
|
+
role.concept == fact_type.supertype &&
|
640
|
+
fact_type.provides_identification
|
631
641
|
|
632
642
|
pc = @constellation.PresenceConstraint(:new)
|
633
643
|
pc.vocabulary = @vocabulary
|
@@ -636,7 +646,7 @@ module ActiveFacts
|
|
636
646
|
pc.is_mandatory = true if mc
|
637
647
|
pc.min_frequency = mc ? 1 : 0
|
638
648
|
pc.max_frequency = 1
|
639
|
-
pc.is_preferred_identifier = true if pi || unary_identifier ||
|
649
|
+
pc.is_preferred_identifier = true if pi || unary_identifier || is_supertype_constraint
|
640
650
|
#puts "#{name} covers #{roles.describe} has min=#{pc.min_frequency}, max=1, preferred=#{pc.is_preferred_identifier.inspect}" if emit_special_debug
|
641
651
|
|
642
652
|
#puts roles.verbalise
|
@@ -673,9 +683,9 @@ module ActiveFacts
|
|
673
683
|
ec.vocabulary = @vocabulary
|
674
684
|
ec.name = name
|
675
685
|
# ec.enforcement =
|
676
|
-
role_sequences.
|
677
|
-
@constellation.SetComparisonRoles(ec, rs)
|
678
|
-
|
686
|
+
role_sequences.each_with_index do |rs, i|
|
687
|
+
@constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
|
688
|
+
end
|
679
689
|
ec.is_mandatory = true if x_mandatory
|
680
690
|
}
|
681
691
|
end
|
@@ -699,9 +709,9 @@ module ActiveFacts
|
|
699
709
|
ec.vocabulary = @vocabulary
|
700
710
|
ec.name = name
|
701
711
|
# ec.enforcement =
|
702
|
-
role_sequences.
|
703
|
-
@constellation.SetComparisonRoles(ec, rs)
|
704
|
-
|
712
|
+
role_sequences.each_with_index do |rs, i|
|
713
|
+
@constellation.SetComparisonRoles(ec, i, :role_sequence => rs)
|
714
|
+
end
|
705
715
|
}
|
706
716
|
end
|
707
717
|
|