activefacts 0.6.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.
- data/History.txt +4 -0
- data/Manifest.txt +83 -0
- data/README.rdoc +81 -0
- data/Rakefile +41 -0
- data/bin/afgen +46 -0
- data/bin/cql +52 -0
- data/examples/CQL/Address.cql +46 -0
- data/examples/CQL/Blog.cql +54 -0
- data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
- data/examples/CQL/Death.cql +16 -0
- data/examples/CQL/Genealogy.cql +95 -0
- data/examples/CQL/Marriage.cql +18 -0
- data/examples/CQL/Metamodel.cql +238 -0
- data/examples/CQL/MultiInheritance.cql +19 -0
- data/examples/CQL/OilSupply.cql +47 -0
- data/examples/CQL/Orienteering.cql +108 -0
- data/examples/CQL/PersonPlaysGame.cql +17 -0
- data/examples/CQL/SchoolActivities.cql +31 -0
- data/examples/CQL/SimplestUnary.cql +12 -0
- data/examples/CQL/SubtypePI.cql +32 -0
- data/examples/CQL/Warehousing.cql +99 -0
- data/examples/CQL/WindowInRoomInBldg.cql +22 -0
- data/lib/activefacts.rb +10 -0
- data/lib/activefacts/api.rb +25 -0
- data/lib/activefacts/api/concept.rb +384 -0
- data/lib/activefacts/api/constellation.rb +106 -0
- data/lib/activefacts/api/entity.rb +239 -0
- data/lib/activefacts/api/instance.rb +54 -0
- data/lib/activefacts/api/numeric.rb +158 -0
- data/lib/activefacts/api/role.rb +94 -0
- data/lib/activefacts/api/standard_types.rb +67 -0
- data/lib/activefacts/api/support.rb +59 -0
- data/lib/activefacts/api/value.rb +122 -0
- data/lib/activefacts/api/vocabulary.rb +120 -0
- data/lib/activefacts/cql.rb +31 -0
- data/lib/activefacts/cql/CQLParser.treetop +104 -0
- data/lib/activefacts/cql/Concepts.treetop +112 -0
- data/lib/activefacts/cql/DataTypes.treetop +66 -0
- data/lib/activefacts/cql/Expressions.treetop +113 -0
- data/lib/activefacts/cql/FactTypes.treetop +185 -0
- data/lib/activefacts/cql/Language/English.treetop +92 -0
- data/lib/activefacts/cql/LexicalRules.treetop +169 -0
- data/lib/activefacts/cql/Rakefile +6 -0
- data/lib/activefacts/cql/parser.rb +88 -0
- data/lib/activefacts/generate/absorption.rb +87 -0
- data/lib/activefacts/generate/cql.rb +441 -0
- data/lib/activefacts/generate/cql/html.rb +397 -0
- data/lib/activefacts/generate/null.rb +19 -0
- data/lib/activefacts/generate/ordered.rb +557 -0
- data/lib/activefacts/generate/ruby.rb +326 -0
- data/lib/activefacts/generate/sql/server.rb +164 -0
- data/lib/activefacts/generate/text.rb +21 -0
- data/lib/activefacts/input/cql.rb +1268 -0
- data/lib/activefacts/input/orm.rb +926 -0
- data/lib/activefacts/persistence.rb +1 -0
- data/lib/activefacts/persistence/composition.rb +653 -0
- data/lib/activefacts/support.rb +51 -0
- data/lib/activefacts/version.rb +3 -0
- data/lib/activefacts/vocabulary.rb +6 -0
- data/lib/activefacts/vocabulary/extensions.rb +343 -0
- data/lib/activefacts/vocabulary/metamodel.rb +303 -0
- data/script/txt2html +71 -0
- data/spec/absorption_spec.rb +95 -0
- data/spec/api/autocounter.rb +82 -0
- data/spec/api/constellation.rb +130 -0
- data/spec/api/entity_type.rb +101 -0
- data/spec/api/instance.rb +428 -0
- data/spec/api/roles.rb +122 -0
- data/spec/api/value_type.rb +112 -0
- data/spec/api_spec.rb +14 -0
- data/spec/cql_cql_spec.rb +58 -0
- data/spec/cql_parse_spec.rb +31 -0
- data/spec/cql_ruby_spec.rb +60 -0
- data/spec/cql_sql_spec.rb +54 -0
- data/spec/cql_symbol_tables_spec.rb +259 -0
- data/spec/cql_unit_spec.rb +336 -0
- data/spec/cqldump_spec.rb +169 -0
- data/spec/norma_cql_spec.rb +48 -0
- data/spec/norma_ruby_spec.rb +50 -0
- data/spec/norma_sql_spec.rb +45 -0
- data/spec/norma_tables_spec.rb +94 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +173 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
#
|
2
|
+
# Generate text output for ActiveFacts vocabularies.
|
3
|
+
#
|
4
|
+
# Copyright (c) 2007 Clifford Heath. Read the LICENSE file.
|
5
|
+
# Author: Clifford Heath.
|
6
|
+
#
|
7
|
+
module ActiveFacts
|
8
|
+
module Generate
|
9
|
+
class TEXT
|
10
|
+
def initialize(vocabulary)
|
11
|
+
@vocabulary = vocabulary
|
12
|
+
@vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::Constellation === @vocabulary
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate(out = $>)
|
16
|
+
out.puts @vocabulary.constellation.verbalise
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,1268 @@
|
|
1
|
+
#
|
2
|
+
# Compile a CQL file into an ActiveFacts vocabulary.
|
3
|
+
# Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
|
4
|
+
#
|
5
|
+
require 'activefacts/vocabulary'
|
6
|
+
require 'activefacts/cql/parser'
|
7
|
+
|
8
|
+
require 'ruby-debug'
|
9
|
+
|
10
|
+
module ActiveFacts
|
11
|
+
module Input #:nodoc:
|
12
|
+
# The CQL Input module is activated whenever afgen is called upon to process a file
|
13
|
+
# whose name ends in .cql. The file is parsed to a constellation and the vocabulary
|
14
|
+
# object defined in that file is returned.
|
15
|
+
class CQL
|
16
|
+
include ActiveFacts
|
17
|
+
include ActiveFacts::Metamodel
|
18
|
+
|
19
|
+
class SymbolTable; end #:nodoc:
|
20
|
+
|
21
|
+
RingTypes = %w{acyclic intransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
|
22
|
+
RingPairs = {
|
23
|
+
:intransitive => [:acyclic, :asymmetric, :symmetric],
|
24
|
+
:irreflexive => [:symmetric]
|
25
|
+
}
|
26
|
+
|
27
|
+
# Open the specified file and read it:
|
28
|
+
def self.readfile(filename)
|
29
|
+
File.open(filename) {|file|
|
30
|
+
self.read(file, filename)
|
31
|
+
}
|
32
|
+
rescue => e
|
33
|
+
puts e.message+"\n\t"+e.backtrace*"\n\t" if debug :exception
|
34
|
+
raise "In #{filename} #{e.message.strip}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Read the specified input stream:
|
38
|
+
def self.read(file, filename = "stdin")
|
39
|
+
CQL.new(file.read, filename).read
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize(file, filename = "stdin")
|
43
|
+
@file = file
|
44
|
+
@filename = filename
|
45
|
+
end
|
46
|
+
|
47
|
+
# Read the input, returning a new Vocabulary:
|
48
|
+
def read
|
49
|
+
@constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
|
50
|
+
|
51
|
+
@parser = ActiveFacts::CQLParser.new
|
52
|
+
|
53
|
+
# The syntax tree created from each parsed CQL statement gets passed to the block.
|
54
|
+
# parse_all returns an array of the block's non-nil return values.
|
55
|
+
result = @parser.parse_all(@file, :definition) do |node|
|
56
|
+
begin
|
57
|
+
kind, *value = @parser.definition(node)
|
58
|
+
#print "Parsed '#{node.text_value}'"
|
59
|
+
#print " to "; p value
|
60
|
+
raise "Definition of #{kind} must be in a vocabulary" if kind != :vocabulary and !@vocabulary
|
61
|
+
case kind
|
62
|
+
when :vocabulary
|
63
|
+
@vocabulary = @constellation.Vocabulary(value[0])
|
64
|
+
when :data_type
|
65
|
+
value_type *value
|
66
|
+
when :entity_type
|
67
|
+
entity_type *value
|
68
|
+
when :fact_type
|
69
|
+
fact_type *value
|
70
|
+
when :constraint
|
71
|
+
constraint *value
|
72
|
+
else
|
73
|
+
print "="*20+" unhandled declaration type: "; p kind, value
|
74
|
+
end
|
75
|
+
rescue => e
|
76
|
+
puts e.message+"\n\t"+e.backtrace*"\n\t" if debug :exception
|
77
|
+
start_line = @file.line_of(node.interval.first)
|
78
|
+
end_line = @file.line_of(node.interval.last-1)
|
79
|
+
lines = start_line != end_line ? "s #{start_line}-#{end_line}" : " #{start_line.to_s}"
|
80
|
+
raise "at line#{lines} #{e.message.strip}"
|
81
|
+
end
|
82
|
+
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
raise @parser.failure_reason unless result
|
86
|
+
@vocabulary
|
87
|
+
end
|
88
|
+
|
89
|
+
def value_type(name, base_type_name, parameters, unit, ranges)
|
90
|
+
length, scale = *parameters
|
91
|
+
|
92
|
+
# Create the base type:
|
93
|
+
base_type = nil
|
94
|
+
if (base_type_name != name)
|
95
|
+
unless base_type = @constellation.ValueType[[@constellation.Name(base_type_name), @vocabulary]]
|
96
|
+
#puts "REVISIT: Creating base ValueType #{base_type_name} in #{@vocabulary.inspect}"
|
97
|
+
base_type = @constellation.ValueType(base_type_name, @vocabulary)
|
98
|
+
return if base_type_name == name
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Create and initialise the ValueType:
|
103
|
+
vt = @constellation.ValueType(name, @vocabulary)
|
104
|
+
vt.supertype = base_type if base_type
|
105
|
+
vt.length = length if length
|
106
|
+
vt.scale = scale if scale
|
107
|
+
|
108
|
+
# REVISIT: Find and apply the units
|
109
|
+
|
110
|
+
if ranges.size != 0
|
111
|
+
vt.value_restriction = @constellation.ValueRestriction(:new)
|
112
|
+
ranges.each do |range|
|
113
|
+
min, max = Array === range ? range : [range, range]
|
114
|
+
v_range = @constellation.ValueRange(
|
115
|
+
min ? [min.to_s, true] : nil,
|
116
|
+
max ? [max.to_s, true] : nil
|
117
|
+
)
|
118
|
+
ar = @constellation.AllowedRange(v_range, vt.value_restriction)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def entity_type(name, supertypes, identification, clauses)
|
124
|
+
#puts "Entity Type #{name}, supertypes #{supertypes.inspect}, id #{identification.inspect}, clauses = #{clauses.inspect}"
|
125
|
+
debug :entity, "Defining Entity Type #{name}" do
|
126
|
+
# Assert the entity:
|
127
|
+
# If this entity was forward referenced, this won't be a new object, and will subsume its roles
|
128
|
+
entity_type = @constellation.EntityType(name, @vocabulary)
|
129
|
+
|
130
|
+
# Set up its supertypes:
|
131
|
+
supertypes.each do |supertype_name|
|
132
|
+
add_supertype(entity_type, supertype_name, !identification && supertype_name == supertypes[0])
|
133
|
+
end
|
134
|
+
|
135
|
+
# Use a two-pass algorithm for entity fact types...
|
136
|
+
# The first step is to find all role references and definitions in the clauses
|
137
|
+
# After bind_roles, each item in the reading part of each clause is either:
|
138
|
+
# * a string, which is a linking word, or
|
139
|
+
# * the phrase hash augmented with a :binding=>Binding
|
140
|
+
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
141
|
+
@symbols.bind_roles_in_clauses(clauses, identification ? identification[:roles] : nil)
|
142
|
+
|
143
|
+
# Next arrange the readings according to what fact they belong to,
|
144
|
+
# then process each fact type using normal fact type processing.
|
145
|
+
# That way if we find a fact type here having none of the players being the
|
146
|
+
# entity type, we know it's an objectified fact type. The CQL syntax might make
|
147
|
+
# us come here with such a case when the fact type is a subtype of some entity type,
|
148
|
+
# such as occurs in the Metamodel with TypeInheritance.
|
149
|
+
|
150
|
+
# N.B. This doesn't allow forward identification by roles with adjectives (see the i[0]):
|
151
|
+
@symbols.allowed_forward = (ir = identification && identification[:roles]) ? ir.inject({}){|h, i| h[i[0]] = true; h} : {}
|
152
|
+
|
153
|
+
# If we're using a common identification mode, find or create the necessary ValueTypes first:
|
154
|
+
vt_name = vt = nil
|
155
|
+
if identification && identification[:mode]
|
156
|
+
mode = identification[:mode] # An identification mode
|
157
|
+
|
158
|
+
# Find or Create an appropriate ValueType called "#{name}#{mode}", of the supertype "#{mode}"
|
159
|
+
vt_name = "#{name}#{mode}"
|
160
|
+
unless vt = @constellation.ValueType[[vt_name, @vocabulary]]
|
161
|
+
base_vt = @constellation.ValueType(mode, @vocabulary)
|
162
|
+
vt = @constellation.ValueType(vt_name, @vocabulary, :supertype => base_vt)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
identifying_fact_types = {}
|
167
|
+
clauses_by_fact_type(clauses).each do |clauses_for_fact_type|
|
168
|
+
fact_type = nil
|
169
|
+
@symbols.embedded_presence_constraints = [] # Clear embedded_presence_constraints for each fact type
|
170
|
+
debug "New Fact Type for entity #{name}" do
|
171
|
+
clauses_for_fact_type.each do |clause|
|
172
|
+
type, qualifiers, reading = *clause
|
173
|
+
debug :reading, "Clause: #{clause.inspect}" do
|
174
|
+
f = bind_fact_reading(fact_type, qualifiers, reading)
|
175
|
+
identifying_fact_types[f] = true
|
176
|
+
fact_type ||= f
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Find the role that this entity type plays in the fact type, if any:
|
182
|
+
player_roles = fact_type.all_role.select{|role| role.concept == entity_type }
|
183
|
+
raise "#{role.concept.name} may only play one role in each identifying fact type" if player_roles.size > 1
|
184
|
+
if player_role = player_roles[0]
|
185
|
+
non_player_roles = fact_type.all_role-[player_role]
|
186
|
+
|
187
|
+
raise "#{name} cannot be identified by a role in a non-binary fact type" if non_player_roles.size > 1
|
188
|
+
elsif identification
|
189
|
+
# This situation occurs when an objectified fact type has an entity identifier
|
190
|
+
raise "Entity type #{name} may only objectify a single fact type" if entity_type.fact_type
|
191
|
+
|
192
|
+
entity_type.fact_type = fact_type
|
193
|
+
fact_type_identification(fact_type, name, false)
|
194
|
+
else
|
195
|
+
# it's an objectified fact type, such as a subtype
|
196
|
+
entity_type.fact_type = fact_type
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Finally, create the identifying uniqueness constraint, or mark it as preferred
|
201
|
+
# if it's already been created. The identifying roles have been defined already.
|
202
|
+
|
203
|
+
if identification
|
204
|
+
debug :identification, "Handling identification" do
|
205
|
+
if id_role_names = identification[:roles] # A list of identifying roles
|
206
|
+
debug "Identifying roles: #{id_role_names.inspect}"
|
207
|
+
|
208
|
+
# Pick out the identifying_roles in the order they were declared,
|
209
|
+
# not the order the fact types were defined:
|
210
|
+
identifying_roles = id_role_names.map do |names|
|
211
|
+
unless (role = bind_unary_fact_type(entity_type, names))
|
212
|
+
player, binding = @symbols.bind(names)
|
213
|
+
role = @symbols.roles_by_binding[binding]
|
214
|
+
raise "identifying role #{names*"-"} not found in fact types for #{name}" unless role
|
215
|
+
end
|
216
|
+
role
|
217
|
+
end
|
218
|
+
|
219
|
+
# Find a uniqueness constraint as PI, or make one
|
220
|
+
pc = find_pc_over_roles(identifying_roles)
|
221
|
+
if (pc)
|
222
|
+
debug "Existing PC #{pc.verbalise} is now PK for #{name} #{pc.class.roles.keys.map{|k|"#{k} => "+pc.send(k).verbalise}*", "}"
|
223
|
+
pc.is_preferred_identifier = true
|
224
|
+
pc.name = "#{name}PK" unless pc.name
|
225
|
+
else
|
226
|
+
debug "Adding PK for #{name} using #{identifying_roles.map{|r| r.concept.name}.inspect}"
|
227
|
+
|
228
|
+
role_sequence = @constellation.RoleSequence(:new)
|
229
|
+
# REVISIT: Need to sort the identifying_roles to match the identification parameter array
|
230
|
+
identifying_roles.each_with_index do |identifying_role, index|
|
231
|
+
@constellation.RoleRef(role_sequence, index, :role => identifying_role)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Add a unique constraint over all identifying roles
|
235
|
+
pc = @constellation.PresenceConstraint(
|
236
|
+
:new,
|
237
|
+
:vocabulary => @vocabulary,
|
238
|
+
:name => "#{name}PK", # Is this a useful name?
|
239
|
+
:role_sequence => role_sequence,
|
240
|
+
:is_preferred_identifier => true,
|
241
|
+
:max_frequency => 1 # Unique
|
242
|
+
#:is_mandatory => true,
|
243
|
+
#:min_frequency => 1,
|
244
|
+
)
|
245
|
+
end
|
246
|
+
|
247
|
+
elsif identification[:mode]
|
248
|
+
mode = identification[:mode] # An identification mode
|
249
|
+
|
250
|
+
raise "Entity definition using reference mode may only have one identifying fact type" if identifying_fact_types.size > 1
|
251
|
+
mode_fact_type = identifying_fact_types.keys[0]
|
252
|
+
|
253
|
+
# If the entity type is an objectified fact type, don't use the objectified fact type!
|
254
|
+
mode_fact_type = nil if mode_fact_type && mode_fact_type.entity_type == entity_type
|
255
|
+
|
256
|
+
debug :mode, "Processing Reference Mode for #{name}#{mode_fact_type ? " with existing '#{mode_fact_type.default_reading}'" : ""}"
|
257
|
+
|
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
|
+
# Fact Type:
|
262
|
+
if (ft = mode_fact_type)
|
263
|
+
entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)]
|
264
|
+
value_role = ft.all_role[1-n]
|
265
|
+
else
|
266
|
+
ft = @constellation.FactType(:new)
|
267
|
+
entity_role = @constellation.Role(ft, 0, entity_type)
|
268
|
+
value_role = @constellation.Role(ft, 1, vt)
|
269
|
+
debug :mode, "Creating new fact type to identify #{name}"
|
270
|
+
end
|
271
|
+
|
272
|
+
# Forward reading, if it doesn't already exist:
|
273
|
+
rss = entity_role.all_role_ref.map{|rr| rr.role_sequence.all_role_ref.size == 2 ? rr.role_sequence : nil }.compact
|
274
|
+
rs01 = rss.select{|rs| rs.all_role_ref[1].role == value_role }[0]
|
275
|
+
rs10 = rss.select{|rs| rs.all_role_ref[1].role == entity_role }[0]
|
276
|
+
if !rs01
|
277
|
+
rs01 = @constellation.RoleSequence(:new)
|
278
|
+
@constellation.RoleRef(rs01, 0, :role => entity_role)
|
279
|
+
@constellation.RoleRef(rs01, 1, :role => value_role)
|
280
|
+
@constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs01, :reading_text => "{0} has {1}")
|
281
|
+
debug :mode, "Creating new forward reading '#{name} has #{vt.name}'"
|
282
|
+
else
|
283
|
+
debug :mode, "Using existing forward reading #{rs01.all_reading[0].expand}"
|
284
|
+
end
|
285
|
+
|
286
|
+
# Reverse reading:
|
287
|
+
if !rs10
|
288
|
+
rs10 = @constellation.RoleSequence(:new)
|
289
|
+
@constellation.RoleRef(rs10, 0, :role => value_role)
|
290
|
+
@constellation.RoleRef(rs10, 1, :role => entity_role)
|
291
|
+
@constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs10, :reading_text => "{0} is of {1}")
|
292
|
+
debug :mode, "Creating new reverse reading '#{vt.name} is of #{name}'"
|
293
|
+
else
|
294
|
+
debug :mode, "Using existing reverse reading"
|
295
|
+
end
|
296
|
+
|
297
|
+
# Entity Type must have a value type. Find or create the role sequence, then create a PC if necessary
|
298
|
+
debug :mode, "entity_role has #{entity_role.all_role_ref.size} attached sequences"
|
299
|
+
debug :mode, "entity_role has #{entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
|
300
|
+
rs0 = entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
|
301
|
+
if !rs0
|
302
|
+
rs0 = @constellation.RoleSequence(:new)
|
303
|
+
@constellation.RoleRef(rs0, 0, :role => entity_role)
|
304
|
+
debug :mode, "Creating new EntityType role sequence"
|
305
|
+
else
|
306
|
+
rs0 = rs0.role_sequence
|
307
|
+
debug :mode, "Using existing EntityType role sequence"
|
308
|
+
end
|
309
|
+
if (rs0.all_presence_constraint.size == 0)
|
310
|
+
@constellation.PresenceConstraint(
|
311
|
+
:new,
|
312
|
+
:name => '',
|
313
|
+
:enforcement => '',
|
314
|
+
:vocabulary => @vocabulary,
|
315
|
+
:role_sequence => rs0,
|
316
|
+
:min_frequency => 1,
|
317
|
+
:max_frequency => 1,
|
318
|
+
:is_preferred_identifier => false,
|
319
|
+
:is_mandatory => true
|
320
|
+
)
|
321
|
+
debug :mode, "Creating new EntityType PresenceConstraint"
|
322
|
+
else
|
323
|
+
debug :mode, "Using existing EntityType PresenceConstraint"
|
324
|
+
end
|
325
|
+
|
326
|
+
# Value Type must have a value type. Find or create the role sequence, then create a PC if necessary
|
327
|
+
debug :mode, "value_role has #{value_role.all_role_ref.size} attached sequences"
|
328
|
+
debug :mode, "value_role has #{value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
|
329
|
+
rs1 = value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
|
330
|
+
if (!rs1)
|
331
|
+
rs1 = @constellation.RoleSequence(:new)
|
332
|
+
@constellation.RoleRef(rs1, 0, :role => value_role)
|
333
|
+
debug :mode, "Creating new ValueType role sequence"
|
334
|
+
else
|
335
|
+
rs1 = rs1.role_sequence
|
336
|
+
debug :mode, "Using existing ValueType role sequence"
|
337
|
+
end
|
338
|
+
if (rs1.all_presence_constraint.size == 0)
|
339
|
+
@constellation.PresenceConstraint(
|
340
|
+
:new,
|
341
|
+
:name => '',
|
342
|
+
:enforcement => '',
|
343
|
+
:vocabulary => @vocabulary,
|
344
|
+
:role_sequence => rs1,
|
345
|
+
:min_frequency => 0,
|
346
|
+
:max_frequency => 1,
|
347
|
+
:is_preferred_identifier => true,
|
348
|
+
:is_mandatory => false
|
349
|
+
)
|
350
|
+
debug :mode, "Creating new ValueType PresenceConstraint"
|
351
|
+
else
|
352
|
+
debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
|
353
|
+
rs1.all_presence_constraint[0].is_preferred_identifier = true
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
else
|
358
|
+
# identification must be inherited.
|
359
|
+
debug "Identification is inherited"
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def add_supertype(entity_type, supertype_name, identifying_supertype)
|
365
|
+
debug :supertype, "Supertype #{supertype_name}"
|
366
|
+
supertype = @constellation.EntityType(supertype_name, @vocabulary)
|
367
|
+
inheritance_fact = @constellation.TypeInheritance(entity_type, supertype, :fact_type_id => :new)
|
368
|
+
|
369
|
+
# Create a reading:
|
370
|
+
sub_role = @constellation.Role(inheritance_fact, 0, entity_type)
|
371
|
+
super_role = @constellation.Role(inheritance_fact, 1, supertype)
|
372
|
+
rs = @constellation.RoleSequence(:new)
|
373
|
+
@constellation.RoleRef(rs, 0, :role => sub_role)
|
374
|
+
@constellation.RoleRef(rs, 1, :role => super_role)
|
375
|
+
@constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :reading_text => "{0} is a subtype of {1}")
|
376
|
+
|
377
|
+
if identifying_supertype
|
378
|
+
inheritance_fact.provides_identification = true
|
379
|
+
end
|
380
|
+
|
381
|
+
# Create uniqueness constraints over the subtyping fact type
|
382
|
+
p1rs = @constellation.RoleSequence(:new)
|
383
|
+
@constellation.RoleRef(p1rs, 0).role = sub_role
|
384
|
+
pc1 = @constellation.PresenceConstraint(:new)
|
385
|
+
pc1.name = "#{entity_type.name}MustHaveSupertype#{supertype.name}"
|
386
|
+
pc1.vocabulary = @vocabulary
|
387
|
+
pc1.role_sequence = p1rs
|
388
|
+
pc1.is_mandatory = true # A subtype instance must have a supertype instance
|
389
|
+
pc1.min_frequency = 1
|
390
|
+
pc1.max_frequency = 1
|
391
|
+
pc1.is_preferred_identifier = false
|
392
|
+
|
393
|
+
# The supertype role often identifies the subtype:
|
394
|
+
p2rs = @constellation.RoleSequence(:new)
|
395
|
+
@constellation.RoleRef(p2rs, 0).role = super_role
|
396
|
+
pc2 = @constellation.PresenceConstraint(:new)
|
397
|
+
pc2.name = "#{supertype.name}MayBeA#{entity_type.name}"
|
398
|
+
pc2.vocabulary = @vocabulary
|
399
|
+
pc2.role_sequence = p2rs
|
400
|
+
pc2.is_mandatory = false
|
401
|
+
pc2.min_frequency = 0
|
402
|
+
pc2.max_frequency = 1
|
403
|
+
pc2.is_preferred_identifier = inheritance_fact.provides_identification
|
404
|
+
end
|
405
|
+
|
406
|
+
# If one of the words is the name of the entity type, and the other
|
407
|
+
# words consist of a unary fact type reading, return the role it plays.
|
408
|
+
def bind_unary_fact_type(entity_type, words)
|
409
|
+
return nil unless i = words.index(entity_type.name)
|
410
|
+
|
411
|
+
to_match = words.clone
|
412
|
+
to_match[i] = '{0}'
|
413
|
+
to_match = to_match*' '
|
414
|
+
|
415
|
+
# See if any unary fact type of this or any supertype matches these words:
|
416
|
+
entity_type.supertypes_transitive.each do |supertype|
|
417
|
+
supertype.all_role.each do |role|
|
418
|
+
role.fact_type.all_role.size == 1 &&
|
419
|
+
role.fact_type.all_reading.each do |reading|
|
420
|
+
if reading.reading_text == to_match
|
421
|
+
debug :identification, "Bound identification to unary role '#{to_match.sub(/\{0\}/, entity_type.name)}'"
|
422
|
+
return role
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
nil
|
428
|
+
end
|
429
|
+
|
430
|
+
def fact_type(name, clauses, conditions)
|
431
|
+
debug "Processing clauses for fact type" do
|
432
|
+
fact_type = nil
|
433
|
+
|
434
|
+
#
|
435
|
+
# The first step is to find all role references and definitions in the reading clauses.
|
436
|
+
# This also:
|
437
|
+
# * deletes any adjectives that were used but not hyphenated
|
438
|
+
# * changes each linking word phrase into a simple String
|
439
|
+
# * adds a :binding key to each bound role
|
440
|
+
#
|
441
|
+
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
442
|
+
@symbols.bind_roles_in_clauses(clauses)
|
443
|
+
|
444
|
+
clauses.each do |clause|
|
445
|
+
kind, qualifiers, reading = *clause
|
446
|
+
|
447
|
+
fact_type = bind_fact_reading(fact_type, qualifiers, reading)
|
448
|
+
end
|
449
|
+
|
450
|
+
# The fact type has a name iff it's objectified as an entity type
|
451
|
+
#puts "============= Creating entity #{name} to nominalize fact type #{fact_type.default_reading} ======================" if name
|
452
|
+
fact_type.entity_type = @constellation.EntityType(name, @vocabulary) if name
|
453
|
+
|
454
|
+
# Add the identifying PresenceConstraint for this fact type:
|
455
|
+
if fact_type.all_role.size == 1 && !fact_type.entity_type
|
456
|
+
# All is well, unaries don't need an identifying PC unless objectified
|
457
|
+
else
|
458
|
+
fact_type_identification(fact_type, name, true)
|
459
|
+
end
|
460
|
+
|
461
|
+
# REVISIT: Process the fact derivation conditions, if any
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
def constraint *value
|
466
|
+
case type = value.shift
|
467
|
+
when :presence
|
468
|
+
presence_constraint *value
|
469
|
+
when :subset
|
470
|
+
subset_constraint *value
|
471
|
+
when :set
|
472
|
+
set_constraint *value
|
473
|
+
when :equality
|
474
|
+
equality_constraint *value
|
475
|
+
else
|
476
|
+
$stderr.puts "REVISIT: external #{type} constraints aren't yet handled:\n\t"+value.map{|a| a.inspect }*"\n\t"
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
# The readings list is an array of an array of fact types.
|
481
|
+
# The fact types contain roles played by concepts, where each
|
482
|
+
# concept plays more than one role. In fact, a concept may
|
483
|
+
# occur in more than one binding, and each binding plays more
|
484
|
+
# than one role. The bindings that are common to all fact types
|
485
|
+
# in each array in the readings list form the constrained role
|
486
|
+
# sequences. Each binding that isn't common at this top level
|
487
|
+
# must occur more than once in each group of fact types where
|
488
|
+
# it appears, and it forms a join between those fact types.
|
489
|
+
def bind_join_paths_as_role_sequences(readings_list)
|
490
|
+
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
491
|
+
fact_roles_list = []
|
492
|
+
bindings_list = []
|
493
|
+
readings_list.each_with_index do |readings, index|
|
494
|
+
# readings is an array of readings
|
495
|
+
@symbols.bind_roles_in_readings(readings)
|
496
|
+
|
497
|
+
# Was:
|
498
|
+
# fact_roles_list[index] = invoked_fact_roles(readings[0])
|
499
|
+
# raise "Fact type reading not found for #{readings[0].inspect}" unless fact_roles_list[index]
|
500
|
+
# bindings_list[index] = readings[0].map{|phrase| Hash === phrase ? phrase[:binding] : nil}.compact
|
501
|
+
|
502
|
+
fact_roles_list << readings.map do |reading|
|
503
|
+
ifr = invoked_fact_roles(reading)
|
504
|
+
raise "Fact type reading not found for #{reading.inspect}" unless ifr
|
505
|
+
ifr
|
506
|
+
end
|
507
|
+
bindings_list << readings.map do |reading|
|
508
|
+
reading.map{ |phrase| Hash === phrase ? phrase[:binding] : nil}.compact
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
# Each set of binding arrays in the list must share at least one common binding
|
513
|
+
bindings_by_join_path = bindings_list.map{|join_path| join_path.flatten}
|
514
|
+
common_bindings = bindings_by_join_path[1..-1].inject(bindings_by_join_path[0]) { |c, b| c & b }
|
515
|
+
# Was:
|
516
|
+
# common_bindings = bindings_list.inject(bindings_list[0]) { |common, bindings| common & bindings }
|
517
|
+
raise "Set constraints must have at least one common role between the sets" unless common_bindings.size > 0
|
518
|
+
|
519
|
+
# REVISIT: Do we need to constrain things such that each join path only includes *one* instance of each common binding?
|
520
|
+
|
521
|
+
# For each set of binding arrays, if there's more than one binding array in the set,
|
522
|
+
# it represents a join path. Here we check that each join path is complete, i.e. linked up.
|
523
|
+
# Each element of a join path is the array of bindings for a fact type invocation.
|
524
|
+
# Each invocation must share a binding (not one of the globally common ones) with
|
525
|
+
# another invocation in that join path.
|
526
|
+
bindings_list.each_with_index do |join_path, jpnum|
|
527
|
+
# Check that this bindings array creates a complete join path:
|
528
|
+
join_path.each_with_index do |bindings, i|
|
529
|
+
fact_type_roles = fact_roles_list[jpnum][i]
|
530
|
+
fact_type = fact_type_roles[0].fact_type
|
531
|
+
|
532
|
+
# The bindings are for one fact type invocation.
|
533
|
+
# These bindings must be joined to some later fact type by a common binding that isn't a globally-common one:
|
534
|
+
local_bindings = bindings-common_bindings
|
535
|
+
next if local_bindings.size == 0 # No join path is required, as only one fact type is invoked.
|
536
|
+
next if i == join_path.size-1 # We already checked that the last fact type invocation is joined
|
537
|
+
ok = local_bindings.detect do |local_binding|
|
538
|
+
j = i+1
|
539
|
+
join_path[j..-1].detect do |other_bindings|
|
540
|
+
other_fact_type_roles = fact_roles_list[jpnum][j]
|
541
|
+
other_fact_type = other_fact_type_roles[0].fact_type
|
542
|
+
j += 1
|
543
|
+
# These next two lines allow joining from/to an objectified fact type:
|
544
|
+
fact_type_roles.detect{|r| r.concept == other_fact_type.entity_type } ||
|
545
|
+
other_fact_type_roles.detect{|r| r.concept == fact_type.entity_type } ||
|
546
|
+
other_bindings.include?(local_binding)
|
547
|
+
end
|
548
|
+
end
|
549
|
+
raise "Incomplete join path; one of the bindings #{local_bindings.inspect} must re-occur to establish a join" unless ok
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
# Create the role sequences and their role references.
|
554
|
+
# Each role sequence contain one RoleRef for each common binding
|
555
|
+
role_sequences = readings_list.map{|r| @constellation.RoleSequence(:new) }
|
556
|
+
common_bindings.each_with_index do |binding, index|
|
557
|
+
role_sequences.each_with_index do |rs, rsi|
|
558
|
+
join_path = bindings_list[rsi]
|
559
|
+
fact_pos = nil
|
560
|
+
join_pos = (0...join_path.size).detect do |i|
|
561
|
+
fact_pos = join_path[i].index(binding)
|
562
|
+
end
|
563
|
+
@constellation.RoleRef(rs, index).role = fact_roles_list[rsi][join_pos][fact_pos]
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
role_sequences
|
568
|
+
end
|
569
|
+
|
570
|
+
def subset_constraint(subset_readings, superset_readings)
|
571
|
+
role_sequences = bind_join_paths_as_role_sequences([subset_readings, superset_readings])
|
572
|
+
|
573
|
+
#puts "subset_constraint:\n\t#{subset_readings.inspect}\n\t#{superset_readings.inspect}"
|
574
|
+
#puts "\t#{role_sequences.map{|rs| rs.describe}.inspect}"
|
575
|
+
|
576
|
+
# create the constraint:
|
577
|
+
constraint = @constellation.SubsetConstraint(:new)
|
578
|
+
constraint.vocabulary = @vocabulary
|
579
|
+
#constraint.name = nil
|
580
|
+
#constraint.enforcement =
|
581
|
+
constraint.subset_role_sequence = role_sequences[0]
|
582
|
+
constraint.superset_role_sequence = role_sequences[1]
|
583
|
+
end
|
584
|
+
|
585
|
+
def set_constraint(constrained_roles, quantifier, *readings_list)
|
586
|
+
# Exactly one or at most one, nothing else will do
|
587
|
+
raise "Set comparison constraint must use 'at most' or 'exactly' one" if quantifier[1] != 1
|
588
|
+
|
589
|
+
role_sequences = bind_join_paths_as_role_sequences(readings_list)
|
590
|
+
|
591
|
+
# Create the constraint:
|
592
|
+
constraint = @constellation.SetExclusionConstraint(:new)
|
593
|
+
constraint.vocabulary = @vocabulary
|
594
|
+
role_sequences.each do |rs|
|
595
|
+
@constellation.SetComparisonRoles(constraint, rs)
|
596
|
+
end
|
597
|
+
constraint.is_mandatory = quantifier[0] == 1
|
598
|
+
end
|
599
|
+
|
600
|
+
def equality_constraint(*readings_list)
|
601
|
+
#puts "REVISIT: equality\n\t#{readings_list.map{|rl| rl.inspect}*"\n\tif and only if\n\t"}"
|
602
|
+
|
603
|
+
role_sequences = bind_join_paths_as_role_sequences(readings_list)
|
604
|
+
|
605
|
+
# Create the constraint:
|
606
|
+
constraint = @constellation.SetEqualityConstraint(:new)
|
607
|
+
constraint.vocabulary = @vocabulary
|
608
|
+
role_sequences.each do |rs|
|
609
|
+
@constellation.SetComparisonRoles(constraint, rs)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
def presence_constraint(constrained_role_names, quantifier, readings)
|
614
|
+
raise "REVISIT: Join presence constraints not supported yet" if readings[0].size > 1
|
615
|
+
readings = readings.map{|r| r[0] }
|
616
|
+
#p readings
|
617
|
+
|
618
|
+
@symbols = SymbolTable.new(@constellation, @vocabulary)
|
619
|
+
|
620
|
+
# Find players for all constrained_role_names. These may use leading or trailing adjective forms...
|
621
|
+
constrained_players = []
|
622
|
+
constrained_bindings = []
|
623
|
+
constrained_role_names.each do |role_name|
|
624
|
+
player, binding = @symbols.bind(role_name)
|
625
|
+
constrained_players << player
|
626
|
+
constrained_bindings << binding
|
627
|
+
end
|
628
|
+
#puts "Constrained bindings are #{constrained_bindings.inspect}"
|
629
|
+
#puts "Constrained bindings object_id's are #{constrained_bindings.map{|b|b.object_id.to_s}*","}"
|
630
|
+
|
631
|
+
# Find players for all the concepts in all readings:
|
632
|
+
@symbols.bind_roles_in_readings(readings)
|
633
|
+
|
634
|
+
constrained_roles = []
|
635
|
+
unmatched_roles = constrained_role_names.clone
|
636
|
+
readings.each do |reading|
|
637
|
+
# puts reading.inspect
|
638
|
+
|
639
|
+
# If this succeeds, the reading found matches the roles in our phrases
|
640
|
+
fact_roles = invoked_fact_roles(reading)
|
641
|
+
raise "Fact type reading not found for #{reading.inspect}" unless fact_roles
|
642
|
+
|
643
|
+
# Look for the constrained role(s); the bindings will be the same
|
644
|
+
matched_bindings = reading.select{|p| Hash === p}.map{|p| p[:binding]}
|
645
|
+
#puts "matched_bindings = #{matched_bindings.inspect}"
|
646
|
+
#puts "matched_bindings object_id's are #{matched_bindings.map{|b|b.object_id.to_s}*","}}"
|
647
|
+
matched_bindings.each_with_index{|b, pos|
|
648
|
+
i = constrained_bindings.index(b)
|
649
|
+
next unless i
|
650
|
+
unmatched_roles[i] = nil
|
651
|
+
#puts "found #{constrained_bindings[i].inspect} found as #{b.inspect} in position #{i.inspect}"
|
652
|
+
role = fact_roles[pos]
|
653
|
+
constrained_roles << role unless constrained_roles.include?(role)
|
654
|
+
}
|
655
|
+
end
|
656
|
+
|
657
|
+
# Check that all constrained roles were matched at least once:
|
658
|
+
unmatched_roles.compact!
|
659
|
+
raise "Constrained roles #{unmatched_roles.map{|ur| ur*"-"}*", "} not found in fact types" if unmatched_roles.size != 0
|
660
|
+
|
661
|
+
rs = @constellation.RoleSequence(:new)
|
662
|
+
#puts "constrained_roles: #{constrained_roles.map{|r| r.concept.name}.inspect}"
|
663
|
+
constrained_roles.each_with_index do |role, index|
|
664
|
+
raise "Constrained role #{constrained_role_names[index]} not found" unless role
|
665
|
+
rr = @constellation.RoleRef(rs, index)
|
666
|
+
rr.role = role
|
667
|
+
end
|
668
|
+
#puts "New external PresenceConstraint with quantifier = #{quantifier.inspect} over #{rs.describe}"
|
669
|
+
@constellation.PresenceConstraint(
|
670
|
+
:new,
|
671
|
+
:name => '',
|
672
|
+
:enforcement => '',
|
673
|
+
:vocabulary => @vocabulary,
|
674
|
+
:role_sequence => rs,
|
675
|
+
:min_frequency => quantifier[0],
|
676
|
+
:max_frequency => quantifier[1],
|
677
|
+
:is_preferred_identifier => false,
|
678
|
+
:is_mandatory => quantifier[0] && quantifier[0] > 0
|
679
|
+
)
|
680
|
+
end
|
681
|
+
|
682
|
+
def inheritance_path(subtype, supertype)
|
683
|
+
direct_inheritance = subtype.all_supertype_inheritance.select{|ti| ti.supertype == supertype}
|
684
|
+
return direct_inheritance if (direct_inheritance[0])
|
685
|
+
subtype.all_supertype_inheritance.each{|ti|
|
686
|
+
ip = inheritance_path(ti.supertype, supertype)
|
687
|
+
return ip+[ti] if (ip)
|
688
|
+
}
|
689
|
+
return nil
|
690
|
+
end
|
691
|
+
|
692
|
+
# For a given reading from the parser, find the matching declared reading, and return
|
693
|
+
# the array of Role object in the same order as they occur in the reading.
|
694
|
+
def invoked_fact_roles(reading)
|
695
|
+
if (reading[0] == "!SUBTYPE!")
|
696
|
+
subtype = reading[1][:binding].concept
|
697
|
+
supertype = reading[2][:binding].concept
|
698
|
+
raise "#{subtype.name} is not a subtype of #{supertype.name}" unless subtype.supertypes_transitive.include?(supertype)
|
699
|
+
ip = inheritance_path(subtype, supertype)
|
700
|
+
return [ip[0].all_role[0], ip[-1].all_role[1]]
|
701
|
+
end
|
702
|
+
|
703
|
+
bindings = reading.select{|p| Hash === p}
|
704
|
+
players = bindings.map{|p| p[:binding].concept }
|
705
|
+
invoked_fact_roles_by_players(reading, players)
|
706
|
+
end
|
707
|
+
|
708
|
+
def invoked_fact_roles_by_players(reading, players)
|
709
|
+
players[0].all_role.each do |role|
|
710
|
+
# Does this fact type have the right number of roles?
|
711
|
+
next if role.fact_type.all_role.size != players.size
|
712
|
+
|
713
|
+
# Does this fact type include the correct other players?
|
714
|
+
# REVISIT: Might need subtype/supertype matching here, with an implied subtyping join invocation
|
715
|
+
next if role.fact_type.all_role.detect{|r| !players.include?(r.concept)}
|
716
|
+
|
717
|
+
# Oooh, a real candidate. Check the reading words.
|
718
|
+
debug "Considering "+role.fact_type.describe do
|
719
|
+
next unless role.fact_type.all_reading.detect do |candidate_reading|
|
720
|
+
debug "Considering reading"+candidate_reading.reading_text do
|
721
|
+
to_match = reading.clone
|
722
|
+
players_to_match = players.clone
|
723
|
+
candidate_reading.words_and_role_refs.each do |wrr|
|
724
|
+
if (RoleRef === wrr)
|
725
|
+
break unless Hash === to_match.first
|
726
|
+
break unless binding = to_match[0][:binding]
|
727
|
+
# REVISIT: May need to match super- or sub-types here too!
|
728
|
+
break unless players_to_match[0] == wrr.role.concept
|
729
|
+
break if wrr.leading_adjective && binding.leading_adjective != wrr.leading_adjective
|
730
|
+
break if wrr.trailing_adjective && binding.trailing_adjective != wrr.trailing_adjective
|
731
|
+
|
732
|
+
# All matched.
|
733
|
+
to_match.shift
|
734
|
+
players_to_match.shift
|
735
|
+
# elsif # REVISIT: Match "not" and "none" here as negating the fact type invocation
|
736
|
+
else
|
737
|
+
break unless String === to_match[0]
|
738
|
+
break unless to_match[0] == wrr
|
739
|
+
to_match.shift
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
743
|
+
# This is the first matching candidate.
|
744
|
+
# REVISIT: Since we do sub/supertype matching (and will do more!),
|
745
|
+
# we need to accumulate all possible matches to be sure
|
746
|
+
# there's only one, or the match is exact, or risk ambiguity.
|
747
|
+
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
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
# Hmm, that didn't work, try the subtypes of the first player.
|
755
|
+
# When a fact type matches like this, there is an implied join to the subtype.
|
756
|
+
players[0].subtypes.map do |subtype|
|
757
|
+
players[0] = subtype
|
758
|
+
fr = invoked_fact_roles_by_players(reading, players)
|
759
|
+
return fr if fr
|
760
|
+
end
|
761
|
+
|
762
|
+
# REVISIT: Do we need to do this again for the supertypes of the first player?
|
763
|
+
|
764
|
+
nil
|
765
|
+
end
|
766
|
+
|
767
|
+
def bind_fact_reading(fact_type, qualifiers, reading)
|
768
|
+
debug :reading, "Processing reading #{reading.inspect}" do
|
769
|
+
role_phrases = reading.select do |phrase|
|
770
|
+
Hash === phrase && phrase[:binding]
|
771
|
+
end
|
772
|
+
|
773
|
+
# All readings for a fact type must have the same number of roles.
|
774
|
+
# This might be relaxed later for fact clauses, where readings might
|
775
|
+
# be concatenated if the adjacent items are the same concept.
|
776
|
+
if (fact_type && fact_type.all_reading.size > 0 && role_phrases.size != fact_type.all_role.size)
|
777
|
+
raise "#{
|
778
|
+
role_phrases.size > fact_type.all_role.size ? "Too many" : "Not all"
|
779
|
+
} roles found for non-initial reading of #{fact_type.describe}"
|
780
|
+
end
|
781
|
+
|
782
|
+
# REVISIT: If the first reading is a re-iteration of an existing fact type, find and use the existing fact type
|
783
|
+
# This will require loading the @symbols.roles_by_binding using a SymbolTable
|
784
|
+
|
785
|
+
fact_type ||= @constellation.FactType(:new)
|
786
|
+
|
787
|
+
# Create the roles on the first reading, or look them up on subsequent readings.
|
788
|
+
# If the player occurs twice, we must find one with matching adjectives.
|
789
|
+
|
790
|
+
role_sequence = @constellation.RoleSequence(:new) # RoleSequence for RoleRefs of this reading
|
791
|
+
roles = []
|
792
|
+
role_phrases.each_with_index do |role_phrase, index|
|
793
|
+
binding = role_phrase[:binding]
|
794
|
+
role_name = role_phrase[:role_name]
|
795
|
+
player = binding.concept
|
796
|
+
if (fact_type.all_reading.size == 0) # First reading
|
797
|
+
# Assert this role of the fact type:
|
798
|
+
role = @constellation.Role(fact_type, fact_type.all_role.size, player)
|
799
|
+
role.role_name = role_name if role_name
|
800
|
+
debug "Concept #{player.name} found, created role #{role.describe} by binding #{binding.inspect}"
|
801
|
+
@symbols.roles_by_binding[binding] = role
|
802
|
+
else # Subsequent readings
|
803
|
+
#debug "Looking for role #{binding.inspect} in bindings #{@symbols.roles_by_binding.inspect}"
|
804
|
+
role = @symbols.roles_by_binding[binding]
|
805
|
+
raise "Role #{binding.inspect} not found in prior readings" if !role
|
806
|
+
player = role.concept
|
807
|
+
end
|
808
|
+
roles << role
|
809
|
+
|
810
|
+
# Create the RoleRefs for the RoleSequence
|
811
|
+
|
812
|
+
role_ref = @constellation.RoleRef(role_sequence, index, :role => roles[index])
|
813
|
+
leading_adjective = role_phrase[:leading_adjective]
|
814
|
+
role_ref.leading_adjective = leading_adjective if leading_adjective
|
815
|
+
trailing_adjective = role_phrase[:trailing_adjective]
|
816
|
+
role_ref.trailing_adjective = trailing_adjective if trailing_adjective
|
817
|
+
end
|
818
|
+
|
819
|
+
# Create any embedded constraints:
|
820
|
+
debug "Creating embedded presence constraints for #{fact_type.describe}" do
|
821
|
+
create_embedded_presence_constraints(fact_type, role_phrases, roles)
|
822
|
+
end
|
823
|
+
|
824
|
+
process_qualifiers(role_sequence, qualifiers)
|
825
|
+
|
826
|
+
# Save the first role sequence to be used for a default PresenceConstraint
|
827
|
+
add_reading(fact_type, role_sequence, reading)
|
828
|
+
end
|
829
|
+
fact_type
|
830
|
+
end
|
831
|
+
|
832
|
+
def fact_type_identification(fact_type, name, prefer)
|
833
|
+
if !@symbols.embedded_presence_constraints.detect{|pc| pc.max_frequency == 1}
|
834
|
+
first_role_sequence = fact_type.all_reading[0].role_sequence
|
835
|
+
identifier = @constellation.PresenceConstraint(
|
836
|
+
:new,
|
837
|
+
:vocabulary => @vocabulary,
|
838
|
+
:name => "#{name}PK", # Is this a useful name?
|
839
|
+
:role_sequence => first_role_sequence,
|
840
|
+
:is_preferred_identifier => prefer,
|
841
|
+
:max_frequency => 1 # Unique
|
842
|
+
)
|
843
|
+
# REVISIT: The UC might be provided later as an external constraint, relax this rule:
|
844
|
+
#raise "'#{fact_type.default_reading}': non-unary fact types having no uniqueness constraints must be objectified (named)" unless fact_type.entity_type
|
845
|
+
debug "Made default fact type identifier #{identifier.object_id} over #{first_role_sequence.describe} in #{fact_type.describe}"
|
846
|
+
elsif prefer
|
847
|
+
#debug "Made fact type identifier #{identifier.object_id} preferred over #{@symbols.embedded_presence_constraints[0].role_sequence.describe} in #{fact_type.describe}"
|
848
|
+
@symbols.embedded_presence_constraints[0].is_preferred_identifier = true
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
# Categorise the fact type clauses according to the set of role player names
|
853
|
+
# Return an array where each element is an array of clauses, the clauses having
|
854
|
+
# matching players, and otherwise preserving the order of definition.
|
855
|
+
def clauses_by_fact_type(clauses)
|
856
|
+
clause_group_by_role_players = {}
|
857
|
+
clauses.inject([]) do |clause_groups, clause|
|
858
|
+
type, qualifiers, reading = *clause
|
859
|
+
|
860
|
+
debug "Clause: #{clause.inspect}"
|
861
|
+
roles = reading.map do |phrase|
|
862
|
+
Hash === phrase ? phrase[:binding] : nil
|
863
|
+
end.compact
|
864
|
+
|
865
|
+
# Look for an existing clause group involving these players, or make one:
|
866
|
+
clause_group = clause_group_by_role_players[key = roles.sort]
|
867
|
+
if clause_group # Another clause for an existing clause group
|
868
|
+
clause_group << clause
|
869
|
+
else # A new clause group
|
870
|
+
clause_groups << (clause_group_by_role_players[key] = [clause])
|
871
|
+
end
|
872
|
+
clause_groups
|
873
|
+
end
|
874
|
+
end
|
875
|
+
|
876
|
+
# For each fact reading there may be embedded mandatory, uniqueness or frequency constraints:
|
877
|
+
def create_embedded_presence_constraints(fact_type, role_phrases, roles)
|
878
|
+
embedded_presence_constraints = []
|
879
|
+
role_phrases.zip(roles).each_with_index do |role_pair, index|
|
880
|
+
role_phrase, role = *role_pair
|
881
|
+
|
882
|
+
next unless quantifier = role_phrase[:quantifier]
|
883
|
+
|
884
|
+
debug "Processing embedded constraint #{quantifier.inspect} on #{role.concept.name} in #{fact_type.describe}" do
|
885
|
+
constrained_roles = roles.clone
|
886
|
+
constrained_roles.delete_at(index)
|
887
|
+
constraint = find_pc_over_roles(constrained_roles)
|
888
|
+
if constraint
|
889
|
+
debug "Setting max frequency to #{quantifier[1]} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
|
890
|
+
constraint.max_frequency = quantifier[1]
|
891
|
+
else
|
892
|
+
role_sequence = @constellation.RoleSequence(:new)
|
893
|
+
constrained_roles.each_with_index do |constrained_role, i|
|
894
|
+
role_ref = @constellation.RoleRef(role_sequence, i, :role => constrained_role)
|
895
|
+
end
|
896
|
+
constraint = @constellation.PresenceConstraint(
|
897
|
+
:new,
|
898
|
+
:vocabulary => @vocabulary,
|
899
|
+
:role_sequence => role_sequence,
|
900
|
+
:is_mandatory => quantifier[0] && quantifier[0] > 0, # REVISIT: Check "maybe" qualifier?
|
901
|
+
:max_frequency => quantifier[1],
|
902
|
+
:min_frequency => quantifier[0]
|
903
|
+
)
|
904
|
+
embedded_presence_constraints << constraint
|
905
|
+
debug "Made new PC min=#{quantifier[0].inspect} max=#{quantifier[1].inspect} constraint #{constraint.object_id} over #{(e = fact_type.entity_type) ? e.name : role_sequence.describe} in #{fact_type.describe}"
|
906
|
+
end
|
907
|
+
end
|
908
|
+
end
|
909
|
+
@symbols.embedded_presence_constraints += embedded_presence_constraints
|
910
|
+
end
|
911
|
+
|
912
|
+
def process_qualifiers(role_sequence, qualifiers)
|
913
|
+
return unless qualifiers.size > 0
|
914
|
+
qualifiers.sort!
|
915
|
+
|
916
|
+
# Process the ring constraints:
|
917
|
+
ring_constraints, qualifiers = qualifiers.partition{|q| RingTypes.include?(q) }
|
918
|
+
unless ring_constraints.empty?
|
919
|
+
# A Ring may be over a supertype/subtype pair, and this won't find that.
|
920
|
+
role_refs = role_sequence.all_role_ref
|
921
|
+
role_pairs = []
|
922
|
+
player_supertypes_by_role = role_refs.map{|rr|
|
923
|
+
concept = rr.role.concept
|
924
|
+
EntityType === concept ? supertypes(concept) : [concept]
|
925
|
+
}
|
926
|
+
role_refs.each_with_index{|rr1, i|
|
927
|
+
player1 = rr1.role.concept
|
928
|
+
(i+1...role_refs.size).each{|j|
|
929
|
+
rr2 = role_refs[j]
|
930
|
+
player2 = rr2.role.concept
|
931
|
+
if player_supertypes_by_role[i] - player_supertypes_by_role[j] != player_supertypes_by_role[i]
|
932
|
+
role_pairs << [rr1.role, rr2.role]
|
933
|
+
end
|
934
|
+
}
|
935
|
+
}
|
936
|
+
raise "ring constraint (#{ring_constraints*" "}) role pair not found" if role_pairs.size == 0
|
937
|
+
raise "ring constraint (#{ring_constraints*" "}) is ambiguous over roles of #{role_pairs.map{|rp| rp.map{|r| r.concept.name}}.inspect}" if role_pairs.size > 1
|
938
|
+
roles = role_pairs[0]
|
939
|
+
|
940
|
+
# Ensure that the keys in RingPairs follow others:
|
941
|
+
ring_constraints = ring_constraints.partition{|rc| !RingPairs.keys.include?(rc.downcase.to_sym) }.flatten
|
942
|
+
|
943
|
+
if ring_constraints.size > 1 and !RingPairs[ring_constraints[-1].to_sym].include?(ring_constraints[0].to_sym)
|
944
|
+
raise "incompatible ring constraint types (#{ring_constraints*", "})"
|
945
|
+
end
|
946
|
+
ring_type = ring_constraints.map{|c| c.capitalize}*""
|
947
|
+
|
948
|
+
ring = @constellation.RingConstraint(
|
949
|
+
:new,
|
950
|
+
:vocabulary => @vocabulary,
|
951
|
+
# :name => name, # REVISIT: Create a name for Ring Constraints?
|
952
|
+
:role => roles[0],
|
953
|
+
:other_role => roles[1],
|
954
|
+
:ring_type => ring_type
|
955
|
+
)
|
956
|
+
|
957
|
+
debug "Added #{ring.verbalise} #{ring.class.roles.keys.map{|k|"#{k} => "+ring.send(k).verbalise}*", "}"
|
958
|
+
end
|
959
|
+
|
960
|
+
return unless qualifiers.size > 0
|
961
|
+
|
962
|
+
# Process the remaining qualifiers:
|
963
|
+
puts "REVISIT: Qualifiers #{qualifiers.inspect} over #{role_sequence.describe}"
|
964
|
+
end
|
965
|
+
|
966
|
+
def find_pc_over_roles(roles)
|
967
|
+
return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
|
968
|
+
roles[0].all_role_ref.each do |role_ref|
|
969
|
+
next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
|
970
|
+
pc = role_ref.role_sequence.all_presence_constraint[0]
|
971
|
+
#puts "Existing PresenceConstraint matches those roles!" if pc
|
972
|
+
return pc if pc
|
973
|
+
end
|
974
|
+
nil
|
975
|
+
end
|
976
|
+
|
977
|
+
def add_reading(fact_type, role_sequence, reading)
|
978
|
+
ordinal = (fact_type.all_reading.map(&:ordinal).max||-1) + 1 # Use the next unused ordinal
|
979
|
+
defined_reading = @constellation.Reading(fact_type, ordinal, :role_sequence => role_sequence)
|
980
|
+
role_num = -1
|
981
|
+
defined_reading.reading_text = reading.map {|phrase|
|
982
|
+
Hash === phrase ? "{#{role_num += 1}}" : phrase
|
983
|
+
}*" "
|
984
|
+
raise "Wrong number of players (#{role_num+1}) found in reading #{defined_reading.reading_text} over #{fact_type.describe}" if role_num+1 != fact_type.all_role.size
|
985
|
+
debug "Added reading #{defined_reading.reading_text}"
|
986
|
+
end
|
987
|
+
|
988
|
+
# Return an array of this entity type and all its supertypes, transitively:
|
989
|
+
def supertypes(o)
|
990
|
+
([o] + o.all_supertype_inheritance.map{|ti| supertypes(ti.supertype)}.flatten).uniq
|
991
|
+
end
|
992
|
+
|
993
|
+
def concept_by_name(name)
|
994
|
+
player = @constellation.Concept[[name, @vocabulary.identifying_role_values]]
|
995
|
+
|
996
|
+
# REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
|
997
|
+
if !player && %w{Date DateAndTime Time}.include?(name)
|
998
|
+
player = @constellation.ValueType(name, @vocabulary.identifying_role_values)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
if (!player && @symbols.allowed_forward[name])
|
1002
|
+
player = @constellation.EntityType(name, @vocabulary)
|
1003
|
+
end
|
1004
|
+
player
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
class SymbolTable #:nodoc:all
|
1008
|
+
# Externally built tables used in this binding context:
|
1009
|
+
attr_reader :roles_by_binding
|
1010
|
+
attr_accessor :embedded_presence_constraints
|
1011
|
+
attr_accessor :allowed_forward
|
1012
|
+
attr_reader :constellation
|
1013
|
+
attr_reader :vocabulary
|
1014
|
+
attr_reader :bindings_by_concept
|
1015
|
+
attr_reader :role_names
|
1016
|
+
|
1017
|
+
# A Binding here is a form of reference to a concept, being a name and optional adjectives, possibly designated by a role name:
|
1018
|
+
Binding = Struct.new("Binding", :concept, :name, :leading_adjective, :trailing_adjective, :role_name)
|
1019
|
+
class Binding
|
1020
|
+
def inspect
|
1021
|
+
"Binding(#{concept.class.basename} #{concept.name}, #{[leading_adjective, name, trailing_adjective].compact*"-"}#{role_name ? " (as #{role_name})" : ""})"
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
# Any ordering works to allow a hash to be keyed by a set (unordered array) of Bindings:
|
1025
|
+
def <=>(other)
|
1026
|
+
object_id <=> other.object_id
|
1027
|
+
end
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
def initialize(constellation, vocabulary)
|
1031
|
+
@constellation = constellation
|
1032
|
+
@vocabulary = vocabulary
|
1033
|
+
@bindings_by_concept = Hash.new {|h, k| h[k] = [] } # Indexed by Binding#name, maybe multiple entries for each name
|
1034
|
+
@role_names = {}
|
1035
|
+
|
1036
|
+
@embedded_presence_constraints = []
|
1037
|
+
@roles_by_binding = {} # Build a hash of allowed bindings on first reading (check against it on subsequent ones)
|
1038
|
+
@allowed_forward = {} # No roles may be forward-referenced
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
#
|
1042
|
+
# This method is the guts of role matching.
|
1043
|
+
# "words" may be a single word (and then the adjectives may also be used) or two words.
|
1044
|
+
# In either case a word is expected to be a defined concept or role name.
|
1045
|
+
# If a role_name is provided here, that's a *definition* and will only be accepted if legal
|
1046
|
+
# If allowed_forward is true, words is a single word and is not defined, create a forward Entity
|
1047
|
+
# If leading_speculative or trailing_speculative is true, the adjectives may not apply. If they do apply, use them.
|
1048
|
+
# If loose_binding_except is true, it's a hash containing names that may *not* be loose-bound... else none may.
|
1049
|
+
#
|
1050
|
+
# Loose binding is when a word without an adjective matches a role with, or vice verse.
|
1051
|
+
#
|
1052
|
+
def bind(words, leading_adjective = nil, trailing_adjective = nil, role_name = nil, allowed_forward = false, leading_speculative = false, trailing_speculative = false, loose_binding_except = nil)
|
1053
|
+
words = Array(words)
|
1054
|
+
if (words.size > 2 or words.size == 2 && (leading_adjective or trailing_adjective or allowed_forward))
|
1055
|
+
raise "role has too many adjectives '#{[leading_adjective, words, trailing_adjective].flatten.compact*" "}'"
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
# Check for use of a role name, valid if they haven't used any adjectives or tried to define a role_name:
|
1059
|
+
binding = @role_names[words[0]]
|
1060
|
+
if binding && words.size == 1 # If ok, this is it.
|
1061
|
+
raise "May not use existing role name '#{words[0]}' to define a new role name" if role_name
|
1062
|
+
if (leading_adjective && !leading_speculative) || (trailing_adjective && !trailing_speculative)
|
1063
|
+
raise "May not use existing role name '#{words[0]}' with adjectives"
|
1064
|
+
end
|
1065
|
+
return binding.concept, binding
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
# Look for an existing definition
|
1069
|
+
# If we have more than one word that might be the concept name, find which it is:
|
1070
|
+
words.each do |w|
|
1071
|
+
# Find the existing defined binding that matches this one:
|
1072
|
+
bindings = @bindings_by_concept[w]
|
1073
|
+
best_match = nil
|
1074
|
+
matched_adjectives = 0
|
1075
|
+
bindings.each do |binding|
|
1076
|
+
# Adjectives defined on the binding must be matched unless loose binding is allowed.
|
1077
|
+
loose_ok = loose_binding_except and !loose_binding_except[binding.concept.name]
|
1078
|
+
|
1079
|
+
# Don't allow binding a new role name to an existing one:
|
1080
|
+
next if role_name and role_name != binding.role_name
|
1081
|
+
|
1082
|
+
quality = 0
|
1083
|
+
if binding.leading_adjective != leading_adjective
|
1084
|
+
next if binding.leading_adjective && leading_adjective # Both set, but different
|
1085
|
+
next if !loose_ok && (!leading_speculative || !leading_adjective)
|
1086
|
+
quality += 1
|
1087
|
+
end
|
1088
|
+
|
1089
|
+
if binding.trailing_adjective != trailing_adjective
|
1090
|
+
next if binding.trailing_adjective && trailing_adjective # Both set, but different
|
1091
|
+
next if !loose_ok && (!trailing_speculative || !trailing_adjective)
|
1092
|
+
quality += 1
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
quality += 1 unless binding.role_name # A role name that was not matched... better if there wasn't one
|
1096
|
+
|
1097
|
+
if (quality > matched_adjectives || !best_match)
|
1098
|
+
best_match = binding # A better match than we had before
|
1099
|
+
matched_adjectives = quality
|
1100
|
+
break unless loose_ok || leading_speculative || trailing_speculative
|
1101
|
+
end
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
if best_match
|
1105
|
+
# We've found the best existing definition
|
1106
|
+
|
1107
|
+
# Indicate which speculative adjectives were used so the clauses can be deleted:
|
1108
|
+
leading_adjective.replace("") if best_match.leading_adjective and leading_adjective and leading_speculative
|
1109
|
+
trailing_adjective.replace("") if best_match.trailing_adjective and trailing_adjective and trailing_speculative
|
1110
|
+
|
1111
|
+
return best_match.concept, best_match
|
1112
|
+
end
|
1113
|
+
|
1114
|
+
# No existing defined binding. Look up an existing concept of this name:
|
1115
|
+
player = concept(w, allowed_forward)
|
1116
|
+
next unless player
|
1117
|
+
|
1118
|
+
# Found a new binding for this player, save it.
|
1119
|
+
|
1120
|
+
# Check that a trailing adjective isn't an existing role name or concept:
|
1121
|
+
trailing_word = words[1] if w == words[0]
|
1122
|
+
if trailing_word
|
1123
|
+
raise "May not use existing role name '#{trailing_word}' with a new name or with adjectives" if @role_names[trailing_word]
|
1124
|
+
raise "ambiguous concept reference #{words*" '"}'" if concept(trailing_word)
|
1125
|
+
end
|
1126
|
+
leading_word = words[0] if w != words[0]
|
1127
|
+
|
1128
|
+
raise "may not redefine existing concept '#{role_name}' as a role name" if role_name and concept(role_name)
|
1129
|
+
|
1130
|
+
binding = Binding.new(
|
1131
|
+
player,
|
1132
|
+
w,
|
1133
|
+
(!leading_speculative && leading_adjective) || leading_word,
|
1134
|
+
(!trailing_speculative && trailing_adjective) || trailing_word,
|
1135
|
+
role_name
|
1136
|
+
)
|
1137
|
+
@bindings_by_concept[binding.name] << binding
|
1138
|
+
@role_names[binding.role_name] = binding if role_name
|
1139
|
+
return binding.concept, binding
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
# Not found.
|
1143
|
+
return nil
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
# return the EntityType or ValueType this name refers to:
|
1147
|
+
def concept(name, allowed_forward = false)
|
1148
|
+
# See if the name is a defined concept in this vocabulary:
|
1149
|
+
player = @constellation.Concept[[name, virv = @vocabulary.identifying_role_values]]
|
1150
|
+
|
1151
|
+
# REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
|
1152
|
+
if !player && %w{Date DateAndTime Time}.include?(name)
|
1153
|
+
player = @constellation.ValueType(name, virv)
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
if !player && allowed_forward
|
1157
|
+
player = @constellation.EntityType(name, @vocabulary)
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
player
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
def bind_roles_in_clauses(clauses, identification = [])
|
1164
|
+
identification ||= []
|
1165
|
+
bind_roles_in_readings(
|
1166
|
+
clauses.map{|clause| clause[2]}, # Extract the readings
|
1167
|
+
single_word_identifiers = identification.map{|i| i.size == 1 ? i[0] : nil}.compact.uniq
|
1168
|
+
)
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
#
|
1172
|
+
# Walk through all phrases of all readings identifying role players.
|
1173
|
+
# Each role player phrase gets a :binding key added to it.
|
1174
|
+
#
|
1175
|
+
# Any adjectives that the parser didn't recognise are merged with their players here,
|
1176
|
+
# as long as they're indicated as adjectives of that player somewhere in the readings.
|
1177
|
+
#
|
1178
|
+
# Other words are turned from phrases (hashes) into simple strings.
|
1179
|
+
#
|
1180
|
+
def bind_roles_in_readings(readings, allowed_forwards = [])
|
1181
|
+
disallow_loose_binding = allowed_forwards.inject({}) { |h, v| h[v] = true; h }
|
1182
|
+
readings.each do |reading|
|
1183
|
+
debug :bind, "Binding a reading"
|
1184
|
+
|
1185
|
+
if (reading.size == 1 && reading[0][:subtype])
|
1186
|
+
# REVISIT: Handle a subtype reading here
|
1187
|
+
subtype_name = reading[0][:subtype]
|
1188
|
+
supertype_name = reading[0][:supertype]
|
1189
|
+
subtype, subtype_binding = bind(subtype_name)
|
1190
|
+
supertype, supertype_binding = bind(supertype_name)
|
1191
|
+
reading.replace([
|
1192
|
+
"!SUBTYPE!",
|
1193
|
+
{:word => subtype, :binding => subtype_binding },
|
1194
|
+
{:word => supertype, :binding => supertype_binding }
|
1195
|
+
]
|
1196
|
+
)
|
1197
|
+
next
|
1198
|
+
end
|
1199
|
+
|
1200
|
+
phrase_numbers_used_speculatively = []
|
1201
|
+
disallow_loose_binding_this_reading = disallow_loose_binding.clone
|
1202
|
+
reading.each_with_index do |phrase, index|
|
1203
|
+
la = phrase[:leading_adjective]
|
1204
|
+
player_name = phrase[:word]
|
1205
|
+
ta = phrase[:trailing_adjective]
|
1206
|
+
role_name = phrase[:role_name]
|
1207
|
+
|
1208
|
+
# We use the preceeding phrase and/or following phrase speculatively if they're simple words:
|
1209
|
+
preceeding_phrase = nil
|
1210
|
+
following_phrase = nil
|
1211
|
+
if !la && index > 0 && (preceeding_phrase = reading[index-1])
|
1212
|
+
preceeding_phrase = nil unless String === preceeding_phrase || preceeding_phrase.keys == [:word]
|
1213
|
+
la = preceeding_phrase[:word] if Hash === preceeding_phrase
|
1214
|
+
end
|
1215
|
+
if !ta && (following_phrase = reading[index+1])
|
1216
|
+
following_phrase = nil unless following_phrase.keys == [:word]
|
1217
|
+
ta = following_phrase[:word] if following_phrase
|
1218
|
+
end
|
1219
|
+
|
1220
|
+
# If the identification includes this player name as a single word, it's allowed to be forward referenced:
|
1221
|
+
allowed_forward = allowed_forwards.include?(player_name)
|
1222
|
+
|
1223
|
+
debug :bind, "Binding a role: #{[player_name, la, ta, role_name, allowed_forward, !!preceeding_phrase, !!following_phrase].inspect}"
|
1224
|
+
player, binding = bind(
|
1225
|
+
player_name,
|
1226
|
+
la, ta,
|
1227
|
+
role_name,
|
1228
|
+
allowed_forward,
|
1229
|
+
!!preceeding_phrase, !!following_phrase,
|
1230
|
+
reading == readings[0] ? nil : disallow_loose_binding_this_reading # Never allow loose binding on the first reading
|
1231
|
+
)
|
1232
|
+
disallow_loose_binding_this_reading[player.name] = true if player
|
1233
|
+
|
1234
|
+
# Arrange to delete the speculative adjectives that were used:
|
1235
|
+
if preceeding_phrase && preceeding_phrase[:word] == ""
|
1236
|
+
debug :bind, "binding consumed a speculative leading_adjective #{la}"
|
1237
|
+
# The numbers are adjusted to allow for prior deletions.
|
1238
|
+
phrase_numbers_used_speculatively << index-1-phrase_numbers_used_speculatively.size
|
1239
|
+
end
|
1240
|
+
if following_phrase && following_phrase[:word] == ""
|
1241
|
+
debug :bind, "binding consumed a speculative trailing_adjective #{ta}"
|
1242
|
+
phrase_numbers_used_speculatively << index+1-phrase_numbers_used_speculatively.size
|
1243
|
+
end
|
1244
|
+
|
1245
|
+
if player
|
1246
|
+
# Replace the words used to identify the role by a reference to the role itself,
|
1247
|
+
# leaving :quantifier, :function, :restriction and :literal intact
|
1248
|
+
phrase[:binding] = binding
|
1249
|
+
binding
|
1250
|
+
else
|
1251
|
+
raise "Internal error; role #{phrase.inspect} not matched" unless phrase.keys == [:word]
|
1252
|
+
# Just a linking word
|
1253
|
+
reading[index] = phrase[:word]
|
1254
|
+
end
|
1255
|
+
debug :bind, "Bound phrase: #{phrase.inspect}" + " -> " + (player ? player.name+", "+binding.inspect : phrase[:word].inspect)
|
1256
|
+
|
1257
|
+
end
|
1258
|
+
phrase_numbers_used_speculatively.each do |index|
|
1259
|
+
reading.delete_at(index)
|
1260
|
+
end
|
1261
|
+
debug :bind, "Bound reading: #{reading.inspect}"
|
1262
|
+
end
|
1263
|
+
end
|
1264
|
+
end # of SymbolTable class
|
1265
|
+
|
1266
|
+
end
|
1267
|
+
end
|
1268
|
+
end
|