activefacts 0.7.3 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/LICENSE +19 -0
  2. data/Manifest.txt +24 -2
  3. data/Rakefile +25 -3
  4. data/bin/afgen +1 -1
  5. data/bin/cql +13 -2
  6. data/css/offline.css +3 -0
  7. data/css/orm2.css +24 -0
  8. data/css/print.css +8 -0
  9. data/css/style-print.css +357 -0
  10. data/css/style.css +387 -0
  11. data/download.html +85 -0
  12. data/examples/CQL/Address.cql +3 -3
  13. data/examples/CQL/Blog.cql +13 -14
  14. data/examples/CQL/CompanyDirectorEmployee.cql +4 -4
  15. data/examples/CQL/Death.cql +3 -2
  16. data/examples/CQL/Genealogy.cql +13 -11
  17. data/examples/CQL/Marriage.cql +2 -2
  18. data/examples/CQL/Metamodel.cql +136 -93
  19. data/examples/CQL/MultiInheritance.cql +2 -2
  20. data/examples/CQL/OilSupply.cql +14 -10
  21. data/examples/CQL/Orienteering.cql +22 -19
  22. data/examples/CQL/PersonPlaysGame.cql +3 -2
  23. data/examples/CQL/SchoolActivities.cql +4 -2
  24. data/examples/CQL/SimplestUnary.cql +1 -1
  25. data/examples/CQL/SubtypePI.cql +6 -7
  26. data/examples/CQL/Warehousing.cql +16 -19
  27. data/examples/CQL/unit.cql +584 -0
  28. data/examples/index.html +276 -0
  29. data/examples/intro.html +497 -0
  30. data/examples/local.css +20 -0
  31. data/index.html +96 -0
  32. data/lib/activefacts/api/concept.rb +48 -46
  33. data/lib/activefacts/api/constellation.rb +43 -23
  34. data/lib/activefacts/api/entity.rb +2 -2
  35. data/lib/activefacts/api/instance.rb +6 -2
  36. data/lib/activefacts/api/instance_index.rb +5 -0
  37. data/lib/activefacts/api/value.rb +8 -2
  38. data/lib/activefacts/api/vocabulary.rb +15 -10
  39. data/lib/activefacts/cql/CQLParser.treetop +109 -88
  40. data/lib/activefacts/cql/Concepts.treetop +32 -10
  41. data/lib/activefacts/cql/Context.treetop +34 -0
  42. data/lib/activefacts/cql/Expressions.treetop +9 -9
  43. data/lib/activefacts/cql/FactTypes.treetop +30 -31
  44. data/lib/activefacts/cql/Language/English.treetop +50 -0
  45. data/lib/activefacts/cql/LexicalRules.treetop +2 -1
  46. data/lib/activefacts/cql/Terms.treetop +117 -0
  47. data/lib/activefacts/cql/ValueTypes.treetop +152 -0
  48. data/lib/activefacts/cql/compiler.rb +1718 -0
  49. data/lib/activefacts/cql/parser.rb +124 -57
  50. data/lib/activefacts/generate/absorption.rb +1 -1
  51. data/lib/activefacts/generate/cql.rb +111 -100
  52. data/lib/activefacts/generate/cql/html.rb +5 -5
  53. data/lib/activefacts/generate/oo.rb +3 -3
  54. data/lib/activefacts/generate/ordered.rb +51 -19
  55. data/lib/activefacts/generate/ruby.rb +10 -8
  56. data/lib/activefacts/generate/sql/mysql.rb +14 -10
  57. data/lib/activefacts/generate/sql/server.rb +29 -24
  58. data/lib/activefacts/input/cql.rb +9 -1264
  59. data/lib/activefacts/input/orm.rb +213 -200
  60. data/lib/activefacts/persistence/columns.rb +11 -10
  61. data/lib/activefacts/persistence/index.rb +15 -18
  62. data/lib/activefacts/persistence/reference.rb +17 -17
  63. data/lib/activefacts/persistence/tables.rb +50 -51
  64. data/lib/activefacts/version.rb +1 -1
  65. data/lib/activefacts/vocabulary/extensions.rb +79 -8
  66. data/lib/activefacts/vocabulary/metamodel.rb +183 -114
  67. data/spec/absorption_ruby_spec.rb +99 -0
  68. data/spec/absorption_spec.rb +3 -4
  69. data/spec/api/constellation.rb +1 -1
  70. data/spec/api/entity_type.rb +3 -1
  71. data/spec/api/instance.rb +4 -2
  72. data/spec/api/roles.rb +8 -6
  73. data/spec/api_spec.rb +1 -2
  74. data/spec/cql/context_spec.rb +71 -0
  75. data/spec/cql/samples_spec.rb +154 -0
  76. data/spec/cql/unit_spec.rb +375 -0
  77. data/spec/cql_cql_spec.rb +31 -21
  78. data/spec/cql_mysql_spec.rb +70 -0
  79. data/spec/cql_parse_spec.rb +15 -9
  80. data/spec/cql_ruby_spec.rb +27 -13
  81. data/spec/cql_sql_spec.rb +42 -16
  82. data/spec/cql_symbol_tables_spec.rb +2 -3
  83. data/spec/cqldump_spec.rb +7 -7
  84. data/spec/helpers/file_matcher.rb +39 -0
  85. data/spec/norma_cql_spec.rb +20 -12
  86. data/spec/norma_ruby_spec.rb +6 -3
  87. data/spec/norma_sql_spec.rb +6 -3
  88. data/spec/norma_tables_spec.rb +6 -4
  89. data/spec/spec_helper.rb +27 -8
  90. data/status.html +69 -0
  91. data/why.html +60 -0
  92. metadata +34 -11
  93. data/lib/activefacts/cql/DataTypes.treetop +0 -81
  94. data/spec/cql_unit_spec.rb +0 -330
@@ -47,21 +47,67 @@ module ActiveFacts
47
47
  / number s { def value; number.value; end }
48
48
  end
49
49
 
50
+ rule according_to
51
+ 'according' S to
52
+ end
53
+
54
+ rule because
55
+ 'because' S
56
+ end
57
+
58
+ rule as_opposed_to
59
+ as S 'opposed' S to
60
+ end
61
+
62
+ rule so_that
63
+ 'so' S that
64
+ end
65
+
66
+ rule to_avoid
67
+ to s 'avoid' !alphanumeric
68
+ end
69
+
70
+ rule as_agreed_by
71
+ s as s 'agreed' s d:('on' S date)? by s people
72
+ { def value; [ d.empty? ? nil : d.date.value, people.value ]; end }
73
+ end
74
+
75
+ rule date
76
+ s d:(!(by/')') .)+
77
+ { def value; d.text_value.strip; end }
78
+ end
79
+
80
+ rule people
81
+ s h:person s t:(',' s !context_type person s)*
82
+ { def value; [h.text_value] + t.elements.map{|e| e.person.text_value }; end }
83
+ end
84
+
85
+ rule person
86
+ id (s id)*
87
+ end
88
+
50
89
  rule acyclic 'acyclic' !alphanumeric end
51
90
  rule alias 'alias' !alphanumeric end
52
91
  rule all 'all' !alphanumeric end
53
92
  rule and 'and' !alphanumeric end
93
+ rule approximately 'approximately' !alphanumeric end
54
94
  rule as 'as' !alphanumeric end
55
95
  rule at 'at' !alphanumeric end
96
+ rule both 'both' !alphanumeric end
97
+ rule but 'but' !alphanumeric end
56
98
  rule by 'by' !alphanumeric end
99
+ rule converts 'converts' !alphanumeric end
57
100
  rule definitely 'definitely' !alphanumeric end
58
101
  rule each 'each' !alphanumeric end
59
102
  rule either 'either' !alphanumeric end
60
103
  rule entity 'entity' !alphanumeric end
104
+ rule ephemeral 'ephemeral' !alphanumeric end
61
105
  rule exactly 'exactly' !alphanumeric end
62
106
  rule false 'false' !alphanumeric end
107
+ rule feminine 'feminine' !alphanumeric end
63
108
  rule from 'from' !alphanumeric end
64
109
  rule identified ('known'/'identified') !alphanumeric end
110
+ rule independent 'independent' !alphanumeric end
65
111
  rule if 'if' !alphanumeric end
66
112
  rule import 'import' !alphanumeric end
67
113
  rule includes 'includes' !alphanumeric end
@@ -69,6 +115,7 @@ module ActiveFacts
69
115
  rule is 'is' !alphanumeric end
70
116
  rule its 'its' !alphanumeric end
71
117
  rule least 'least' !alphanumeric end
118
+ rule masculine 'masculine' !alphanumeric end
72
119
  rule matches 'matches' !alphanumeric end
73
120
  rule maybe 'maybe' !alphanumeric end
74
121
  rule most 'most' !alphanumeric end
@@ -79,8 +126,11 @@ module ActiveFacts
79
126
  rule one 'one' !alphanumeric end
80
127
  rule only 'only' !alphanumeric end
81
128
  rule or 'or' !alphanumeric end
129
+ rule partitioned 'partitioned' !alphanumeric end
130
+ rule personal 'personal' !alphanumeric end
82
131
  rule restricted 'restricted' !alphanumeric end
83
132
  rule returning 'returning' !alphanumeric end
133
+ rule separate 'separate' !alphanumeric end
84
134
  rule some 'some' !alphanumeric end
85
135
  rule static 'static' !alphanumeric end
86
136
  rule symmetric 'symmetric' !alphanumeric end
@@ -123,7 +123,7 @@ module ActiveFacts
123
123
  end
124
124
 
125
125
  rule exponent
126
- ( [Ee] '-'? [0-9]+ )
126
+ ( [Ee] [-+]? [0-9]+ )
127
127
  end
128
128
 
129
129
  rule hexnumber
@@ -140,6 +140,7 @@ module ActiveFacts
140
140
 
141
141
  rule id
142
142
  alpha alphanumeric*
143
+ { def value; text_value; end }
143
144
  end
144
145
 
145
146
  rule alpha
@@ -0,0 +1,117 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to Term names
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar Terms
10
+ rule term_definition_name
11
+ id s
12
+ t:(!non_term_def id s)*
13
+ { def value
14
+ t.elements.inject([
15
+ id.value
16
+ ]){|a, e| a << e.id.value}*' '
17
+ end
18
+ }
19
+ end
20
+
21
+ rule non_term_def
22
+ entity_prefix
23
+ / written_as # Value type
24
+ / is s mapping_pragmas where # Objectified type
25
+ / non_role_word
26
+ end
27
+
28
+ rule entity_prefix
29
+ is s identified_by / subtype_prefix
30
+ end
31
+
32
+ rule prescan
33
+ s (
34
+ term_definition_name s entity_prefix
35
+ &{|e| input.context.entity_type(e[0].value); true }
36
+ /
37
+ term_definition_name written_as
38
+ &{|e| input.context.value_type(e[0].value); true }
39
+ /
40
+ term_definition_name s is s mapping_pragmas where
41
+ &{|e| input.context.objectified_fact_type(e[0].value); true }
42
+ )?
43
+ prescan_rest
44
+ &{|s|
45
+ @terminal_failures = []
46
+ @max_terminal_failure_index = start_index
47
+
48
+ # puts "========== prescan is complete on #{(s.map{|e|e.text_value}*" ").inspect} =========="
49
+ false # REVISIT: Wipe any terminal failures that were added? Can there be anything relevant?
50
+ }
51
+ end
52
+
53
+ # Do a first-pass mainly lexical analysis, looking only for role name definitions
54
+ # For use in detecting terms later.
55
+ # REVISIT: and adjectives
56
+ rule prescan_rest
57
+ &{ input.context.reset_role_names }
58
+ (
59
+ context # Context notes have different lexical conventions
60
+ / role_name # A role name definition
61
+ &{|s| input.context.role_name(s[0].value) }
62
+ # Adjective definitions
63
+ / new_adjective_term
64
+ / global_term # If we see A B - C D, don't recognise B as a new adjective for C D.
65
+ / id
66
+ / range # Covers all numbers and strings
67
+ / comparator # handle two-character operators
68
+ / S # White space and comments, must precede / and *
69
+ / [-+{}\[\].,:^/%*()] # All other punctuation and operators
70
+ )* [?;] s
71
+ end
72
+
73
+ rule new_adjective_term
74
+ !global_term adj:id '-' s global_term
75
+ &{|s| input.context.new_leading_adjective_term(s[1].value, s[4].value) } # Definitely a new leading adjective for this term
76
+ /
77
+ global_term s '-' !global_term adj:id
78
+ &{|s| input.context.new_trailing_adjective_term(s[4].value, s[1].value) } # Definitely a new trailing adjective for this term
79
+ end
80
+
81
+ # This is the rule to use after the prescan; it only succeeds on a complete term or role reference
82
+ rule term
83
+ s head:id &{|s| w = s[1].text_value; input.context.term_starts(w) }
84
+ tail:(s '-'? s w:id &{|s| w = s[3].text_value; input.context.term_continues(w) } )*
85
+ { def value
86
+ name = tail.elements.inject(head.value) { |t, e| "#{t} #{e.w.value}" }
87
+ #puts "Detected term #{name.inspect}"
88
+ name
89
+ end
90
+ }
91
+ end
92
+
93
+ rule global_term
94
+ # This rule shouldn't be used outside the prescan, it will memoize the wrong things.
95
+ head:id &{|s| input.context.term_starts(s[0].text_value) }
96
+ tail:(s w:id &{|s| input.context.term_continues(s[1].text_value) } )*
97
+ { def value
98
+ tail.elements.inject(head.value) { |t, e| "#{t} #{e.w.value}" }
99
+ end
100
+ }
101
+ end
102
+
103
+ rule non_role_word
104
+ # These words are illegal in (but maybe ok following) a reading where a role word is expected:
105
+ and
106
+ / where
107
+ / if
108
+ / only
109
+ / or
110
+ / quantifier
111
+ / restriction
112
+ / but
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,152 @@
1
+ #
2
+ # ActiveFacts CQL Parser.
3
+ # Parse rules relating to ValueType definitions.
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module CQL
9
+ grammar ValueTypes
10
+ rule value_type
11
+ s term_definition_name
12
+ written_as
13
+ &{|e| input.context.value_type(e[1].value); true }
14
+ base:id s
15
+ value_type_parameters
16
+ units
17
+ r:restriction?
18
+ mapping_pragmas
19
+ s ';' s
20
+ {
21
+ def defined_type
22
+ [
23
+ :value_type,
24
+ base.value,
25
+ value_type_parameters.values,
26
+ units.value,
27
+ (!r.empty? ? r.ranges : []),
28
+ mapping_pragmas.value
29
+ ]
30
+ end
31
+
32
+ def value
33
+ [ term_definition_name.value,
34
+ defined_type
35
+ ]
36
+ end
37
+ }
38
+ end
39
+
40
+ rule value_type_parameters
41
+ '(' s tpl:type_parameter_list? ')' s
42
+ { def values; tpl.empty? ? [] : tpl.values; end }
43
+ / s
44
+ { def values; []; end }
45
+ end
46
+
47
+ rule type_parameter_list
48
+ head:number s tail:( ',' s number s )*
49
+ {
50
+ def values
51
+ [head.value] + tail.elements.map{|i| i.number.value}
52
+ end
53
+ }
54
+ end
55
+
56
+ rule unit_definition
57
+ u:(
58
+ unit_coefficient units s o:unit_offset?
59
+ converts s a:(approximately s)? to s
60
+ singular:id s plural:('/' s p:id s)?
61
+ /
62
+ singular:id s plural:('/' s p:id s)?
63
+ converts s a:(approximately s)? to s
64
+ unit_coefficient units s o:unit_offset?
65
+ )
66
+ q:(approximately / ephemeral)? s
67
+ ';' s
68
+ { def value
69
+ r = { :coefficient => u.unit_coefficient.value,
70
+ :offset => u.o.text_value.empty? ? 0 : u.o.value,
71
+ :base => u.units.value,
72
+ :singular => u.singular.text_value,
73
+ }
74
+ r[:plural] = u.plural.p.text_value unless u.plural.text_value.empty?
75
+ r[:approximately] = true if q.text_value == 'approximately' || !u.a.text_value.empty?
76
+ r[:ephemeral] = true if q.text_value == 'ephemeral'
77
+ [ nil, [ :unit, r ] ]
78
+ end
79
+ }
80
+ end
81
+
82
+ rule unit_coefficient
83
+ numerator:number denominator:(s '/' s number)? s
84
+ { def value
85
+ { :numerator => numerator.text_value,
86
+ :denominator => (denominator.text_value.empty? ? "1" : denominator.number.text_value)
87
+ }
88
+ end
89
+ }
90
+ end
91
+
92
+ rule unit_offset
93
+ sign:[-+] s number s
94
+ { def value
95
+ sign.text_value == '-' ? "-"+number.text_value : number.text_value
96
+ end
97
+ }
98
+ end
99
+
100
+ rule units
101
+ '' u:(!non_unit unit s tail:(!non_unit unit s)* div:('/' s unit s tail:(!non_unit unit s)*)?)?
102
+ { def value
103
+ if u.text_value.empty?
104
+ []
105
+ else
106
+ u.tail.elements.inject([u.unit.value]) { |a, e| a << e.unit.value } +
107
+ (u.div.text_value.empty? ? [] : u.div.tail.elements.inject([u.div.unit.inverse]) { |a, e| a << e.unit.inverse })
108
+ end
109
+ end
110
+ }
111
+ end
112
+
113
+ rule non_unit
114
+ restricted / converts / approximately / ephemeral
115
+ end
116
+
117
+ rule unit
118
+ unit_name:id pow:('^' '-'? [0-9])?
119
+ { def value
120
+ [unit_name.text_value, pow.text_value.empty? ? 1 : Integer(pow.text_value[1..-1])]
121
+ end
122
+ def inverse
123
+ a = value
124
+ a[1] = -a[1]
125
+ a
126
+ end
127
+ }
128
+ end
129
+
130
+ rule restriction
131
+ restricted s to s range_list s units
132
+ {
133
+ def ranges
134
+ range_list.ranges # REVISIT: Use the units here
135
+ end
136
+ }
137
+ end
138
+
139
+ rule range_list
140
+ '{' s
141
+ head:range s tail:( ',' s range )*
142
+ '}' s
143
+ {
144
+ def ranges
145
+ [head.value] + tail.elements.map{|e| e.range.value }
146
+ end
147
+ }
148
+ end
149
+
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,1718 @@
1
+ # Compile a CQL string into an ActiveFacts vocabulary.
2
+ #
3
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/vocabulary'
6
+ require 'activefacts/cql/parser'
7
+
8
+ module ActiveFacts
9
+ module CQL
10
+ class Compiler < ActiveFacts::CQL::Parser
11
+ attr_reader :vocabulary
12
+
13
+ class SymbolTable; end #:nodoc:
14
+
15
+ RingTypes = %w{acyclic intransitive symmetric asymmetric transitive antisymmetric irreflexive reflexive}
16
+ RingPairs = {
17
+ :intransitive => [:acyclic, :asymmetric, :symmetric],
18
+ :irreflexive => [:symmetric]
19
+ }
20
+
21
+ def initialize(string, filename = "stdin")
22
+ @string = string # The contents of the input string
23
+ @filename = filename
24
+
25
+ @constellation = ActiveFacts::API::Constellation.new(ActiveFacts::Metamodel)
26
+
27
+ # The syntax tree created from each parsed CQL statement gets passed to the block.
28
+ # parse_all returns an array of the block's non-nil return values.
29
+ result = parse_all(@string, :definition) do |node|
30
+ begin
31
+ kind, *value = d = definition(node)
32
+ #print "Parsed '#{node.text_value}' (#{kind.inspect})"
33
+ #print " to "; p value
34
+ raise "Definition of #{kind} must be in a vocabulary" if kind != :vocabulary and !@vocabulary
35
+ case kind
36
+ when :vocabulary
37
+ @vocabulary = @constellation.Vocabulary(value[0])
38
+ when :value_type
39
+ value_type *value
40
+ when :entity_type
41
+ entity_type *value
42
+ when :fact_type
43
+ fact_type *value
44
+ when :constraint
45
+ constraint *value
46
+ when :fact
47
+ fact *value
48
+ when :unit
49
+ unit *value
50
+ else
51
+ print "="*20+" unhandled declaration type: "; p kind, value
52
+ end
53
+ rescue => e
54
+ puts e.message+"\n\t"+e.backtrace*"\n\t" if debug :exception
55
+ start_line = @string.line_of(node.interval.first)
56
+ end_line = @string.line_of(node.interval.last-1)
57
+ lines = start_line != end_line ? "s #{start_line}-#{end_line}" : " #{start_line.to_s}"
58
+ raise "at line#{lines} #{e.message.strip}"
59
+ end
60
+
61
+ nil
62
+ end
63
+ raise failure_reason unless result
64
+ @vocabulary
65
+ end
66
+
67
+ def context
68
+ @context ||= Context.new(self)
69
+ end
70
+
71
+ private
72
+ def value_type(name, base_type_name, parameters, unit, ranges, mapping_pragmas)
73
+ length, scale = *parameters
74
+
75
+ # Create the base type:
76
+ base_type = nil
77
+ if (base_type_name != name)
78
+ unless base_type = @constellation.ValueType[[@vocabulary, @constellation.Name(base_type_name)]]
79
+ #puts "REVISIT: Creating base ValueType #{base_type_name} in #{@vocabulary.inspect}"
80
+ base_type = @constellation.ValueType(@vocabulary, base_type_name)
81
+ return if base_type_name == name
82
+ end
83
+ end
84
+
85
+ # Create and initialise the ValueType:
86
+ vt = @constellation.ValueType(@vocabulary, name)
87
+ vt.supertype = base_type if base_type
88
+ vt.length = length if length
89
+ vt.scale = scale if scale
90
+
91
+ # REVISIT: Find and apply the units
92
+
93
+ if ranges.size != 0
94
+ vt.value_restriction = value_restriction ranges
95
+ end
96
+ end
97
+
98
+ def value_restriction(ranges)
99
+ vr = @constellation.ValueRestriction(:new)
100
+ ranges.each do |range|
101
+ min, max = Array === range ? range : [range, range]
102
+ v_range = @constellation.ValueRange(
103
+ min ? [[String === min ? eval(min) : min.to_s, String === min, nil], true] : nil,
104
+ max ? [[String === max ? eval(max) : max.to_s, String === max, nil], true] : nil
105
+ )
106
+ ar = @constellation.AllowedRange(vr, v_range)
107
+ end
108
+ vr
109
+ end
110
+
111
+ def entity_type(name, supertypes, identification, mapping_pragmas, clauses)
112
+ #puts "Entity Type #{name}, supertypes #{supertypes.inspect}, id #{identification.inspect}, clauses = #{clauses.inspect}"
113
+ debug :entity, "Defining Entity Type #{name}" do
114
+ # Assert the entity:
115
+ # If this entity was forward referenced, this won't be a new object, and will subsume its roles
116
+ entity_type = @constellation.EntityType(@vocabulary, name)
117
+ entity_type.is_independent = true if (mapping_pragmas.include? 'independent')
118
+
119
+ # Set up its supertypes:
120
+ supertypes.each do |supertype_name|
121
+ add_supertype(entity_type, supertype_name, !identification && supertype_name == supertypes[0], mapping_pragmas)
122
+ end
123
+
124
+ # If we're using a common identification mode, find or create the necessary ValueTypes first:
125
+ vt_name = vt = nil
126
+ if identification && identification[:mode]
127
+ mode = identification[:mode] # An identification mode
128
+
129
+ # Find or Create an appropriate ValueType called "#{name}#{mode}", of the supertype "#{mode}"
130
+ vt_name = "#{name}#{mode}"
131
+ unless vt = @constellation.ValueType[[@vocabulary, vt_name]]
132
+ base_vt = @constellation.ValueType(@vocabulary, mode)
133
+ vt = @constellation.ValueType(@vocabulary, vt_name, :supertype => base_vt)
134
+ if parameters = identification[:parameters]
135
+ length, scale = *parameters
136
+ vt.length = length if length
137
+ vt.scale = scale if scale
138
+ end
139
+ end
140
+ # REVISIT: If we do this, it gets emitted twice when we generate CQL. The generator should detect that the restriction is the same and not emit it.
141
+ #if (ranges = identification[:restriction])
142
+ # vt.value_restriction = value_restriction(ranges)
143
+ #end
144
+ end
145
+
146
+ # Use a two-pass algorithm for entity fact types...
147
+ # The first step is to find all role references and definitions in the clauses
148
+ # After bind_roles, each phrase in each clause is either:
149
+ # * a string, which is a linking word, or
150
+ # * the phrase hash augmented with a :binding=>Binding
151
+ @symbols = SymbolTable.new(@constellation, @vocabulary)
152
+ @symbols.bind_roles_in_clauses(clauses, identification ? identification[:roles] : nil)
153
+
154
+ # Next arrange the clauses according to what fact they belong to,
155
+ # then process each fact type using normal fact type processing.
156
+ # That way if we find a fact type here having none of the players being the
157
+ # entity type, we know it's an objectified fact type. The CQL syntax might make
158
+ # us come here with such a case when the fact type is a subtype of some entity type,
159
+ # such as occurs in the Metamodel with TypeInheritance.
160
+
161
+ # N.B. This doesn't allow forward identification by roles with adjectives (see the i[0]):
162
+ @symbols.allowed_forward = (ir = identification && identification[:roles]) ? ir.inject({}){|h, i| h[i[0]] = true; h} : {}
163
+
164
+ identifying_fact_types = {}
165
+ clauses_by_fact_type(clauses).each do |clauses_for_fact_type|
166
+ fact_type = nil
167
+ @symbols.embedded_presence_constraints = [] # Clear embedded_presence_constraints for each fact type
168
+ debug :entity, "New Fact Type for entity #{name}" do
169
+ clauses_for_fact_type.each do |clause|
170
+ type, qualifiers, phrases, context = *clause
171
+ debug :reading, "Clause: #{clause.inspect}" do
172
+ f, r = *bind_fact_reading(fact_type, qualifiers, phrases)
173
+ identifying_fact_types[f] = true
174
+ fact_type ||= f
175
+ end
176
+ end
177
+ end
178
+
179
+ # Find the role that this entity type plays in the fact type, if any:
180
+ debug :reading, "Roles are: #{fact_type.all_role.map{|role| (role.concept == entity_type ? "*" : "") + role.concept.name }*", "}"
181
+ player_roles = fact_type.all_role.select{|role| role.concept == entity_type }
182
+ raise "#{role.concept.name} may only play one role in each identifying fact type" if player_roles.size > 1
183
+ if player_role = player_roles[0]
184
+ non_player_roles = fact_type.all_role-[player_role]
185
+
186
+ raise "#{name} cannot be identified by a role in a non-binary fact type" if non_player_roles.size > 1
187
+ elsif identification
188
+ # This situation occurs when an objectified fact type has an entity identifier
189
+ #raise "Entity type #{name} cannot objectify fact type #{fact_type.describe}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
190
+ raise "Entity type #{name} cannot objectify fact type #{identification.inspect}, it already objectifies #{entity_type.fact_type.describe}" if entity_type.fact_type
191
+ debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe} with distinct identifier"
192
+
193
+ entity_type.fact_type = fact_type
194
+ fact_type_identification(fact_type, name, false)
195
+ else
196
+ debug :entity, "Entity type #{name} objectifies fact type #{fact_type.describe}"
197
+ # it's an objectified fact type, such as a subtype
198
+ entity_type.fact_type = fact_type
199
+ end
200
+ end
201
+
202
+ # Finally, create the identifying uniqueness constraint, or mark it as preferred
203
+ # if it's already been created. The identifying roles have been defined already.
204
+
205
+ if identification
206
+ debug :identification, "Handling identification" do
207
+ if id_role_names = identification[:roles] # A list of identifying roles
208
+ debug "Identifying roles: #{id_role_names.inspect}"
209
+
210
+ # Pick out the identifying_roles in the order they were declared,
211
+ # not the order the fact types were defined:
212
+ identifying_roles = id_role_names.map do |names|
213
+ unless (role = bind_unary_fact_type(entity_type, names))
214
+ player, binding = @symbols.bind(names)
215
+ role = @symbols.roles_by_binding[binding]
216
+ raise "identifying role #{names*"-"} not found in fact types for #{name}" unless role
217
+ end
218
+ role
219
+ end
220
+
221
+ # Find a uniqueness constraint as PI, or make one
222
+ pc = find_pc_over_roles(identifying_roles)
223
+ if (pc)
224
+ debug "Existing PC #{pc.verbalise} is now PK for #{name} #{pc.class.roles.keys.map{|k|"#{k} => "+pc.send(k).verbalise}*", "}"
225
+ pc.is_preferred_identifier = true
226
+ pc.name = "#{name}PK" unless pc.name
227
+ else
228
+ debug "Adding PK for #{name} using #{identifying_roles.map{|r| r.concept.name}.inspect}"
229
+
230
+ role_sequence = @constellation.RoleSequence(:new)
231
+ # REVISIT: Need to sort the identifying_roles to match the identification parameter array
232
+ identifying_roles.each_with_index do |identifying_role, index|
233
+ @constellation.RoleRef(role_sequence, index, :role => identifying_role)
234
+ end
235
+
236
+ # Add a unique constraint over all identifying roles
237
+ pc = @constellation.PresenceConstraint(
238
+ :new,
239
+ :vocabulary => @vocabulary,
240
+ :name => "#{name}PK", # Is this a useful name?
241
+ :role_sequence => role_sequence,
242
+ :is_preferred_identifier => true,
243
+ :max_frequency => 1 # Unique
244
+ #:is_mandatory => true,
245
+ #:min_frequency => 1,
246
+ )
247
+ end
248
+
249
+ elsif identification[:mode]
250
+ mode = identification[:mode] # An identification mode
251
+
252
+ raise "Entity definition using reference mode may only have one identifying fact type" if identifying_fact_types.size > 1
253
+ mode_fact_type = identifying_fact_types.keys[0]
254
+
255
+ # If the entity type is an objectified fact type, don't use the objectified fact type!
256
+ mode_fact_type = nil if mode_fact_type && mode_fact_type.entity_type == entity_type
257
+
258
+ debug :mode, "Processing Reference Mode for #{name}#{mode_fact_type ? " with existing '#{mode_fact_type.default_reading}'" : ""}"
259
+
260
+ # Fact Type:
261
+ if (ft = mode_fact_type)
262
+ entity_role, value_role = ft.all_role.partition{|role| role.concept == entity_type}.flatten
263
+ else
264
+ ft = @constellation.FactType(:new)
265
+ entity_role = @constellation.Role(ft, 0, :concept => entity_type)
266
+ value_role = @constellation.Role(ft, 1, :concept => vt)
267
+ debug :mode, "Creating new fact type to identify #{name}"
268
+ end
269
+
270
+ # REVISIT: The restriction applies only to the value role. There is good reason to apply it above to the value type as well.
271
+ if (ranges = identification[:restriction])
272
+ value_role.role_value_restriction = value_restriction(ranges)
273
+ end
274
+
275
+ # Forward reading, if it doesn't already exist:
276
+ rss = entity_role.all_role_ref.map{|rr| rr.role_sequence.all_role_ref.size == 2 ? rr.role_sequence : nil }.compact
277
+ # Find or create RoleSequences for the forward and reverse readings:
278
+ rs01 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [entity_role, value_role] }[0]
279
+ if !rs01
280
+ rs01 = @constellation.RoleSequence(:new)
281
+ @constellation.RoleRef(rs01, 0, :role => entity_role)
282
+ @constellation.RoleRef(rs01, 1, :role => value_role)
283
+ end
284
+ if rs01.all_reading.empty?
285
+ @constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs01, :text => "{0} has {1}")
286
+ debug :mode, "Creating new forward reading '#{name} has #{vt.name}'"
287
+ else
288
+ debug :mode, "Using existing forward reading"
289
+ end
290
+
291
+ # Reverse reading:
292
+ rs10 = rss.select{|rs| rs.all_role_ref.sort_by{|rr| rr.ordinal}.map(&:role) == [value_role, entity_role] }[0]
293
+ if !rs10
294
+ rs10 = @constellation.RoleSequence(:new)
295
+ @constellation.RoleRef(rs10, 0, :role => value_role)
296
+ @constellation.RoleRef(rs10, 1, :role => entity_role)
297
+ end
298
+ if rs10.all_reading.empty?
299
+ @constellation.Reading(ft, ft.all_reading.size, :role_sequence => rs10, :text => "{0} is of {1}")
300
+ debug :mode, "Creating new reverse reading '#{vt.name} is of #{name}'"
301
+ else
302
+ debug :mode, "Using existing reverse reading"
303
+ end
304
+
305
+ # Entity Type must have a value type. Find or create the role sequence, then create a PC if necessary
306
+ debug :mode, "entity_role has #{entity_role.all_role_ref.size} attached sequences"
307
+ debug :mode, "entity_role has #{entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
308
+ rs0 = entity_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
309
+ if !rs0
310
+ rs0 = @constellation.RoleSequence(:new)
311
+ @constellation.RoleRef(rs0, 0, :role => entity_role)
312
+ debug :mode, "Creating new EntityType role sequence"
313
+ else
314
+ rs0 = rs0.role_sequence
315
+ debug :mode, "Using existing EntityType role sequence"
316
+ end
317
+ if (rs0.all_presence_constraint.size == 0)
318
+ @constellation.PresenceConstraint(
319
+ :new,
320
+ :name => '',
321
+ :enforcement => '',
322
+ :vocabulary => @vocabulary,
323
+ :role_sequence => rs0,
324
+ :min_frequency => 1,
325
+ :max_frequency => 1,
326
+ :is_preferred_identifier => false,
327
+ :is_mandatory => true
328
+ )
329
+ debug :mode, "Creating new EntityType PresenceConstraint"
330
+ else
331
+ debug :mode, "Using existing EntityType PresenceConstraint"
332
+ end
333
+
334
+ # Value Type must have a value type. Find or create the role sequence, then create a PC if necessary
335
+ debug :mode, "value_role has #{value_role.all_role_ref.size} attached sequences"
336
+ debug :mode, "value_role has #{value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1}.size} unary sequences"
337
+ rs1 = value_role.all_role_ref.select{|rr| rr.role_sequence.all_role_ref.size == 1 ? rr.role_sequence : nil }.compact[0]
338
+ if (!rs1)
339
+ rs1 = @constellation.RoleSequence(:new)
340
+ @constellation.RoleRef(rs1, 0, :role => value_role)
341
+ debug :mode, "Creating new ValueType role sequence"
342
+ else
343
+ rs1 = rs1.role_sequence
344
+ debug :mode, "Using existing ValueType role sequence"
345
+ end
346
+ if (rs1.all_presence_constraint.size == 0)
347
+ @constellation.PresenceConstraint(
348
+ :new,
349
+ :name => '',
350
+ :enforcement => '',
351
+ :vocabulary => @vocabulary,
352
+ :role_sequence => rs1,
353
+ :min_frequency => 0,
354
+ :max_frequency => 1,
355
+ :is_preferred_identifier => true,
356
+ :is_mandatory => false
357
+ )
358
+ debug :mode, "Creating new ValueType PresenceConstraint"
359
+ else
360
+ debug :mode, "Marking existing ValueType PresenceConstraint as preferred"
361
+ rs1.all_presence_constraint[0].is_preferred_identifier = true
362
+ end
363
+ end
364
+ end
365
+ else
366
+ # identification must be inherited.
367
+ debug "Identification is inherited"
368
+ end
369
+ end
370
+ end
371
+
372
+ def add_supertype(entity_type, supertype_name, identifying_supertype, mapping_pragmas)
373
+ debug :supertype, "Supertype #{supertype_name}"
374
+ supertype = @constellation.EntityType(@vocabulary, supertype_name)
375
+ inheritance_fact = @constellation.TypeInheritance(entity_type, supertype, :fact_type_id => :new)
376
+
377
+ assimilations = mapping_pragmas.select { |p| ['absorbed', 'separate', 'partitioned'].include? p}
378
+ raise "Conflicting assimilation pragmas #{assimilations*", "}" if assimilations.size > 1
379
+ inheritance_fact.assimilation = assimilations[0]
380
+
381
+ # Create a reading:
382
+ sub_role = @constellation.Role(inheritance_fact, 0, :concept => entity_type)
383
+ super_role = @constellation.Role(inheritance_fact, 1, :concept => supertype)
384
+
385
+ rs = @constellation.RoleSequence(:new)
386
+ @constellation.RoleRef(rs, 0, :role => sub_role)
387
+ @constellation.RoleRef(rs, 1, :role => super_role)
388
+ @constellation.Reading(inheritance_fact, 0, :role_sequence => rs, :text => "{0} is a kind of {1}")
389
+ @constellation.Reading(inheritance_fact, 1, :role_sequence => rs, :text => "{0} is a subtype of {1}")
390
+
391
+ rs2 = @constellation.RoleSequence(:new)
392
+ @constellation.RoleRef(rs2, 0, :role => super_role)
393
+ @constellation.RoleRef(rs2, 1, :role => sub_role)
394
+ n = 'aeiouh'.include?(sub_role.concept.name.downcase[0]) ? 1 : 0
395
+ @constellation.Reading(inheritance_fact, 2+n, :role_sequence => rs2, :text => "{0} is a {1}")
396
+ @constellation.Reading(inheritance_fact, 3-n, :role_sequence => rs2, :text => "{0} is an {1}")
397
+
398
+ if identifying_supertype
399
+ inheritance_fact.provides_identification = true
400
+ end
401
+
402
+ # Create uniqueness constraints over the subtyping fact type
403
+ p1rs = @constellation.RoleSequence(:new)
404
+ @constellation.RoleRef(p1rs, 0).role = sub_role
405
+ pc1 = @constellation.PresenceConstraint(:new)
406
+ pc1.name = "#{entity_type.name}MustHaveSupertype#{supertype.name}"
407
+ pc1.vocabulary = @vocabulary
408
+ pc1.role_sequence = p1rs
409
+ pc1.is_mandatory = true # A subtype instance must have a supertype instance
410
+ pc1.min_frequency = 1
411
+ pc1.max_frequency = 1
412
+ pc1.is_preferred_identifier = false
413
+
414
+ # The supertype role often identifies the subtype:
415
+ p2rs = @constellation.RoleSequence(:new)
416
+ @constellation.RoleRef(p2rs, 0).role = super_role
417
+ pc2 = @constellation.PresenceConstraint(:new)
418
+ pc2.name = "#{supertype.name}MayBeA#{entity_type.name}"
419
+ pc2.vocabulary = @vocabulary
420
+ pc2.role_sequence = p2rs
421
+ pc2.is_mandatory = false
422
+ pc2.min_frequency = 0
423
+ pc2.max_frequency = 1
424
+ pc2.is_preferred_identifier = inheritance_fact.provides_identification
425
+ end
426
+
427
+ def unit params
428
+ singular = params[:singular]
429
+ plural = params[:plural]
430
+ base_units = params[:base]
431
+ denominator = params[:coefficient][:denominator]
432
+ numerator = params[:coefficient][:numerator]
433
+ offset = params[:offset]
434
+ approximately = params[:approximately]
435
+ ephemeral = params[:ephemeral]
436
+
437
+ if (numerator.to_f / denominator.to_i != 1.0)
438
+ coefficient = @constellation.Coefficient(
439
+ :numerator => numerator.to_f,
440
+ :denominator => denominator.to_i,
441
+ :is_precise => !approximately
442
+ )
443
+ else
444
+ coefficient = nil
445
+ end
446
+ offset = offset.to_f
447
+ offset = nil if offset == 0
448
+ debug :units, "Defining new unit #{singular}#{plural ? "/"+plural : ""}" do
449
+ debug :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
450
+ debug :units, "Offset is #{offset}" if offset
451
+ raise "Redefinition of unit #{singular}" if @constellation.Unit.values.detect{|u| u.name == singular}
452
+ raise "Redefinition of unit #{plural}" if @constellation.Unit.values.detect{|u| u.name == plural}
453
+ unit = @constellation.Unit(:new,
454
+ :name => singular,
455
+ # :plural => plural,
456
+ :coefficient => coefficient,
457
+ :offset => offset,
458
+ :is_fundamental => base_units.empty?,
459
+ :is_ephemeral => ephemeral,
460
+ :vocabulary => @vocabulary
461
+ )
462
+ base_units.each do |base_unit, exponent|
463
+ base = @constellation.Unit.values.detect{|u| u.name == base_unit}
464
+ debug :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
465
+ base ||= @constellation.Unit(:new, :name => base_unit, :is_fundamental => true, :vocabulary => @vocabulary)
466
+ @constellation.Derivation(:derived_unit => unit, :base_unit => base, :exponent => exponent)
467
+ end
468
+ if plural
469
+ plural_unit = @constellation.Unit(:new,
470
+ :name => plural,
471
+ :is_fundamental => false,
472
+ :vocabulary => @vocabulary
473
+ )
474
+ @constellation.Derivation(:derived_unit => plural_unit, :base_unit => unit, :exponent => 1)
475
+ end
476
+ end
477
+ end
478
+
479
+ # If one of the words is the name of the entity type, and the other
480
+ # words consist of a unary fact type reading, return the role it plays.
481
+ def bind_unary_fact_type(entity_type, words)
482
+ return nil unless i = words.index(entity_type.name)
483
+
484
+ to_match = words.clone
485
+ to_match[i] = '{0}'
486
+ to_match = to_match*' '
487
+
488
+ # See if any unary fact type of this or any supertype matches these words:
489
+ entity_type.supertypes_transitive.each do |supertype|
490
+ supertype.all_role.each do |role|
491
+ role.fact_type.all_role.size == 1 &&
492
+ role.fact_type.all_reading.each do |reading|
493
+ if reading.text == to_match
494
+ debug :identification, "Bound identification to unary role '#{to_match.sub(/\{0\}/, entity_type.name)}'"
495
+ return role
496
+ end
497
+ end
498
+ end
499
+ end
500
+ nil
501
+ end
502
+
503
+ def fact_type(name, clauses, conditions)
504
+ debug "Processing clauses for fact type" do
505
+ fact_type = nil
506
+
507
+ #
508
+ # The first step is to find all role references and definitions in the phrases
509
+ # This also:
510
+ # * deletes any adjectives that were used but not hyphenated
511
+ # * changes each linking word phrase into a simple String
512
+ # * adds a :binding key to each bound role
513
+ #
514
+ @symbols = SymbolTable.new(@constellation, @vocabulary)
515
+ @symbols.bind_roles_in_clauses(clauses)
516
+
517
+ clauses.each do |clause|
518
+ kind, qualifiers, phrases, context = *clause
519
+
520
+ fact_type, r = *bind_fact_reading(fact_type, qualifiers, phrases)
521
+ end
522
+
523
+ # The fact type has a name iff it's objectified as an entity type
524
+ #puts "============= Creating entity type #{name} to nominalize fact type #{fact_type.default_reading} ======================" if name
525
+ fact_type.entity_type = @constellation.EntityType(@vocabulary, name) if name
526
+
527
+ # Add the identifying PresenceConstraint for this fact type:
528
+ if fact_type.all_role.size == 1 && !fact_type.entity_type
529
+ # All is well, unaries don't need an identifying PC unless objectified
530
+ else
531
+ fact_type_identification(fact_type, name, true)
532
+ end
533
+
534
+ # REVISIT: Process the fact derivation conditions, if any
535
+ end
536
+ end
537
+
538
+ def fact(population_name, clauses)
539
+ debug "Processing clauses for fact" do
540
+ population_name ||= ''
541
+ population = @constellation.Population(@vocabulary, population_name)
542
+ @symbols = SymbolTable.new(@constellation, @vocabulary)
543
+ @symbols.bind_roles_in_clauses(clauses)
544
+
545
+ bound_instances = {} # Instances indexed by binding
546
+ facts =
547
+ clauses.map do |clause|
548
+ kind, qualifiers, phrases, context = *clause
549
+ # Every bound word (term) in the phrases must have a literal
550
+ # OR be bound to an entity type identified by the phrases
551
+ # Any clause that has one binding and no other word is a value instance or simply-identified entity type
552
+ phrases.map! do |phrase|
553
+ next phrase unless l = phrase[:literal]
554
+ binding = phrase[:binding]
555
+ debug :instance, "Making #{binding.concept.class.basename} #{binding.concept.name} using #{l.inspect}" do
556
+ bound_instances[binding] =
557
+ instance_identified_by_literal(population, binding.concept, l)
558
+ end
559
+ phrase
560
+ end
561
+
562
+ if phrases.size == 1 && Hash === (phrase = phrases[0])
563
+ binding = phrase[:binding]
564
+ l = phrase[:literal]
565
+ debug :instance, "Making(2) #{binding.concept.class.basename} #{binding.concept.name} using #{l.inspect}" do
566
+ bound_instances[binding] =
567
+ instance_identified_by_literal(population, binding.concept, l)
568
+ end
569
+ else
570
+ [phrases, *bind_fact_reading(nil, qualifiers, phrases)]
571
+ end
572
+ end
573
+
574
+ # Because the fact types may include forward references, we must process the list repeatedly
575
+ # until we make no further progress. Any remaining
576
+ progress = true
577
+ pass = 0
578
+ while progress
579
+ progress = false
580
+ pass += 1
581
+ debug :instance, "Pass #{pass}" do
582
+ facts.map! do |fact|
583
+ next fact unless fact.is_a?(Array)
584
+ phrases, fact_type, reading = *fact
585
+
586
+ # This is a fact type we bound; see if we can create the fact instance yet
587
+
588
+ bare_roles = phrases.select{|w| w.is_a?(Hash) && !w[:literal] && !bound_instances[w[:binding]]}
589
+ # REVISIT: Bare bindings might be bound to instances we created
590
+
591
+ debug :instance, "Considering '#{fact_type.preferred_reading.expand}' with bare roles: #{bare_roles.map{|role| role[:binding].concept.name}*", "} "
592
+
593
+ case
594
+ when bare_roles.size == 0
595
+ debug :instance, "All bindings in '#{fact_type.preferred_reading.expand}' contain instances; create the fact type"
596
+ instances = phrases.select{|p| p.is_a?(Hash)}.map{|p| bound_instances[p[:binding]]}
597
+ debug :instance, "Instances are #{instances.map{|i| "#{i.concept.name} #{i.value.inspect}"}*", "}"
598
+
599
+ # Check that this fact doesn't already exist
600
+ fact = fact_type.all_fact.detect{|f|
601
+ # Get the role values of this fact in the order of the reading we just bound
602
+ role_values_in_reading_order = f.all_role_value.sort_by do |rv|
603
+ reading.role_sequence.all_role_ref.detect{|rr| rr.role == rv.role}.ordinal
604
+ end
605
+ # If all this fact's role values are played by the bound instances, it's the same fact
606
+ !role_values_in_reading_order.zip(instances).detect{|rv, i| rv.instance != i }
607
+ }
608
+ unless fact
609
+ fact = @constellation.Fact(:new, :fact_type => fact_type, :population => population)
610
+ @constellation.Instance(:new, :concept => fact_type.entity_type, :fact => fact, :population => population)
611
+ reading.role_sequence.all_role_ref.zip(instances).each do |rr, instance|
612
+ debug :instance, "New fact has #{instance.concept.name} role #{instance.value.inspect}"
613
+ @constellation.RoleValue(:fact => fact, :instance => instance, :role => rr.role, :population => population)
614
+ end
615
+ else
616
+ debug :instance, "Found existing fact type instance"
617
+ end
618
+ progress = true
619
+ next fact
620
+
621
+ # If we have one bare role (no literal or instance) played by an entity type,
622
+ # and the bound fact type participates in the identifier, we might now be able
623
+ # to create the entity instance.
624
+ when bare_roles.size == 1 &&
625
+ (binding = bare_roles[0][:binding]) &&
626
+ (e = binding.concept).is_a?(ActiveFacts::Metamodel::EntityType) &&
627
+ e.preferred_identifier.role_sequence.all_role_ref.detect{|rr| rr.role.fact_type == fact_type}
628
+
629
+ # Check this instance doesn't already exist already:
630
+ identifying_binding = (phrases.select{|p| Hash === p}.map{|p|p[:binding]}-[binding])[0]
631
+ identifying_instance = bound_instances[identifying_binding]
632
+
633
+ debug :instance, "This clause associates a new #{binding.concept.name} with a #{identifying_binding.concept.name}#{identifying_instance ? " which exists" : ""}"
634
+
635
+ identifying_role_ref = e.preferred_identifier.role_sequence.all_role_ref.detect { |rr|
636
+ rr.role.fact_type == fact_type && rr.role.concept == identifying_binding.concept
637
+ }
638
+ unless identifying_role_ref
639
+ debug :instance, "Failed to find a #{identifying_instance.concept.name}"
640
+ next fact # We can't do this yet
641
+ end
642
+ role_value = identifying_instance.all_role_value.detect do |rv|
643
+ rv.fact.fact_type == identifying_role_ref.role.fact_type
644
+ end
645
+ if role_value
646
+ instance = (role_value.fact.all_role_value.to_a-[role_value])[0].instance
647
+ debug :instance, "Found existing instance (of #{instance.concept.name}) from a previous definition"
648
+ bound_instances[binding] = instance
649
+ progress = true
650
+ next role_value.instance
651
+ end
652
+
653
+ pi_role_refs = e.preferred_identifier.role_sequence.all_role_ref
654
+ # For each pi role, we have to find the fact clause, which contains the binding we need.
655
+ # Then we have to create an instance of each fact
656
+ identifiers =
657
+ pi_role_refs.map do |rr|
658
+ fact_a = facts.detect{|f| f.is_a?(Array) && f[1] == rr.role.fact_type}
659
+ identifying_binding = fact_a[0].detect{|phrase| phrase.is_a?(Hash) && phrase[:binding] != binding}[:binding]
660
+ identifying_instance = bound_instances[identifying_binding]
661
+
662
+ [rr, fact_a, identifying_binding, identifying_instance]
663
+ end
664
+ if identifiers.detect{ |i| !i[3] } # Not all required facts are bound yet
665
+ debug :instance, "Can't go through with creating #{binding.concept.name}; not all the facts are in"
666
+ next fact
667
+ end
668
+
669
+ debug :instance, "Going ahead with creating #{binding.concept.name} using #{identifiers.size} roles"
670
+ instance = @constellation.Instance(:new, :concept => e, :population => population)
671
+ bound_instances[binding] = instance
672
+ identifiers.each do |rr, fact_a, identifying_binding, identifying_instance|
673
+ # This reading provides the identifying literal for the EntityType e
674
+ id_fact = @constellation.Fact(:new, :fact_type => rr.role.fact_type, :population => population)
675
+ role = (rr.role.fact_type.all_role.to_a-[rr.role])[0]
676
+ @constellation.RoleValue(:instance => instance, :fact => id_fact, :population => population, :role => role)
677
+ @constellation.RoleValue(:instance => identifying_instance, :fact => id_fact, :role => rr.role, :population => population)
678
+ true
679
+ end
680
+
681
+ progress = true
682
+ end
683
+ fact
684
+ end
685
+ end
686
+ end
687
+ incomplete = facts.select{|ft| !ft.is_a?(ActiveFacts::Metamodel::Instance) && !ft.is_a?(ActiveFacts::Metamodel::Fact)}
688
+ if incomplete.size > 0
689
+ # Provide a readable description of the problem here, by showing each binding with no instance
690
+ missing_bindings = incomplete.map do |f|
691
+ phrases = f[0]
692
+ phrases.select{|p|
693
+ p.is_a?(Hash) and binding = p[:binding] and !bound_instances[binding]
694
+ }.map{|phrase| phrase[:binding]}
695
+ end.flatten.uniq
696
+ raise "Not enough facts are given to identify #{
697
+ missing_bindings.map do |b|
698
+ [ b.leading_adjective, b.concept.name, b.trailing_adjective ].compact*" " +
699
+ " (need #{b.concept.preferred_identifier.role_sequence.all_role_ref.map do |rr|
700
+ [ rr.leading_adjective, rr.role.role_name || rr.role.concept.name, rr.trailing_adjective ].compact*" "
701
+ end*", "
702
+ })"
703
+ end*", "
704
+ }"
705
+ end
706
+ end
707
+ end
708
+
709
+ def entity_identified_by_literal(population, concept, literal)
710
+ # A literal that identifies an entity type means the entity type has only one identifying role
711
+ # That role is played either by a value type, or by another similarly single-identified entity type
712
+ debug "Making EntityType #{concept.name} identified by '#{literal}' #{population.name.size>0 ? " in "+population.name.inspect : ''}" do
713
+ identifying_role_refs = concept.preferred_identifier.role_sequence.all_role_ref
714
+ raise "Single literal cannot satisfy multiple identifying roles for #{concept.name}" if identifying_role_refs.size > 1
715
+ role = identifying_role_refs.single.role
716
+ identifying_instance = instance_identified_by_literal(population, role.concept, literal)
717
+ existing_instance = nil
718
+ instance_rv = identifying_instance.all_role_value.detect { |rv|
719
+ next false unless rv.population == population # Not this population
720
+ next false unless rv.fact.fact_type == role.fact_type # Not this fact type
721
+ other_role_value = (rv.fact.all_role_value-[rv])[0]
722
+ existing_instance = other_role_value.instance
723
+ other_role_value.instance.concept == concept # Is it this concept?
724
+ }
725
+ if instance_rv
726
+ instance = existing_instance
727
+ debug :instance, "This #{concept.name} entity already exists"
728
+ else
729
+ fact = @constellation.Fact(:new, :fact_type => role.fact_type, :population => population)
730
+ instance = @constellation.Instance(:new, :concept => concept, :population => population)
731
+ # The identifying fact type has two roles; create both role instances:
732
+ @constellation.RoleValue(:instance => identifying_instance, :fact => fact, :population => population, :role => role)
733
+ @constellation.RoleValue(:instance => instance, :fact => fact, :population => population, :role => (role.fact_type.all_role-[role])[0])
734
+ end
735
+ instance
736
+ end
737
+ end
738
+
739
+ def instance_identified_by_literal(population, concept, literal)
740
+ if concept.is_a?(ActiveFacts::Metamodel::EntityType)
741
+ entity_identified_by_literal(population, concept, literal)
742
+ else
743
+ debug :instance, "Making ValueType #{concept.name} #{literal.inspect} #{population.name.size>0 ? " in "+population.name.inspect : ''}" do
744
+
745
+ is_a_string = String === literal
746
+ instance = @constellation.Instance.detect do |key, i|
747
+ # REVISIT: And same unit
748
+ i.population == population &&
749
+ i.value &&
750
+ i.value.literal == literal &&
751
+ i.value.is_a_string == is_a_string
752
+ end
753
+ #instance = concept.all_instance.detect { |instance|
754
+ # instance.population == population && instance.value == literal
755
+ #}
756
+ debug :instance, "This #{concept.name} value already exists" if instance
757
+ unless instance
758
+ instance = @constellation.Instance(
759
+ :new,
760
+ :concept => concept,
761
+ :population => population,
762
+ :value => [literal.to_s, is_a_string, nil]
763
+ )
764
+ end
765
+ instance
766
+ end
767
+ end
768
+ end
769
+
770
+ def constraint *value
771
+ case type = value.shift
772
+ when :presence
773
+ presence_constraint *value
774
+ when :set
775
+ set_constraint *value
776
+ when :subset
777
+ subset_constraint *value
778
+ when :equality
779
+ equality_constraint *value
780
+ else
781
+ $stderr.puts "REVISIT: external #{type} constraints aren't yet handled:\n\t"+value.map{|a| a.inspect }*"\n\t"
782
+ end
783
+ end
784
+
785
+ # The joins list is an array of an array of fact types.
786
+ # The fact types contain roles played by concepts, where each
787
+ # concept plays more than one role. In fact, a concept may
788
+ # occur in more than one binding, and each binding plays more
789
+ # than one role. The bindings that are common to all fact types
790
+ # in each array in the joins list form the constrained role
791
+ # sequences. Each binding that isn't common at this top level
792
+ # must occur more than once in each group of fact types where
793
+ # it appears, and it forms a join between those fact types.
794
+ def bind_joins_as_role_sequences(joins_list)
795
+ @symbols = SymbolTable.new(@constellation, @vocabulary)
796
+ fact_roles_list = []
797
+ bindings_list = []
798
+ joins_list.each_with_index do |joins, index|
799
+ # joins is an array of phrase arrays, each for one reading
800
+ @symbols.bind_roles_in_phrases_list(joins)
801
+
802
+ fact_roles_list << joins.map do |phrases|
803
+ ifr = invoked_fact_roles(phrases)
804
+ raise "Fact type reading not found for #{phrases.inspect}" unless ifr
805
+ ifr
806
+ end
807
+ bindings_list << joins.map do |phrases|
808
+ phrases.map{ |phrase| Hash === phrase ? phrase[:binding] : nil}.compact
809
+ end
810
+ end
811
+
812
+ # Each set of binding arrays in the list must share at least one common binding
813
+ bindings_by_join = bindings_list.map{|join| join.flatten}
814
+ common_bindings = bindings_by_join[1..-1].inject(bindings_by_join[0]) { |c, b| c & b }
815
+ # Was:
816
+ # common_bindings = bindings_list.inject(bindings_list[0]) { |common, bindings| common & bindings }
817
+ raise "Set constraints must have at least one common role between the sets" unless common_bindings.size > 0
818
+
819
+ # REVISIT: Do we need to constrain things such that each join path only includes *one* instance of each common binding?
820
+
821
+ # For each set of binding arrays, if there's more than one binding array in the set,
822
+ # it represents a join path. Here we check that each join path is complete, i.e. linked up.
823
+ # Each element of a join path is the array of bindings for a fact type invocation.
824
+ # Each invocation must share a binding (not one of the globally common ones) with
825
+ # another invocation in that join path.
826
+ bindings_list.each_with_index do |join, jpnum|
827
+ # Check that this bindings array creates a complete join path:
828
+ join.each_with_index do |bindings, i|
829
+ fact_type_roles = fact_roles_list[jpnum][i]
830
+ fact_type = fact_type_roles[0].fact_type
831
+
832
+ # The bindings are for one fact type invocation.
833
+ # These bindings must be joined to some later fact type by a common binding that isn't a globally-common one:
834
+ local_bindings = bindings-common_bindings
835
+ next if local_bindings.size == 0 # No join path is required, as only one fact type is invoked.
836
+ next if i == join.size-1 # We already checked that the last fact type invocation is joined
837
+ ok = local_bindings.detect do |local_binding|
838
+ j = i+1
839
+ join[j..-1].detect do |other_bindings|
840
+ other_fact_type_roles = fact_roles_list[jpnum][j]
841
+ other_fact_type = other_fact_type_roles[0].fact_type
842
+ j += 1
843
+ # These next two lines allow joining from/to an objectified fact type:
844
+ fact_type_roles.detect{|r| r.concept == other_fact_type.entity_type } ||
845
+ other_fact_type_roles.detect{|r| r.concept == fact_type.entity_type } ||
846
+ other_bindings.include?(local_binding)
847
+ end
848
+ end
849
+ raise "Incomplete join path; one of the bindings #{local_bindings.inspect} must re-occur to establish a join" unless ok
850
+ end
851
+ end
852
+
853
+ # Create the role sequences and their role references.
854
+ # Each role sequence contain one RoleRef for each common binding
855
+ # REVISIT: This results in ordering all RoleRefs according to the order of the common_bindings.
856
+ # This for example means that a set constraint having joins might have the join order changed so they all match.
857
+ # 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.
858
+ role_sequences = joins_list.map{|r| @constellation.RoleSequence(:new) }
859
+ common_bindings.each_with_index do |binding, index|
860
+ role_sequences.each_with_index do |rs, rsi|
861
+ join = bindings_list[rsi]
862
+ fact_pos = nil
863
+ join_pos = (0...join.size).detect do |i|
864
+ fact_pos = join[i].index(binding)
865
+ end
866
+ @constellation.RoleRef(rs, index).role = fact_roles_list[rsi][join_pos][fact_pos]
867
+ end
868
+ end
869
+
870
+ role_sequences
871
+ end
872
+
873
+ def presence_constraint(constrained_role_names, quantifier, phrases_list, context)
874
+ raise "REVISIT: Join presence constraints not supported yet" if phrases_list[0].size > 1
875
+ phrases_list = phrases_list.map{|r| r[0] }
876
+ #p phrases_list
877
+
878
+ @symbols = SymbolTable.new(@constellation, @vocabulary)
879
+
880
+ # Find players for all constrained_role_names. These may use leading or trailing adjective forms...
881
+ constrained_players = []
882
+ constrained_bindings = []
883
+ constrained_role_names.each do |role_name|
884
+ player, binding = @symbols.bind(role_name)
885
+ constrained_players << player
886
+ constrained_bindings << binding
887
+ end
888
+ #puts "Constrained bindings are #{constrained_bindings.inspect}"
889
+ #puts "Constrained bindings object_id's are #{constrained_bindings.map{|b|b.object_id.to_s}*","}"
890
+
891
+ # Find players for all the concepts in all phrases_list:
892
+ @symbols.bind_roles_in_phrases_list(phrases_list)
893
+
894
+ constrained_roles = []
895
+ unmatched_roles = constrained_role_names.clone
896
+ phrases_list.each do |phrases|
897
+ # puts phrases.inspect
898
+
899
+ # If this succeeds, the phrases found matches the roles in our phrases
900
+ fact_roles = invoked_fact_roles(phrases)
901
+ raise "Fact type reading not found for #{phrases.inspect}" unless fact_roles
902
+
903
+ # Look for the constrained role(s); the bindings will be the same
904
+ matched_bindings = phrases.select{|p| Hash === p}.map{|p| p[:binding]}
905
+ #puts "matched_bindings = #{matched_bindings.inspect}"
906
+ #puts "matched_bindings object_id's are #{matched_bindings.map{|b|b.object_id.to_s}*","}}"
907
+ matched_bindings.each_with_index{|b, pos|
908
+ i = constrained_bindings.index(b)
909
+ next unless i
910
+ unmatched_roles[i] = nil
911
+ #puts "found #{constrained_bindings[i].inspect} found as #{b.inspect} in position #{i.inspect}"
912
+ role = fact_roles[pos]
913
+ constrained_roles << role unless constrained_roles.include?(role)
914
+ }
915
+ end
916
+
917
+ # Check that all constrained roles were matched at least once:
918
+ unmatched_roles.compact!
919
+ raise "Constrained roles #{unmatched_roles.map{|ur| ur*"-"}*", "} not found in fact types" if unmatched_roles.size != 0
920
+
921
+ rs = @constellation.RoleSequence(:new)
922
+ #puts "constrained_roles: #{constrained_roles.map{|r| r.concept.name}.inspect}"
923
+ constrained_roles.each_with_index do |role, index|
924
+ raise "Constrained role #{constrained_role_names[index]} not found" unless role
925
+ rr = @constellation.RoleRef(rs, index)
926
+ rr.role = role
927
+ end
928
+ #puts "New external PresenceConstraint with quantifier = #{quantifier.inspect} over #{rs.describe}"
929
+
930
+ # REVISIT: Check that no existing PC spans the same roles (nor a superset nor subset?)
931
+
932
+ @constellation.PresenceConstraint(
933
+ :new,
934
+ :name => '',
935
+ :enforcement => '',
936
+ :vocabulary => @vocabulary,
937
+ :role_sequence => rs,
938
+ :min_frequency => quantifier[0],
939
+ :max_frequency => quantifier[1],
940
+ :is_preferred_identifier => false,
941
+ :is_mandatory => quantifier[0] && quantifier[0] > 0
942
+ )
943
+ end
944
+
945
+ def set_constraint(constrained_roles, quantifier, joins_list, context)
946
+ role_sequences = bind_joins_as_role_sequences(joins_list)
947
+
948
+ if quantifier[1] == nil
949
+ # create a presence constraint instead if we get quantifier = [N,nil] (at least N)
950
+ # We massage the bound role sequences to make this work.
951
+ raise "either/or constraint must have one common role" if role_sequences.size != 2 || role_sequences[0].all_role_ref.size != 1
952
+ second_role = role_sequences[1].all_role_ref.single.role
953
+ second_role_ref = @constellation.RoleRef(:role_sequence => role_sequences[0], :ordinal => 1, :role => second_role)
954
+ @constellation.deny(role_sequences[1].all_role_ref.single)
955
+ @constellation.deny(role_sequences[1])
956
+ @constellation.PresenceConstraint(
957
+ :new,
958
+ :name => '',
959
+ :enforcement => '',
960
+ :vocabulary => @vocabulary,
961
+ :role_sequence => role_sequences[0],
962
+ :min_frequency => quantifier[0],
963
+ :max_frequency => nil,
964
+ :is_preferred_identifier => false,
965
+ :is_mandatory => true
966
+ )
967
+ else
968
+ # Create a normal (mandatory) exclusion constraint:
969
+ constraint = @constellation.SetExclusionConstraint(:new)
970
+ constraint.vocabulary = @vocabulary
971
+ role_sequences.each_with_index do |rs, i|
972
+ @constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
973
+ end
974
+ constraint.is_mandatory = quantifier[0] == 1
975
+ end
976
+ end
977
+
978
+ def subset_constraint(joins_list, context)
979
+ role_sequences = bind_joins_as_role_sequences(joins_list)
980
+
981
+ #puts "subset_constraint:\n\t#{subset_readings.inspect}\n\t#{superset_readings.inspect}"
982
+ #puts "\t#{role_sequences.map{|rs| rs.describe}.inspect}"
983
+ #puts "subset_role_sequence = #{role_sequences[0].describe}"
984
+ #puts "superset_role_sequence = #{role_sequences[1].describe}"
985
+
986
+ # create the constraint:
987
+ constraint = @constellation.SubsetConstraint(:new)
988
+ constraint.vocabulary = @vocabulary
989
+ #constraint.name = nil
990
+ #constraint.enforcement =
991
+ constraint.subset_role_sequence = role_sequences[0]
992
+ constraint.superset_role_sequence = role_sequences[1]
993
+ end
994
+
995
+ def equality_constraint(joins_list, context)
996
+ #puts "REVISIT: equality\n\t#{joins_list.map{|rl| rl.inspect}*"\n\tif and only if\n\t"}"
997
+
998
+ role_sequences = bind_joins_as_role_sequences(joins_list)
999
+
1000
+ # Create the constraint:
1001
+ constraint = @constellation.SetEqualityConstraint(:new)
1002
+ constraint.vocabulary = @vocabulary
1003
+ role_sequences.each_with_index do |rs, i|
1004
+ @constellation.SetComparisonRoles(constraint, i, :role_sequence => rs)
1005
+ end
1006
+ end
1007
+
1008
+ # Search the supertypes of 'subtype' looking for an inheritance path to 'supertype',
1009
+ # and returning the array of TypeInheritance fact types from supertype to subtype.
1010
+ def inheritance_path(subtype, supertype)
1011
+ direct_inheritance = subtype.all_supertype_inheritance.select{|ti| ti.supertype == supertype}
1012
+ return direct_inheritance if (direct_inheritance[0])
1013
+ subtype.all_supertype_inheritance.each{|ti|
1014
+ ip = inheritance_path(ti.supertype, supertype)
1015
+ return ip+[ti] if (ip)
1016
+ }
1017
+ return nil
1018
+ end
1019
+
1020
+ # For a given phrase array from the parser, find the matching declared reading, and return
1021
+ # the array of Role object in the same order as they occur in the reading.
1022
+ def invoked_fact_roles(phrases)
1023
+ # REVISIT: Possibly this special reading from the parser can be removed now?
1024
+ if (phrases[0] == "!SUBTYPE!")
1025
+ subtype = phrases[1][:binding].concept
1026
+ supertype = phrases[2][:binding].concept
1027
+ raise "#{subtype.name} is not a subtype of #{supertype.name}" unless subtype.supertypes_transitive.include?(supertype)
1028
+ ip = inheritance_path(subtype, supertype)
1029
+ return [
1030
+ ip[-1].all_role.detect{|r| r.concept == subtype},
1031
+ ip[0].all_role.detect{|r| r.concept == supertype}
1032
+ ]
1033
+ end
1034
+
1035
+ bindings = phrases.select{|p| Hash === p}
1036
+ players = bindings.map{|p| p[:binding].concept }
1037
+ invoked_fact_roles_by_players(phrases, players)
1038
+ end
1039
+
1040
+ def invoked_fact_roles_by_players(phrases, players)
1041
+ players[0].all_role.each do |role|
1042
+ # Does this fact type have the right number of roles?
1043
+ next if role.fact_type.all_role.size != players.size
1044
+
1045
+ # Does this fact type include the correct other players?
1046
+ # REVISIT: Might need subtype/supertype matching here, with an implied subtyping join invocation
1047
+ next if role.fact_type.all_role.detect{|r| !players.include?(r.concept)}
1048
+
1049
+ # Oooh, a real candidate. Check the reading words.
1050
+ debug "Considering "+role.fact_type.describe do
1051
+ next unless role.fact_type.all_reading.detect do |candidate_reading|
1052
+ debug "Considering reading"+candidate_reading.text do
1053
+ to_match = phrases.clone
1054
+ players_to_match = players.clone
1055
+ candidate_reading.words_and_role_refs.each do |wrr|
1056
+ if (wrr.is_a?(ActiveFacts::Metamodel::RoleRef))
1057
+ break unless Hash === to_match.first
1058
+ break unless binding = to_match[0][:binding]
1059
+ # REVISIT: May need to match super- or sub-types here too!
1060
+ break unless players_to_match[0] == wrr.role.concept
1061
+ break if wrr.leading_adjective && binding.leading_adjective != wrr.leading_adjective
1062
+ break if wrr.trailing_adjective && binding.trailing_adjective != wrr.trailing_adjective
1063
+
1064
+ # All matched.
1065
+ to_match.shift
1066
+ players_to_match.shift
1067
+ # elsif # REVISIT: Match "not" and "none" here as negating the fact type invocation
1068
+ else
1069
+ break unless String === to_match[0]
1070
+ break unless to_match[0] == wrr
1071
+ to_match.shift
1072
+ end
1073
+ end
1074
+
1075
+ # This is the first matching candidate.
1076
+ # REVISIT: Since we do sub/supertype matching (and will do more!),
1077
+ # we need to accumulate all possible matches to be sure
1078
+ # there's only one, or the match is exact, or risk ambiguity.
1079
+ debug "Reading match was #{to_match.size == 0 ? "ok" : "bad"}"
1080
+ return candidate_reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}.map{|rr| rr.role} if to_match.size == 0
1081
+ end
1082
+ end
1083
+ end
1084
+ end
1085
+
1086
+ # Hmm, that didn't work, try the subtypes of the first player.
1087
+ # When a fact type matches like this, there is an implied join to the subtype.
1088
+ players[0].subtypes.each do |subtype|
1089
+ players[0] = subtype
1090
+ fr = invoked_fact_roles_by_players(phrases, players)
1091
+ return fr if fr
1092
+ end
1093
+
1094
+ # REVISIT: Do we need to do this again for the supertypes of the first player?
1095
+
1096
+ nil
1097
+ end
1098
+
1099
+ def bind_fact_reading(fact_type, qualifiers, phrases)
1100
+ reading = debug :reading, "Processing reading #{phrases.inspect}" do
1101
+ role_phrases = phrases.select do |phrase|
1102
+ Hash === phrase && phrase[:binding]
1103
+ end
1104
+
1105
+ # All readings for a fact type must have the same number of roles.
1106
+ # This might be relaxed later for fact clauses, where readings might
1107
+ # be concatenated if the adjacent items are the same concept.
1108
+ if (fact_type && fact_type.all_reading.size > 0 && role_phrases.size != fact_type.all_role.size)
1109
+ raise "#{
1110
+ role_phrases.size > fact_type.all_role.size ? "Too many" : "Not all"
1111
+ } roles found for non-initial reading of #{fact_type.describe}"
1112
+ end
1113
+
1114
+ # If the reading is the first and is an invocation of an existing fact type,
1115
+ # find and return the existing fact type and reading.
1116
+ if !fact_type
1117
+ bindings = role_phrases.map{|phrase| phrase[:binding]}
1118
+ bindings_by_name = bindings.sort_by{|b| [b.concept.name, b.leading_adjective||'', b.trailing_adjective||'']}
1119
+ bound_concepts_by_name = bindings_by_name.map{|b| b.concept}
1120
+ reading = nil
1121
+ first_role = nil
1122
+ debug :reading, "Looking for existing fact type to match #{phrases.inspect}" do
1123
+ first_role =
1124
+ bindings[0].concept.all_role.detect do |role|
1125
+ next if role.fact_type.all_role.size != bindings.size # Wrong arity
1126
+ concepts = role.fact_type.all_role.map{|r| r.concept }
1127
+ next unless bound_concepts_by_name == concepts.sort_by{|c| c.name} # Wrong players
1128
+ matching_reading =
1129
+ role.fact_type.all_reading.detect do |reading|
1130
+ debug :reading, "Considering #{reading.expand}"
1131
+ reading_role_refs = reading.role_sequence.all_role_ref.sort_by{|rr| rr.ordinal}
1132
+ reading_concepts = reading_role_refs.map{|rr| rr.role.concept}
1133
+ elements = reading.text.scan(/\{[0-9]+\}|\w+/)
1134
+ next false if elements.zip(phrases).detect do |element, phrase|
1135
+ if element =~ /\A\{([0-9]+)\}\Z/ # Must be a role player; need a matching binding
1136
+ !phrase.is_a?(Hash) or
1137
+ !(binding = phrase[:binding]) or
1138
+ !(role_ref = reading_role_refs[$1.to_i]) or # If we fail here, it's an error!
1139
+ role_ref.role.concept != binding.concept or
1140
+ =begin
1141
+ # REVISIT: This loose matching fails on the Metamodel with RingConstraints.
1142
+ # Need "best match" semantics, or some way to know that these adjectives are "extra" to the readings.
1143
+ (la = role_ref.leading_adjective) && binding[:leading_adjective] != la or
1144
+ (ta = role_ref.trailing_adjective) && binding[:trailing_adjective] != ta
1145
+ =end
1146
+ role_ref.leading_adjective != binding[:leading_adjective] or
1147
+ role_ref.trailing_adjective != binding[:trailing_adjective]
1148
+ else
1149
+ element != phrase
1150
+ end
1151
+ end
1152
+ debug :reading, "'#{reading.expand}' matches!"
1153
+ true # There was no mismatch
1154
+ end
1155
+ matching_reading # This role was in a matching fact type!
1156
+ end
1157
+ end
1158
+
1159
+ if first_role
1160
+ fact_type = first_role.fact_type
1161
+
1162
+ # Remember the roles for each binding, for subsequent readings:
1163
+ reading.role_sequence.all_role_ref.each_with_index do |rr, index|
1164
+ @symbols.roles_by_binding[bindings[index]] = rr.role
1165
+ end
1166
+
1167
+ return [fact_type, reading]
1168
+ end
1169
+ end
1170
+
1171
+ fact_type ||= @constellation.FactType(:new)
1172
+
1173
+ # Create the roles on the first reading, or look them up on subsequent readings.
1174
+ # If the player occurs twice, we must find one with matching adjectives.
1175
+
1176
+ role_sequence = @constellation.RoleSequence(:new) # RoleSequence for RoleRefs of this reading
1177
+ roles = []
1178
+ role_phrases.each_with_index do |role_phrase, index|
1179
+ binding = role_phrase[:binding]
1180
+ role_name = role_phrase[:role_name]
1181
+ player = binding.concept
1182
+ role = nil
1183
+ if (fact_type.all_reading.size == 0) # First reading
1184
+ # Assert this role of the fact type:
1185
+ role = @constellation.Role(fact_type, fact_type.all_role.size, :concept => player)
1186
+ role.role_name = role_name if role_name
1187
+ debug "Concept #{player.name} found, created role #{role.describe} by binding #{binding.inspect}"
1188
+ @symbols.roles_by_binding[binding] = role
1189
+ else # Subsequent readings
1190
+ #debug "Looking for role #{binding.inspect} in bindings #{@symbols.roles_by_binding.inspect}"
1191
+ role = @symbols.roles_by_binding[binding]
1192
+ raise "Role #{binding.inspect} not found in prior readings" if !role
1193
+ player = role.concept
1194
+ end
1195
+
1196
+ # Save a role value restriction
1197
+ if (ranges = role_phrase[:restriction])
1198
+ role.role_value_restriction = value_restriction(ranges)
1199
+ end
1200
+
1201
+ roles << role
1202
+
1203
+ # Create the RoleRefs for the RoleSequence
1204
+
1205
+ role_ref = @constellation.RoleRef(role_sequence, index, :role => roles[index])
1206
+ leading_adjective = role_phrase[:leading_adjective]
1207
+ role_ref.leading_adjective = leading_adjective if leading_adjective
1208
+ trailing_adjective = role_phrase[:trailing_adjective]
1209
+ role_ref.trailing_adjective = trailing_adjective if trailing_adjective
1210
+ end
1211
+
1212
+ # Create any embedded constraints:
1213
+ debug "Creating embedded presence constraints for #{fact_type.describe}" do
1214
+ create_embedded_presence_constraints(fact_type, role_phrases, roles)
1215
+ end
1216
+
1217
+ process_qualifiers(role_sequence, qualifiers)
1218
+
1219
+ # Save the first role sequence to be used for a default PresenceConstraint
1220
+ add_reading(fact_type, role_sequence, phrases)
1221
+ end
1222
+ [fact_type, reading]
1223
+ end
1224
+
1225
+ def fact_type_identification(fact_type, name, prefer)
1226
+ if !@symbols.embedded_presence_constraints.detect{|pc| pc.max_frequency == 1}
1227
+ # Provide a default identifier for a fact type that's lacking one (over all roles):
1228
+ first_role_sequence = fact_type.preferred_reading.role_sequence
1229
+ #puts "Creating PC for #{name}: #{fact_type.describe}"
1230
+ identifier = @constellation.PresenceConstraint(
1231
+ :new,
1232
+ :vocabulary => @vocabulary,
1233
+ :name => "#{name}PK", # Is this a useful name?
1234
+ :role_sequence => first_role_sequence,
1235
+ :is_preferred_identifier => prefer,
1236
+ :max_frequency => 1 # Unique
1237
+ )
1238
+ # REVISIT: The UC might be provided later as an external constraint, relax this rule:
1239
+ #raise "'#{fact_type.default_reading}': non-unary fact types having no uniqueness constraints must be objectified (named)" unless fact_type.entity_type
1240
+ debug "Made default fact type identifier #{identifier.object_id} over #{first_role_sequence.describe} in #{fact_type.describe}"
1241
+ elsif prefer
1242
+ #debug "Made fact type identifier #{identifier.object_id} preferred over #{@symbols.embedded_presence_constraints[0].role_sequence.describe} in #{fact_type.describe}"
1243
+ @symbols.embedded_presence_constraints[0].is_preferred_identifier = true
1244
+ end
1245
+ end
1246
+
1247
+ # Categorise the fact type clauses according to the set of role player names
1248
+ # Return an array where each element is an array of clauses, the clauses having
1249
+ # matching players, and otherwise preserving the order of definition.
1250
+ def clauses_by_fact_type(clauses)
1251
+ clause_group_by_role_players = {}
1252
+ clauses.inject([]) do |clause_groups, clause|
1253
+ type, qualifiers, phrases, context = *clause
1254
+
1255
+ debug "Clause: #{clause.inspect}"
1256
+ roles = phrases.map do |phrase|
1257
+ Hash === phrase ? phrase[:binding] : nil
1258
+ end.compact
1259
+
1260
+ # Look for an existing clause group involving these players, or make one:
1261
+ clause_group = clause_group_by_role_players[key = roles.sort]
1262
+ if clause_group # Another clause for an existing clause group
1263
+ clause_group << clause
1264
+ else # A new clause group
1265
+ clause_groups << (clause_group_by_role_players[key] = [clause])
1266
+ end
1267
+ clause_groups
1268
+ end
1269
+ end
1270
+
1271
+ # For each fact reading there may be embedded mandatory, uniqueness or frequency constraints:
1272
+ def create_embedded_presence_constraints(fact_type, role_phrases, roles)
1273
+ embedded_presence_constraints = []
1274
+ role_phrases.zip(roles).each_with_index do |role_pair, index|
1275
+ role_phrase, role = *role_pair
1276
+
1277
+ next unless quantifier = role_phrase[:quantifier]
1278
+
1279
+ debug "Processing embedded constraint #{quantifier.inspect} on #{role.concept.name} in #{fact_type.describe}" do
1280
+ constrained_roles = roles.clone
1281
+ constrained_roles.delete_at(index)
1282
+ constraint = find_pc_over_roles(constrained_roles)
1283
+ if constraint
1284
+ debug "Setting max frequency to #{quantifier[1]} for existing constraint #{constraint.object_id} over #{constraint.role_sequence.describe} in #{fact_type.describe}"
1285
+ raise "Conflicting maximum frequency for constraint" if constraint.max_frequency && constraint.max_frequency != quantifier[1]
1286
+ constraint.max_frequency = quantifier[1]
1287
+ else
1288
+ role_sequence = @constellation.RoleSequence(:new)
1289
+ constrained_roles.each_with_index do |constrained_role, i|
1290
+ role_ref = @constellation.RoleRef(role_sequence, i, :role => constrained_role)
1291
+ end
1292
+ constraint = @constellation.PresenceConstraint(
1293
+ :new,
1294
+ :vocabulary => @vocabulary,
1295
+ :role_sequence => role_sequence,
1296
+ :is_mandatory => quantifier[0] && quantifier[0] > 0, # REVISIT: Check "maybe" qualifier?
1297
+ :max_frequency => quantifier[1],
1298
+ :min_frequency => quantifier[0]
1299
+ )
1300
+ embedded_presence_constraints << constraint
1301
+ 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}"
1302
+ end
1303
+ end
1304
+ end
1305
+ @symbols.embedded_presence_constraints += embedded_presence_constraints
1306
+ end
1307
+
1308
+ def process_qualifiers(role_sequence, qualifiers)
1309
+ return unless qualifiers.size > 0
1310
+ qualifiers.sort!
1311
+
1312
+ # Process the ring constraints:
1313
+ ring_constraints, qualifiers = qualifiers.partition{|q| RingTypes.include?(q) }
1314
+ unless ring_constraints.empty?
1315
+ # A Ring may be over a supertype/subtype pair, and this won't find that.
1316
+ role_refs = Array(role_sequence.all_role_ref)
1317
+ role_pairs = []
1318
+ player_supertypes_by_role = role_refs.map{|rr|
1319
+ concept = rr.role.concept
1320
+ concept.is_a?(ActiveFacts::Metamodel::EntityType) ? supertypes(concept) : [concept]
1321
+ }
1322
+ role_refs.each_with_index{|rr1, i|
1323
+ player1 = rr1.role.concept
1324
+ (i+1...role_refs.size).each{|j|
1325
+ rr2 = role_refs[j]
1326
+ player2 = rr2.role.concept
1327
+ if player_supertypes_by_role[i] - player_supertypes_by_role[j] != player_supertypes_by_role[i]
1328
+ role_pairs << [rr1.role, rr2.role]
1329
+ end
1330
+ }
1331
+ }
1332
+ raise "ring constraint (#{ring_constraints*" "}) role pair not found" if role_pairs.size == 0
1333
+ 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
1334
+ roles = role_pairs[0]
1335
+
1336
+ # Ensure that the keys in RingPairs follow others:
1337
+ ring_constraints = ring_constraints.partition{|rc| !RingPairs.keys.include?(rc.downcase.to_sym) }.flatten
1338
+
1339
+ if ring_constraints.size > 1 and !RingPairs[ring_constraints[-1].to_sym].include?(ring_constraints[0].to_sym)
1340
+ raise "incompatible ring constraint types (#{ring_constraints*", "})"
1341
+ end
1342
+ ring_type = ring_constraints.map{|c| c.capitalize}*""
1343
+
1344
+ ring = @constellation.RingConstraint(
1345
+ :new,
1346
+ :vocabulary => @vocabulary,
1347
+ # :name => name, # REVISIT: Create a name for Ring Constraints?
1348
+ :role => roles[0],
1349
+ :other_role => roles[1],
1350
+ :ring_type => ring_type
1351
+ )
1352
+
1353
+ debug "Added #{ring.verbalise} #{ring.class.roles.keys.map{|k|"#{k} => "+ring.send(k).verbalise}*", "}"
1354
+ end
1355
+
1356
+ return unless qualifiers.size > 0
1357
+
1358
+ # Process the remaining qualifiers:
1359
+ puts "REVISIT: Qualifiers #{qualifiers.inspect} over #{role_sequence.describe}"
1360
+ end
1361
+
1362
+ def find_pc_over_roles(roles)
1363
+ return nil if roles.size == 0 # Safeguard; this would chuck an exception otherwise
1364
+ roles[0].all_role_ref.each do |role_ref|
1365
+ next if role_ref.role_sequence.all_role_ref.map(&:role) != roles
1366
+ pc = role_ref.role_sequence.all_presence_constraint.single # Will return nil if there's more than one.
1367
+ #puts "Existing PresenceConstraint matches those roles!" if pc
1368
+ return pc if pc
1369
+ end
1370
+ nil
1371
+ end
1372
+
1373
+ def add_reading(fact_type, role_sequence, phrases)
1374
+ ordinal = (fact_type.all_reading.map(&:ordinal).max||-1) + 1 # Use the next unused ordinal
1375
+ reading = @constellation.Reading(fact_type, ordinal, :role_sequence => role_sequence)
1376
+ role_num = -1
1377
+ reading.text = phrases.map {|phrase|
1378
+ Hash === phrase ? "{#{role_num += 1}}" : phrase
1379
+ }*" "
1380
+ raise "Wrong number of players (#{role_num+1}) found in reading #{reading.text} over #{fact_type.describe}" if role_num+1 != fact_type.all_role.size
1381
+ debug "Added reading #{reading.text}"
1382
+ reading
1383
+ end
1384
+
1385
+ # Return an array of this entity type and all its supertypes, transitively:
1386
+ def supertypes(o)
1387
+ ([o] + o.all_supertype_inheritance.map{|ti| supertypes(ti.supertype)}.flatten).uniq
1388
+ end
1389
+
1390
+ def concept_by_name(name)
1391
+ player = @constellation.Concept[[@vocabulary.identifying_role_values, name]]
1392
+
1393
+ # REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
1394
+ if !player && %w{Date DateAndTime Time}.include?(name)
1395
+ player = @constellation.ValueType(@vocabulary.identifying_role_values, name)
1396
+ end
1397
+
1398
+ if (!player && @symbols.allowed_forward[name])
1399
+ player = @constellation.EntityType(@vocabulary, name)
1400
+ end
1401
+ player
1402
+ end
1403
+
1404
+ class SymbolTable #:nodoc:all
1405
+ # Externally built tables used in this binding context:
1406
+ attr_reader :roles_by_binding
1407
+ attr_accessor :embedded_presence_constraints
1408
+ attr_accessor :allowed_forward
1409
+ attr_reader :constellation
1410
+ attr_reader :vocabulary
1411
+ attr_reader :bindings_by_concept
1412
+ attr_reader :role_names
1413
+
1414
+ # A Binding here is a form of reference to a concept, being a name and optional adjectives, possibly designated by a role name:
1415
+ Binding = Struct.new("Binding", :concept, :name, :leading_adjective, :trailing_adjective, :role_name)
1416
+ class Binding
1417
+ def inspect
1418
+ "Binding(#{concept.class.basename} #{concept.name}, #{[leading_adjective, name, trailing_adjective].compact*"-"}#{role_name ? " (as #{role_name})" : ""})"
1419
+ end
1420
+
1421
+ # Any ordering works to allow a hash to be keyed by a set (unordered array) of Bindings:
1422
+ def <=>(other)
1423
+ object_id <=> other.object_id
1424
+ end
1425
+ end
1426
+
1427
+ def initialize(constellation, vocabulary)
1428
+ @constellation = constellation
1429
+ @vocabulary = vocabulary
1430
+ @bindings_by_concept = Hash.new {|h, k| h[k] = [] } # Indexed by Binding#name, maybe multiple entries for each name
1431
+ @role_names = {}
1432
+
1433
+ @embedded_presence_constraints = []
1434
+ @roles_by_binding = {} # Build a hash of allowed bindings on first reading (check against it on subsequent ones)
1435
+ @allowed_forward = {} # No roles may be forward-referenced
1436
+ end
1437
+
1438
+ #
1439
+ # This method is the guts of role matching.
1440
+ # "words" may be a single word (and then the adjectives may also be used) or two words.
1441
+ # In either case a word is expected to be a defined concept or role name.
1442
+ # If a role_name is provided here, that's a *definition* and will only be accepted if legal
1443
+ # If allowed_forward is true, words is a single word and is not defined, create a forward Entity
1444
+ # If leading_speculative or trailing_speculative is true, the adjectives may not apply. If they do apply, use them.
1445
+ # If loose_binding_except is true, it's a hash containing names that may *not* be loose-bound... else none may.
1446
+ #
1447
+ # Loose binding is when a word without an adjective matches a role with, or vice verse.
1448
+ #
1449
+ 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)
1450
+ words = Array(words)
1451
+ if (words.size > 2 or words.size == 2 && (leading_adjective or trailing_adjective or allowed_forward))
1452
+ raise "role has too many adjectives '#{[leading_adjective, words, trailing_adjective].flatten.compact*" "}'"
1453
+ end
1454
+
1455
+ # Check for use of a role name, valid if they haven't used any adjectives or tried to define a role_name:
1456
+ binding = @role_names[words[0]]
1457
+ if binding && words.size == 1 # If ok, this is it.
1458
+ raise "May not use existing role name '#{words[0]}' to define a new role name" if role_name
1459
+ if (leading_adjective && !leading_speculative) || (trailing_adjective && !trailing_speculative)
1460
+ raise "May not use existing role name '#{words[0]}' with adjectives"
1461
+ end
1462
+ return binding.concept, binding
1463
+ end
1464
+
1465
+ # Look for an existing definition
1466
+ # If we have more than one word that might be the concept name, find which it is:
1467
+ words.each do |w|
1468
+ # Find the existing defined binding that matches this one:
1469
+ bindings = @bindings_by_concept[w]
1470
+ best_match = nil
1471
+ matched_adjectives = 0
1472
+ bindings.each do |binding|
1473
+ # Adjectives defined on the binding must be matched unless loose binding is allowed.
1474
+ loose_ok = loose_binding_except and !loose_binding_except[binding.concept.name]
1475
+
1476
+ # Don't allow binding a new role name to an existing one:
1477
+ next if role_name and role_name != binding.role_name
1478
+
1479
+ quality = 0
1480
+ if binding.leading_adjective != leading_adjective
1481
+ next if binding.leading_adjective && leading_adjective # Both set, but different
1482
+ next if !loose_ok && (!leading_speculative || !leading_adjective)
1483
+ quality += 1
1484
+ end
1485
+
1486
+ if binding.trailing_adjective != trailing_adjective
1487
+ next if binding.trailing_adjective && trailing_adjective # Both set, but different
1488
+ next if !loose_ok && (!trailing_speculative || !trailing_adjective)
1489
+ quality += 1
1490
+ end
1491
+
1492
+ quality += 1 unless binding.role_name # A role name that was not matched... better if there wasn't one
1493
+
1494
+ if (quality > matched_adjectives || !best_match)
1495
+ best_match = binding # A better match than we had before
1496
+ matched_adjectives = quality
1497
+ break unless loose_ok || leading_speculative || trailing_speculative
1498
+ end
1499
+ end
1500
+
1501
+ if best_match
1502
+ # We've found the best existing definition
1503
+
1504
+ # Indicate which speculative adjectives were used so the clauses can be deleted:
1505
+ leading_adjective.replace("") if best_match.leading_adjective and leading_adjective and leading_speculative
1506
+ trailing_adjective.replace("") if best_match.trailing_adjective and trailing_adjective and trailing_speculative
1507
+
1508
+ return best_match.concept, best_match
1509
+ end
1510
+
1511
+ # No existing defined binding. Look up an existing concept of this name:
1512
+ player = concept(w, allowed_forward)
1513
+ next unless player
1514
+
1515
+ # Found a new binding for this player, save it.
1516
+
1517
+ # Check that a trailing adjective isn't an existing role name or concept:
1518
+ trailing_word = words[1] if w == words[0]
1519
+ if trailing_word
1520
+ raise "May not use existing role name '#{trailing_word}' with a new name or with adjectives" if @role_names[trailing_word]
1521
+ raise "ambiguous concept reference #{words*" '"}'" if concept(trailing_word)
1522
+ end
1523
+ leading_word = words[0] if w != words[0]
1524
+
1525
+ raise "may not redefine existing concept '#{role_name}' as a role name" if role_name and concept(role_name)
1526
+
1527
+ binding = Binding.new(
1528
+ player,
1529
+ w,
1530
+ (!leading_speculative && leading_adjective) || leading_word,
1531
+ (!trailing_speculative && trailing_adjective) || trailing_word,
1532
+ role_name
1533
+ )
1534
+ @bindings_by_concept[binding.name] << binding
1535
+ @role_names[binding.role_name] = binding if role_name
1536
+ return binding.concept, binding
1537
+ end
1538
+
1539
+ # Not found.
1540
+ return nil
1541
+ end
1542
+
1543
+ # return the EntityType or ValueType this name refers to:
1544
+ def concept(name, allowed_forward = false)
1545
+ # See if the name is a defined concept in this vocabulary:
1546
+ player = @constellation.Concept[[virv = @vocabulary.identifying_role_values, name]]
1547
+
1548
+ # REVISIT: Hack to allow facts to refer to standard types that will be imported from standard vocabulary:
1549
+ if !player && %w{Date DateAndTime Time}.include?(name)
1550
+ player = @constellation.ValueType(virv, name)
1551
+ end
1552
+
1553
+ if !player && allowed_forward
1554
+ player = @constellation.EntityType(@vocabulary, name)
1555
+ end
1556
+
1557
+ player
1558
+ end
1559
+
1560
+ def bind_roles_in_clauses(clauses, identification = [])
1561
+ identification ||= []
1562
+ bind_roles_in_phrases_list(
1563
+ clauses.map{|clause| clause[2]}, # Extract the phrases
1564
+ single_word_identifiers = identification.map{|i| i.size == 1 ? i[0] : nil}.compact.uniq
1565
+ )
1566
+ end
1567
+
1568
+ #
1569
+ # Walk through all phrases identifying role players.
1570
+ # Each role player phrase gets a :binding key added to it.
1571
+ #
1572
+ # Any adjectives that the parser didn't recognise are merged with their players here,
1573
+ # as long as they're indicated as adjectives of that player somewhere in the readings.
1574
+ #
1575
+ # Other words are turned from phrases (hashes) into simple strings.
1576
+ #
1577
+ def bind_roles_in_phrases_list(phrases_list, allowed_forwards = [])
1578
+ disallow_loose_binding = allowed_forwards.inject({}) { |h, v| h[v] = true; h }
1579
+ phrases_list.each do |phrases|
1580
+ debug :bind, "Binding phrases"
1581
+
1582
+ phrase_numbers_used_speculatively = []
1583
+ disallow_loose_binding_this_reading = disallow_loose_binding.clone
1584
+ phrases.each_with_index do |phrase, index|
1585
+ la = phrase[:leading_adjective]
1586
+ player_name = phrase[:word]
1587
+ ta = phrase[:trailing_adjective]
1588
+ role_name = phrase[:role_name]
1589
+
1590
+ # We use the preceeding phrase and/or following phrase speculatively if they're simple words:
1591
+ preceeding_phrase = nil
1592
+ following_phrase = nil
1593
+ if !la && index > 0 && (preceeding_phrase = phrases[index-1])
1594
+ preceeding_phrase = nil unless String === preceeding_phrase || preceeding_phrase.keys == [:word]
1595
+ la = preceeding_phrase[:word] if Hash === preceeding_phrase
1596
+ end
1597
+ if !ta && (following_phrase = phrases[index+1])
1598
+ following_phrase = nil unless following_phrase.keys == [:word]
1599
+ ta = following_phrase[:word] if following_phrase
1600
+ end
1601
+
1602
+ # If the identification includes this player name as a single word, it's allowed to be forward referenced:
1603
+ allowed_forward = allowed_forwards.include?(player_name)
1604
+
1605
+ debug :bind, "Binding a role: #{[player_name, la, ta, role_name, allowed_forward, !!preceeding_phrase, !!following_phrase].inspect}"
1606
+ player, binding = bind(
1607
+ player_name,
1608
+ la, ta,
1609
+ role_name,
1610
+ allowed_forward,
1611
+ !!preceeding_phrase, !!following_phrase,
1612
+ phrases == phrases_list[0] ? nil : disallow_loose_binding_this_reading # Never allow loose binding on the first reading
1613
+ )
1614
+ disallow_loose_binding_this_reading[player.name] = true if player
1615
+
1616
+ # Arrange to delete the speculative adjectives that were used:
1617
+ if preceeding_phrase && preceeding_phrase[:word] == ""
1618
+ debug :bind, "binding consumed a speculative leading_adjective #{la}"
1619
+ # The numbers are adjusted to allow for prior deletions.
1620
+ phrase_numbers_used_speculatively << index-1-phrase_numbers_used_speculatively.size
1621
+ end
1622
+ if following_phrase && following_phrase[:word] == ""
1623
+ debug :bind, "binding consumed a speculative trailing_adjective #{ta}"
1624
+ phrase_numbers_used_speculatively << index+1-phrase_numbers_used_speculatively.size
1625
+ end
1626
+
1627
+ if player
1628
+ # Replace the words used to identify the role by a reference to the role itself,
1629
+ # leaving :quantifier, :function, :restriction and :literal intact
1630
+ phrase[:binding] = binding
1631
+ binding
1632
+ else
1633
+ raise "Internal error; role #{phrase.inspect} not matched" unless phrase.keys == [:word]
1634
+ # Just a linking word
1635
+ phrases[index] = phrase[:word]
1636
+ end
1637
+ debug :bind, "Bound phrase: #{phrase.inspect}" + " -> " + (player ? player.name+", "+binding.inspect : phrase[:word].inspect)
1638
+
1639
+ end
1640
+ phrase_numbers_used_speculatively.each do |index|
1641
+ phrases.delete_at(index)
1642
+ end
1643
+ debug :bind, "Bound phrases: #{phrases.inspect}"
1644
+ end
1645
+ end
1646
+ end # of SymbolTable class
1647
+
1648
+ # The Context manages some key information revealed or needed during parsing
1649
+ class Context
1650
+ # REVISIT; This class is "work in progress", supporting semantic predicates from Treetop.
1651
+ def initialize(compiler)
1652
+ @compiler = compiler
1653
+ @vocabularies = {}
1654
+ end
1655
+
1656
+ def vocabulary(v)
1657
+ # puts "Parser has started work on vocabulary #{v}"
1658
+ @vocabularies[v] = {}
1659
+ @terms = {}
1660
+ true
1661
+ end
1662
+
1663
+ def entity_type(c)
1664
+ #puts "Parser has started work on entity_type '#{c}'"
1665
+ @terms[c] = true
1666
+ true
1667
+ end
1668
+
1669
+ def value_type(c)
1670
+ #puts "Parser has started work on value_type '#{c}'"
1671
+ @terms[c] = true
1672
+ true
1673
+ end
1674
+
1675
+ def objectified_fact_type(c)
1676
+ #puts "Parser has started work on objectified_fact_type '#{c}'"
1677
+ @terms[c] = true
1678
+ true
1679
+ end
1680
+
1681
+ def reset_role_names
1682
+ # puts "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
1683
+ @role_names = {}
1684
+ true
1685
+ end
1686
+
1687
+ def role_name(r)
1688
+ #puts "\tadding role name '#{r}'"
1689
+ @role_names[r] = true
1690
+ true
1691
+ end
1692
+
1693
+ def term?(t)
1694
+ #puts "term?(#{t})"
1695
+ false
1696
+ end
1697
+
1698
+ def global_term?(t)
1699
+ #puts "global_term?(#{t})"
1700
+ false
1701
+ end
1702
+
1703
+ def term_starts(s)
1704
+ #p s
1705
+ #debugger
1706
+ true
1707
+ end
1708
+
1709
+ def term_continues(s)
1710
+ #p s
1711
+ #debugger
1712
+ true
1713
+ end
1714
+ end
1715
+
1716
+ end
1717
+ end
1718
+ end