activefacts 0.8.6 → 0.8.8

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