activefacts 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +83 -0
  3. data/README.rdoc +81 -0
  4. data/Rakefile +41 -0
  5. data/bin/afgen +46 -0
  6. data/bin/cql +52 -0
  7. data/examples/CQL/Address.cql +46 -0
  8. data/examples/CQL/Blog.cql +54 -0
  9. data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
  10. data/examples/CQL/Death.cql +16 -0
  11. data/examples/CQL/Genealogy.cql +95 -0
  12. data/examples/CQL/Marriage.cql +18 -0
  13. data/examples/CQL/Metamodel.cql +238 -0
  14. data/examples/CQL/MultiInheritance.cql +19 -0
  15. data/examples/CQL/OilSupply.cql +47 -0
  16. data/examples/CQL/Orienteering.cql +108 -0
  17. data/examples/CQL/PersonPlaysGame.cql +17 -0
  18. data/examples/CQL/SchoolActivities.cql +31 -0
  19. data/examples/CQL/SimplestUnary.cql +12 -0
  20. data/examples/CQL/SubtypePI.cql +32 -0
  21. data/examples/CQL/Warehousing.cql +99 -0
  22. data/examples/CQL/WindowInRoomInBldg.cql +22 -0
  23. data/lib/activefacts.rb +10 -0
  24. data/lib/activefacts/api.rb +25 -0
  25. data/lib/activefacts/api/concept.rb +384 -0
  26. data/lib/activefacts/api/constellation.rb +106 -0
  27. data/lib/activefacts/api/entity.rb +239 -0
  28. data/lib/activefacts/api/instance.rb +54 -0
  29. data/lib/activefacts/api/numeric.rb +158 -0
  30. data/lib/activefacts/api/role.rb +94 -0
  31. data/lib/activefacts/api/standard_types.rb +67 -0
  32. data/lib/activefacts/api/support.rb +59 -0
  33. data/lib/activefacts/api/value.rb +122 -0
  34. data/lib/activefacts/api/vocabulary.rb +120 -0
  35. data/lib/activefacts/cql.rb +31 -0
  36. data/lib/activefacts/cql/CQLParser.treetop +104 -0
  37. data/lib/activefacts/cql/Concepts.treetop +112 -0
  38. data/lib/activefacts/cql/DataTypes.treetop +66 -0
  39. data/lib/activefacts/cql/Expressions.treetop +113 -0
  40. data/lib/activefacts/cql/FactTypes.treetop +185 -0
  41. data/lib/activefacts/cql/Language/English.treetop +92 -0
  42. data/lib/activefacts/cql/LexicalRules.treetop +169 -0
  43. data/lib/activefacts/cql/Rakefile +6 -0
  44. data/lib/activefacts/cql/parser.rb +88 -0
  45. data/lib/activefacts/generate/absorption.rb +87 -0
  46. data/lib/activefacts/generate/cql.rb +441 -0
  47. data/lib/activefacts/generate/cql/html.rb +397 -0
  48. data/lib/activefacts/generate/null.rb +19 -0
  49. data/lib/activefacts/generate/ordered.rb +557 -0
  50. data/lib/activefacts/generate/ruby.rb +326 -0
  51. data/lib/activefacts/generate/sql/server.rb +164 -0
  52. data/lib/activefacts/generate/text.rb +21 -0
  53. data/lib/activefacts/input/cql.rb +1268 -0
  54. data/lib/activefacts/input/orm.rb +926 -0
  55. data/lib/activefacts/persistence.rb +1 -0
  56. data/lib/activefacts/persistence/composition.rb +653 -0
  57. data/lib/activefacts/support.rb +51 -0
  58. data/lib/activefacts/version.rb +3 -0
  59. data/lib/activefacts/vocabulary.rb +6 -0
  60. data/lib/activefacts/vocabulary/extensions.rb +343 -0
  61. data/lib/activefacts/vocabulary/metamodel.rb +303 -0
  62. data/script/txt2html +71 -0
  63. data/spec/absorption_spec.rb +95 -0
  64. data/spec/api/autocounter.rb +82 -0
  65. data/spec/api/constellation.rb +130 -0
  66. data/spec/api/entity_type.rb +101 -0
  67. data/spec/api/instance.rb +428 -0
  68. data/spec/api/roles.rb +122 -0
  69. data/spec/api/value_type.rb +112 -0
  70. data/spec/api_spec.rb +14 -0
  71. data/spec/cql_cql_spec.rb +58 -0
  72. data/spec/cql_parse_spec.rb +31 -0
  73. data/spec/cql_ruby_spec.rb +60 -0
  74. data/spec/cql_sql_spec.rb +54 -0
  75. data/spec/cql_symbol_tables_spec.rb +259 -0
  76. data/spec/cql_unit_spec.rb +336 -0
  77. data/spec/cqldump_spec.rb +169 -0
  78. data/spec/norma_cql_spec.rb +48 -0
  79. data/spec/norma_ruby_spec.rb +50 -0
  80. data/spec/norma_sql_spec.rb +45 -0
  81. data/spec/norma_tables_spec.rb +94 -0
  82. data/spec/spec.opts +1 -0
  83. data/spec/spec_helper.rb +10 -0
  84. metadata +173 -0
@@ -0,0 +1,92 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ grammar Language
4
+
5
+ rule subtype_prefix
6
+ defined_as 'subtype' S 'of' S
7
+ / 'is' s 'a' s ('kind'/'subtype') s 'of' S
8
+ end
9
+
10
+ rule defined_as
11
+ s 'is' s 'defined' S as s
12
+ end
13
+
14
+ rule identified_by
15
+ identified s by s
16
+ end
17
+
18
+ rule quantifier
19
+ each s { def value; [1, nil]; end }
20
+ / some s { def value; nil; end }
21
+ # REVISIT: "Some" means that any prior occurrence of this role player is to be ignored; this is a new occurrence
22
+ # "that" on the other hand means that this role player was *previously* designated using "some".
23
+ # These distinctions are lost here
24
+ / that s { def value; nil; end }
25
+ / one s { def value; [1, 1]; end }
26
+ / no s { def value; [0, 0]; end }
27
+ / exactly s quantity { def value; q = quantity.value; [q, q]; end }
28
+ / at s least s quantity most:( and s at s most s q:quantity )?
29
+ { def value;
30
+ [ quantity.value,
31
+ most.empty? ? nil : most.q.value
32
+ ]
33
+ end
34
+ }
35
+ / at s most s quantity { def value; [ nil, quantity.value ]; end }
36
+ / from s numeric_range s { def value; numeric_range.value; end }
37
+ / either_all_or_none s { def value; [ -1, 1 ]; end }
38
+ end
39
+
40
+ rule quantity
41
+ one s { def value; 1; end }
42
+ / number s { def value; number.value; end }
43
+ end
44
+
45
+ rule acyclic 'acyclic' !alphanumeric end
46
+ rule alias 'alias' !alphanumeric end
47
+ rule all 'all' !alphanumeric end
48
+ rule and 'and' !alphanumeric end
49
+ rule as 'as' !alphanumeric end
50
+ rule at 'at' !alphanumeric end
51
+ rule by 'by' !alphanumeric end
52
+ rule definitely 'definitely' !alphanumeric end
53
+ rule each 'each' !alphanumeric end
54
+ rule either 'either' !alphanumeric end
55
+ rule entity 'entity' !alphanumeric end
56
+ rule exactly 'exactly' !alphanumeric end
57
+ rule false 'false' !alphanumeric end
58
+ rule from 'from' !alphanumeric end
59
+ rule identified ('known'/'identified') !alphanumeric end
60
+ rule if 'if' !alphanumeric end
61
+ rule import 'import' !alphanumeric end
62
+ rule includes 'includes' !alphanumeric end
63
+ rule intransitive 'intransitive' !alphanumeric end
64
+ rule is 'is' !alphanumeric end
65
+ rule its 'its' !alphanumeric end
66
+ rule least 'least' !alphanumeric end
67
+ rule matches 'matches' !alphanumeric end
68
+ rule maybe 'maybe' !alphanumeric end
69
+ rule most 'most' !alphanumeric end
70
+ rule no 'no' !alphanumeric end
71
+ rule none 'none' !alphanumeric end
72
+ rule not 'not' !alphanumeric end
73
+ rule either_all_or_none either s all s or s none end
74
+ rule one 'one' !alphanumeric end
75
+ rule only 'only' !alphanumeric end
76
+ rule or 'or' !alphanumeric end
77
+ rule restricted 'restricted' !alphanumeric end
78
+ rule returning 'returning' !alphanumeric end
79
+ rule some 'some' !alphanumeric end
80
+ rule static 'static' !alphanumeric end
81
+ rule symmetric 'symmetric' !alphanumeric end
82
+ rule that 'that' !alphanumeric end
83
+ rule to 'to' !alphanumeric end
84
+ rule transient 'transient' !alphanumeric end
85
+ rule transitive 'transitive' !alphanumeric end
86
+ rule true 'true' !alphanumeric end
87
+ rule vocabulary 'vocabulary' !alphanumeric end
88
+ rule where 'where' !alphanumeric end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,169 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ grammar LexicalRules
4
+
5
+ rule range
6
+ numeric_range / string_range
7
+ end
8
+
9
+ rule numeric_range
10
+ number s tail:( '..' s end:number? s )?
11
+ {
12
+ def value
13
+ if !tail.empty?
14
+ last = tail.end.value unless tail.end.empty?
15
+ [ number.value, last ]
16
+ else
17
+ number.value
18
+ end
19
+ end
20
+ }
21
+ / '..' s number s
22
+ {
23
+ def value
24
+ [ nil, number.value ]
25
+ end
26
+ }
27
+ end
28
+
29
+ rule string_range
30
+ string s tail:( '..' s end:string? s )?
31
+ {
32
+ # Ranges require the original text of the string, not the content:
33
+ def value
34
+ first = string.text_value
35
+ if !tail.empty?
36
+ last = tail.end.text_value unless tail.end.empty?
37
+ [ first, last ]
38
+ else
39
+ first
40
+ end
41
+ end
42
+ }
43
+ / '..' s string s
44
+ {
45
+ def value
46
+ [ nil, string.value ]
47
+ end
48
+ }
49
+ end
50
+
51
+ rule literal
52
+ ( boolean_literal
53
+ / string
54
+ / number
55
+ ) s
56
+ {
57
+ def value
58
+ elements[0].value
59
+ end
60
+ }
61
+ end
62
+
63
+ rule boolean_literal
64
+ ( true { def value; true; end }
65
+ / false { def value; false; end }
66
+ ) !alphanumeric
67
+ {
68
+ def value; elements[0].value end
69
+ }
70
+ end
71
+
72
+ rule string
73
+ "'" (string_char)* "'"
74
+ {
75
+ def value
76
+ text_value
77
+ eval(text_value.sub(/\A'(.*)'\Z/,'"\1"'))
78
+ end
79
+ }
80
+ end
81
+
82
+ rule number
83
+ ( real /
84
+ fractional_real /
85
+ hexnumber /
86
+ octalnumber
87
+ ) !alphanumeric
88
+ {
89
+ def value
90
+ eval(text_value)
91
+ end
92
+ }
93
+ end
94
+
95
+ # All purely lexical rules from here down, no-one looks at the structure, just the text_value:
96
+
97
+ rule string_char
98
+ ( '\\' [befntr\\']
99
+ / '\\' [0-7] [0-7] [0-7]
100
+ / '\\0'
101
+ / '\\x' [0-9A-Fa-f] [0-9A-Fa-f]
102
+ / '\\u' [0-9A-Fa-f] [0-9A-Fa-f] [0-9A-Fa-f] [0-9A-Fa-f]
103
+ / (![\'\\\0-\x07\x0A-\x1F] .)
104
+ )
105
+ end
106
+
107
+ rule real
108
+ [-+]? [1-9] [0-9]* fraction? exponent?
109
+ end
110
+
111
+ rule fractional_real
112
+ [-+]? '0' fraction exponent?
113
+ end
114
+
115
+ rule fraction
116
+ '.' [0-9]+
117
+ end
118
+
119
+ rule exponent
120
+ ( [Ee] '-'? [0-9]+ )
121
+ end
122
+
123
+ rule hexnumber
124
+ '0x' [0-9A-Fa-f]+
125
+ end
126
+
127
+ rule octalnumber
128
+ '0' [0-7]*
129
+ end
130
+
131
+ rule mul_op
132
+ '/' / '%' / '*'
133
+ end
134
+
135
+ rule id
136
+ alpha alphanumeric*
137
+ end
138
+
139
+ rule alpha
140
+ [A-Za-z_]
141
+ end
142
+
143
+ rule alphanumeric
144
+ alpha / [0-9]
145
+ end
146
+
147
+ rule s # Optional space
148
+ S?
149
+ end
150
+
151
+ rule S # Mandatory space
152
+ (white / comment_to_eol / comment_c_style)+
153
+ end
154
+
155
+ rule white
156
+ [ \t\n\r]+
157
+ end
158
+
159
+ rule comment_to_eol
160
+ '//' (!"\n" .)+
161
+ end
162
+
163
+ rule comment_c_style
164
+ '/*' (!'*/' . )* '*/'
165
+ end
166
+
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,6 @@
1
+ task :default do
2
+ pattern = File.dirname(__FILE__) + '**/*.treetop'
3
+ files = Dir[pattern]
4
+ # Hopefully this quoting will work where there are spaces in filenames, and even maybe on Windows?
5
+ exec "tt '#{files*"' '"}'"
6
+ end
@@ -0,0 +1,88 @@
1
+ #
2
+ # ActiveFacts CQL parser and loader.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'rubygems'
6
+ require 'treetop'
7
+
8
+ # These are Treetop files, which it will compile on the fly if precompiled ones aren't found:
9
+ require 'activefacts/cql/LexicalRules'
10
+ require 'activefacts/cql/Language/English'
11
+ require 'activefacts/cql/Expressions'
12
+ require 'activefacts/cql/Concepts'
13
+ require 'activefacts/cql/DataTypes'
14
+ require 'activefacts/cql/FactTypes'
15
+ require 'activefacts/cql/CQLParser'
16
+
17
+ module ActiveFacts
18
+ # Extend the generated parser:
19
+ class CQLParser
20
+ include ActiveFacts
21
+
22
+ # Repeatedly parse rule_name until all input is consumed,
23
+ # returning an array of syntax trees for each definition.
24
+ def parse_all(input, rule_name = nil, &block)
25
+ self.root = rule_name if rule_name
26
+
27
+ @index = 0 # Byte offset to start next parse
28
+ self.consume_all_input = false
29
+ results = []
30
+ begin
31
+ node = parse(input, :index => @index)
32
+ return nil unless node
33
+ node = block.call(node) if block
34
+ results << node if node
35
+ end until self.index == @input_length
36
+ results
37
+ end
38
+
39
+ def definition(node)
40
+ name, ast = *node.value
41
+ kind, *value = *ast
42
+
43
+ begin
44
+ debug "CQL: Processing definition #{[kind, name].compact*" "}" do
45
+ case kind
46
+ when :vocabulary
47
+ [kind, name]
48
+ when :data_type
49
+ data_type(name, value)
50
+ when :entity_type
51
+ supertypes = value.shift
52
+ entity_type(name, supertypes, value)
53
+ when :fact_type
54
+ f = fact_type(name, value)
55
+ when :constraint
56
+ ast
57
+ end
58
+ end
59
+ end
60
+ rescue => e
61
+ raise "in #{kind.to_s.camelcase(true)} definition, #{e.message}:\n\t#{node.text_value}"
62
+ end
63
+
64
+ def data_type(name, value)
65
+ # REVISIT: Massage/check data type here?
66
+ [:data_type, name, *value]
67
+ end
68
+
69
+ def entity_type(name, supertypes, value)
70
+ #print "entity_type parameters for #{name}: "; p value
71
+ identification, clauses = *value
72
+ clauses ||= []
73
+
74
+ # raise "Entity type clauses must all be fact types" if clauses.detect{|c| c[0] != :fact_clause }
75
+
76
+ [:entity_type, name, supertypes, identification, clauses]
77
+ end
78
+
79
+ def fact_type(name, value)
80
+ defined_readings, *clauses = value
81
+
82
+ [:fact_type, name, defined_readings, clauses]
83
+ end
84
+
85
+ end
86
+
87
+ Polyglot.register('cql', CQLParser)
88
+ end
@@ -0,0 +1,87 @@
1
+ #
2
+ # Generate text output for ActiveFacts vocabularies.
3
+ #
4
+ # Copyright (c) 2007 Clifford Heath. Read the LICENSE file.
5
+ # Author: Clifford Heath.
6
+ #
7
+ require 'activefacts/persistence'
8
+
9
+ module ActiveFacts
10
+ module Generate
11
+ class ABSORPTION
12
+ include Metamodel
13
+
14
+ def initialize(vocabulary, *options)
15
+ @vocabulary = vocabulary
16
+ @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::Constellation === @vocabulary
17
+ @no_columns = options.include? "no_columns"
18
+ @dependent = options.include? "dependent"
19
+ @paths = options.include? "paths"
20
+ @no_identifier = options.include? "no_identifier"
21
+ end
22
+
23
+ def generate(out = $>)
24
+ no_absorption = 0
25
+ single_absorption_vts = 0
26
+ single_absorption_ets = 0
27
+ multi_absorption_vts = 0
28
+ multi_absorption_ets = 0
29
+ @vocabulary.tables
30
+ @vocabulary.all_feature.sort_by{|c| c.name}.each do |o|
31
+ # Don't dump imported (base) ValueTypes:
32
+ next if ValueType === o && !o.supertype
33
+ show(o)
34
+
35
+ case o.absorption_paths.size
36
+ when 0; no_absorption += 1
37
+ when 1
38
+ if ValueType === o
39
+ single_absorption_vts += 1
40
+ else
41
+ single_absorption_ets += 1
42
+ end
43
+ else
44
+ if ValueType === o
45
+ multi_absorption_vts += 1
46
+ else
47
+ multi_absorption_ets += 1
48
+ end
49
+ end
50
+
51
+ end
52
+ puts "#{no_absorption} concepts have no absorption paths, #{single_absorption_vts}/#{single_absorption_ets} value/entity types have only one path, #{multi_absorption_vts}/#{multi_absorption_ets} have more than one"
53
+ end
54
+
55
+ def show concept
56
+ return unless concept.independent || @dependent
57
+
58
+ print "#{concept.name}"
59
+ print " (#{concept.tentative ? "tentatively " : ""}#{concept.independent ? "in" : ""}dependent)" if @dependent
60
+
61
+ if !@no_identifier && concept.is_a?(EntityType)
62
+ print " is identified by:\n\t#{
63
+ concept.absorbed_reference_roles.all_role_ref.map { |rr| rr.column_name(".") } * ",\n\t"
64
+ }"
65
+ end
66
+ print "\n"
67
+
68
+ unless @no_columns
69
+ puts "#{ concept.absorbed_roles.all_role_ref.map do |role_ref|
70
+ "\t#{role_ref.column_name(".")}\n"
71
+ end*"" }"
72
+ end
73
+
74
+ if (@paths)
75
+ ap = concept.absorption_paths
76
+ puts "#{ ap.map {|role|
77
+ prr = role.preferred_reference.describe
78
+ player = role.fact_type.entity_type == concept ? role.concept : (role.fact_type.all_role-[role])[0].concept
79
+ "\tcan absorb #{prr != role.concept.name ? "(via #{prr}) " : "" }into #{player.name}\n"
80
+ }*"" }"
81
+ end
82
+
83
+ end
84
+ end
85
+ end
86
+ end
87
+
@@ -0,0 +1,441 @@
1
+ #
2
+ # Generate CQL from an ActiveFacts vocabulary.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ require 'activefacts/vocabulary'
6
+ require 'activefacts/generate/ordered'
7
+
8
+ module ActiveFacts
9
+ module Generate #:nodoc:
10
+ class CQL < OrderedDumper
11
+ include Metamodel
12
+
13
+ def vocabulary_start(vocabulary)
14
+ puts "vocabulary #{vocabulary.name};\n\n"
15
+ end
16
+
17
+ def vocabulary_end
18
+ end
19
+
20
+ def value_type_banner
21
+ puts "/*\n * Value Types\n */"
22
+ end
23
+
24
+ def value_type_end
25
+ puts "\n"
26
+ end
27
+
28
+ def value_type_dump(o)
29
+ return unless o.supertype # An imported type
30
+ if o.name == o.supertype.name
31
+ # In ActiveFacts, parameterising a ValueType will create a new datatype
32
+ # throw Can't handle parameterized value type of same name as its datatype" if ...
33
+ end
34
+
35
+ parameters =
36
+ [ o.length != 0 || o.scale != 0 ? o.length : nil,
37
+ o.scale != 0 ? o.scale : nil
38
+ ].compact
39
+ parameters = parameters.length > 0 ? "("+parameters.join(",")+")" : "()"
40
+
41
+ #" restricted to {#{(allowed_values.map{|r| r.inspect}*", ").gsub('"',"'")}}")
42
+
43
+ puts "#{o.name} is defined as #{o.supertype.name}#{ parameters }#{
44
+ o.value_restriction ? " restricted to {#{
45
+ o.value_restriction.all_allowed_range.map{|ar|
46
+ # REVISIT: Need to display as string or numeric according to type here...
47
+ min = ar.value_range.minimum_bound
48
+ max = ar.value_range.maximum_bound
49
+
50
+ (min ? min.value : "") +
51
+ (min.value != (max&&max.value) ? (".." + (max ? max.value : "")) : "")
52
+ }*", "
53
+ }}" : ""
54
+ };"
55
+ end
56
+
57
+ def append_ring_to_reading(reading, ring)
58
+ reading << " [#{(ring.ring_type.scan(/[A-Z][a-z]*/)*", ").downcase}]"
59
+ end
60
+
61
+ def identified_by_roles_and_facts(entity_type, identifying_roles, identifying_facts, preferred_readings)
62
+ identifying_role_names = identifying_roles.map{|role|
63
+ preferred_role_ref = preferred_readings[role.fact_type].role_sequence.all_role_ref.detect{|reading_rr|
64
+ reading_rr.role == role
65
+ }
66
+ role_words = []
67
+ # REVISIT: Consider whether NOT to use the adjective if it's a prefix of the role_name
68
+
69
+ role_name = role.role_name
70
+ role_name = nil if role_name == ""
71
+ # debug "concept.name=#{preferred_role_ref.role.concept.name}, role_name=#{role_name.inspect}, preferred_role_name=#{preferred_role_ref.role.role_name.inspect}"
72
+
73
+ if (role.fact_type.all_role.size == 1)
74
+ # REVISIT: Guard against unary reading containing the illegal words "and" and "where".
75
+ role.fact_type.default_reading # Need whole reading for a unary.
76
+ elsif (role_name)
77
+ role_name
78
+ else
79
+ role_words << preferred_role_ref.leading_adjective if preferred_role_ref.leading_adjective != ""
80
+ role_words << preferred_role_ref.role.concept.name
81
+ role_words << preferred_role_ref.trailing_adjective if preferred_role_ref.trailing_adjective != ""
82
+ role_words.compact*"-"
83
+ end
84
+ }
85
+
86
+ # REVISIT: Consider emitting extra fact types here, instead of in entity_type_dump?
87
+ # Just beware that readings having the same players will be considered to be of the same fact type, even if they're not.
88
+
89
+ # Detect standard reference-mode scenarios
90
+ ft = identifying_facts[0]
91
+ fact_constraints = nil
92
+ if identifying_facts.size == 1 and
93
+ entity_role = ft.all_role[n = (ft.all_role[0].concept == entity_type ? 0 : 1)] and
94
+ value_role = ft.all_role[1-n] and
95
+ value_name = value_role.concept.name and
96
+ residual = value_name.gsub(%r{#{entity_role.concept.name}},'') and
97
+ residual != '' and
98
+ residual != value_name
99
+
100
+ # The EntityType is identified by its association with a single ValueType
101
+ # whose name is an extension (the residual) of the EntityType's name.
102
+
103
+ # Detect standard reference-mode readings:
104
+ forward_reading = reverse_reading = nil
105
+ ft.all_reading.each do |reading|
106
+ if reading.reading_text =~ /^\{(\d)\} has \{\d\}$/
107
+ if reading.role_sequence.all_role_ref[$1.to_i].role == entity_role
108
+ forward_reading = reading
109
+ else
110
+ reverse_reading = reading
111
+ end
112
+ elsif reading.reading_text =~ /^\{(\d)\} is of \{\d\}$/
113
+ if reading.role_sequence.all_role_ref[$1.to_i].role == value_role
114
+ reverse_reading = reading
115
+ else
116
+ forward_reading = reading
117
+ end
118
+ end
119
+ end
120
+
121
+ debug :mode, "------------------- Didn't find standard forward reading" unless forward_reading
122
+ debug :mode, "------------------- Didn't find standard reverse reading" unless reverse_reading
123
+
124
+ # If we didn't find at least one of the standard readings, don't use a refmode:
125
+ if (forward_reading || reverse_reading)
126
+ # Elide the constraints that would have been emitted on those readings.
127
+ # If there is a UC that's not in the standard form for a reference mode,
128
+ # we have to emit the standard reading anyhow.
129
+ fact_constraints = @presence_constraints_by_fact[ft]
130
+ fact_constraints.each do |pc|
131
+ if (pc.role_sequence.all_role_ref.size == 1 and pc.max_frequency == 1)
132
+ # It's a uniqueness constraint, and will be regenerated
133
+ @constraints_used[pc] = true
134
+ end
135
+ end
136
+
137
+ @fact_types_dumped[ft] = true
138
+
139
+ # Figure out whether any non-standard readings exist:
140
+ other_readings = ft.all_reading - [forward_reading] - [reverse_reading]
141
+ debug :mode, "--- other_readings.size now = #{other_readings.size}" if other_readings.size > 0
142
+
143
+ fact_text = other_readings.map do |reading|
144
+ expanded_reading(reading, fact_constraints, true)
145
+ end*",\n\t"
146
+ return " identified by its #{residual}" +
147
+ (fact_text != "" ? " where\n\t" + fact_text : "")
148
+ end
149
+ end
150
+
151
+ identifying_facts.each{|f| @fact_types_dumped[f] = true }
152
+ @identifying_fact_text =
153
+ identifying_facts.map{|f|
154
+ fact_readings_with_constraints(f, fact_constraints)
155
+ }.flatten*",\n\t"
156
+
157
+ " identified by #{ identifying_role_names*" and " }" +
158
+ " where\n\t"+@identifying_fact_text
159
+ end
160
+
161
+ def entity_type_banner
162
+ puts "/*\n * Entity Types\n */"
163
+ end
164
+
165
+ def entity_type_group_end
166
+ puts "\n"
167
+ end
168
+
169
+ def fact_readings(fact_type)
170
+ constrained_fact_readings = fact_readings_with_constraints(fact_type)
171
+ constrained_fact_readings*",\n\t"
172
+ end
173
+
174
+ def subtype_dump(o, supertypes, pi)
175
+ print "#{o.name} is a kind of #{ o.supertypes.map(&:name)*", " }"
176
+ if pi
177
+ print identified_by(o, pi)
178
+ end
179
+ # If there's a preferred_identifier for this subtype, identifying readings were emitted
180
+ print((pi ? "," : " where") + "\n\t" + fact_readings(o.fact_type)) if o.fact_type
181
+ puts ";\n"
182
+ end
183
+
184
+ def non_subtype_dump(o, pi)
185
+ print "#{o.name} is" + identified_by(o, pi)
186
+ print(" where\n\t"+ fact_readings(o.fact_type)) if o.fact_type
187
+ puts ";\n"
188
+ end
189
+
190
+ def fact_type_dump(fact_type, name)
191
+
192
+ @identifying_fact_text = nil
193
+ if (o = fact_type.entity_type)
194
+ print "#{o.name} is"
195
+ if !o.all_type_inheritance_by_subtype.empty?
196
+ print " a kind of #{ o.supertypes.map(&:name)*", " }"
197
+ end
198
+
199
+ # Alternate identification of objectified fact type?
200
+ primary_supertype = o.supertypes[0]
201
+ pi = fact_type.entity_type.preferred_identifier
202
+ if pi && primary_supertype && primary_supertype.preferred_identifier != pi
203
+ print identified_by(o, pi)
204
+ print ";\n"
205
+ end
206
+ end
207
+
208
+ unless @identifying_fact_text
209
+ print " where\n\t" if o
210
+ puts(fact_readings(fact_type)+";")
211
+ end
212
+ end
213
+
214
+ def fact_type_banner
215
+ puts "/*\n * Fact Types\n */"
216
+ end
217
+
218
+ def fact_type_end
219
+ puts "\n"
220
+ end
221
+
222
+ def constraint_banner
223
+ puts "/*\n * Constraints:"
224
+ puts " */"
225
+ end
226
+
227
+ def constraint_end
228
+ end
229
+
230
+ # Of the players of a set of roles, return the one that's a subclass of (or same as) all others, else nil
231
+ def roleplayer_subclass(roles)
232
+ roles[1..-1].inject(roles[0].concept){|subclass, role|
233
+ next nil unless subclass and EntityType === role.concept
234
+ role.concept.supertypes_transitive.include?(subclass) ? role.concept : nil
235
+ }
236
+ end
237
+
238
+ def dump_presence_constraint(c)
239
+ roles = c.role_sequence.all_role_ref.map{|rr| rr.role }
240
+
241
+ # REVISIT: If only one role is covered and it's mandatory >=1 constraint, use SOME/THAT form:
242
+ # each Bug SOME Tester logged THAT Bug;
243
+ players = c.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}.uniq
244
+
245
+ fact_types = c.role_sequence.all_role_ref.map{|rr| rr.role.fact_type}.uniq
246
+ puts \
247
+ "each #{players.size > 1 ? "combination " : ""}#{players*", "} occurs #{c.frequency} time in\n\t"+
248
+ "#{fact_types.map{|ft| ft.default_reading([], nil)}*",\n\t"}" +
249
+ ";"
250
+
251
+ =begin
252
+ # More than one fact type involved, an external constraint.
253
+ fact_type = rr.role.fact_type
254
+ # or all facts are binary and the counterparts of the roles are.
255
+ puts "// REVISIT: " +
256
+ if (player = roleplayer_subclass(roles))
257
+ "#{player.name} must play #{c.frequency} of "
258
+ else
259
+ counterparts = roles.map{|r|
260
+ r.fact_type.all_role[r.fact_type.all_role[0] != r ? 0 : -1]
261
+ }
262
+ player = roleplayer_subclass(counterparts)
263
+ "#{c.frequency} #{player ? player.name : "UNKNOWN" } exists for each "
264
+ end +
265
+ "#{
266
+ c.role_sequence.all_role_ref.map{|rr|
267
+ "'#{rr.role.fact_type.default_reading([], nil)}'"
268
+ }*", "
269
+ }"
270
+ =end
271
+
272
+ =begin
273
+ puts \
274
+ "FOR each #{players*", "}" +
275
+ (c.role_sequence.all_role_ref.size > 1 ? " "+c.frequency+" of these holds" : "") + "\n\t"+
276
+ "#{c.role_sequence.all_role_ref.map{|rr|
277
+ role = rr.role
278
+ fact_type = role.fact_type
279
+ some_that = Array.new(fact_type.all_role.size, "some")
280
+ c.role_sequence.all_role_ref.each{|rr2|
281
+ next if rr2.role.fact_type != fact_type
282
+ some_that[fact_type.all_role.index(role)] = "that"
283
+ }
284
+ rr.role.fact_type.default_reading(some_that, nil)
285
+ }*",\n\t"}" +
286
+ ";"
287
+ =end
288
+ end
289
+
290
+ # Find the common supertype of these concepts.
291
+ # N.B. This will only work if all concepts are on the direct path to the deepest.
292
+ def common_supertype(concepts)
293
+ players_differ = false
294
+ common =
295
+ concepts[1..-1].inject(concepts[0]) do |supertype, concept|
296
+ if !supertype || concept == supertype
297
+ concept # Most common case
298
+ elsif concept.supertypes_transitive.include?(supertype)
299
+ players_differ = true
300
+ supertype
301
+ elsif supertype.supertypes_transitive.include?(concept)
302
+ players_differ = true
303
+ concept
304
+ else
305
+ return nil # No common supertype
306
+ end
307
+ end
308
+ return common, players_differ
309
+ end
310
+
311
+ def dump_set_constraint(c)
312
+ # REVISIT exclusion: every <player-list> must<?> either reading1, reading2, ...
313
+
314
+ # Each constraint involves two or more occurrences of one or more players.
315
+ # For each player, a subtype may be involved in the occurrences.
316
+ # Find the common supertype of each player.
317
+ scrs = c.all_set_comparison_roles
318
+ player_count = scrs[0].role_sequence.all_role_ref.size
319
+ role_seq_count = scrs.size
320
+
321
+ #raise "Can't verbalise constraint over many players and facts" if player_count > 1 and role_seq_count > 1
322
+
323
+ # puts "#{c.class.basename} has #{role_seq_count} scr's: #{scrs.map{|scr| "("+scr.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*", "+")"}*", "}"
324
+
325
+ players_differ = [] # Record which players are also played by subclasses
326
+ players = (0...player_count).map do |pi|
327
+ # Find the common supertype of the players of the pi'th role in each sequence
328
+ concepts = scrs.map{|r| r.role_sequence.all_role_ref[pi].role.concept }
329
+ player, players_differ[pi] = common_supertype(concepts)
330
+ raise "Role sequences of #{c.class.basename} must have concepts matching #{concept.name} in position #{pi}" unless player
331
+ player
332
+ end
333
+ #puts "#{c.class.basename} has players #{players.map{|p| p.name}*", "}"
334
+
335
+ if (SetEqualityConstraint === c)
336
+ # REVISIT: Need a proper approach to some/that and adjective disambiguation:
337
+ puts \
338
+ scrs.map{|scr|
339
+ scr.role_sequence.all_role_ref.map{|rr| rr.role.fact_type.default_reading([], nil) }*" and "
340
+ } * "\n\tif and only if\n\t" + ";"
341
+ return
342
+ end
343
+
344
+ mode = c.is_mandatory ? "exactly one" : "at most one"
345
+ puts "for each #{players.map{|p| p.name}*", "} #{mode} of these holds:\n\t" +
346
+ (scrs.map do |scr|
347
+ constrained_roles = scr.role_sequence.all_role_ref.map{|rr| rr.role }
348
+ fact_types = constrained_roles.map{|r| r.fact_type }.uniq
349
+
350
+ fact_types.map do |fact_type|
351
+ # REVISIT: future: Use "THAT" and "SOME" only when:
352
+ # - the role player occurs twice in the reading, or
353
+ # - is a subclass of the constrained concept, or
354
+ reading = fact_type.preferred_reading
355
+ expand_constrained(reading, constrained_roles, players, players_differ)
356
+ end * " and "
357
+
358
+ end*",\n\t"
359
+ )+';'
360
+ end
361
+
362
+ # Expand this reading using (in)definite articles where needed
363
+ # Handle any roles in constrained_roles specially.
364
+ def expand_constrained(reading, constrained_roles, players, players_differ)
365
+ frequency_constraints = reading.role_sequence.all_role_ref.map {|role_ref|
366
+ i = constrained_roles.index(role_ref.role)
367
+ if !i
368
+ [ "some", role_ref.role.concept.name]
369
+ elsif players_differ[i]
370
+ [ "that", players[i].name ] # Make sure to use the superclass name
371
+ else
372
+ if reading.fact_type.all_role.select{|r| r.concept == role_ref.role.concept }.size > 1
373
+ [ "that", role_ref.role.concept.name ]
374
+ else
375
+ [ "some", role_ref.role.concept.name ]
376
+ end
377
+ end
378
+ }
379
+ frequency_constraints = [] unless frequency_constraints.detect{|fc| fc[0] != "some" }
380
+
381
+ #$stderr.puts "fact_type roles (#{fact_type.all_role.map{|r| r.concept.name}*","}) default_reading '#{fact_type.preferred_reading.reading_text}' roles (#{fact_type.preferred_reading.role_sequence.all_role_ref.map{|rr| rr.role.concept.name}*","}) #{frequency_constraints.inspect}"
382
+
383
+ # REVISIT: Make sure that we refer to the constrained players by their common supertype
384
+
385
+ reading.expand(frequency_constraints, nil)
386
+ end
387
+
388
+ def dump_subset_constraint(c)
389
+ # If the role players are identical and not duplicated, we can simply say "reading1 only if reading2"
390
+ subset_roles = c.subset_role_sequence.all_role_ref.map{|rr| rr.role}
391
+ superset_roles = c.superset_role_sequence.all_role_ref.map{|rr| rr.role}
392
+
393
+ subset_players = subset_roles.map(&:concept)
394
+ superset_players = superset_roles.map(&:concept)
395
+
396
+ subset_fact_types = c.subset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
397
+ superset_fact_types = c.superset_role_sequence.all_role_ref.map{|rr| rr.role.fact_type }.uniq
398
+
399
+ # We need to ensure that if the player of any constrained role also exists
400
+ # as the player of a role that's not a constrained role, there are different
401
+ # adjectives or other qualifiers qualifier applied to distinguish that role.
402
+ fact_type_roles = (subset_fact_types+superset_fact_types).map{|ft| ft.all_role }.flatten
403
+ non_constrained_roles = fact_type_roles - subset_roles - superset_roles
404
+ if (r = non_constrained_roles.detect{|r| (subset_roles+superset_roles).include?(r) })
405
+ # REVISIT: Find a way to deal with this problem, should it arise.
406
+
407
+ # It would help, but not entirely fix it, to use SOME/THAT to identify the constrained roles.
408
+ # See ServiceDirector's DataStore<->Client fact types for example
409
+ # Use SOME on the subset, THAT on the superset.
410
+ raise "Critical ambiguity, #{r.concept.name} occurs both constrained and unconstrained in #{c.name}"
411
+ end
412
+
413
+ puts \
414
+ "#{subset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
415
+ "\n\tonly if " +
416
+ "#{superset_fact_types.map{|ft| ft.default_reading([], nil)}*" and "}" +
417
+ ";"
418
+ end
419
+
420
+ def dump_ring_constraint(c)
421
+ # At present, no ring constraint can be missed to be handled in this pass
422
+ puts "// #{c.ring_type} ring over #{c.role.fact_type.default_reading([], nil)}"
423
+ end
424
+
425
+ def constraint_dump(c)
426
+ case c
427
+ when PresenceConstraint
428
+ dump_presence_constraint(c)
429
+ when RingConstraint
430
+ dump_ring_constraint(c)
431
+ when SetComparisonConstraint # includes SetExclusionConstraint, SetEqualityConstraint
432
+ dump_set_constraint(c)
433
+ when SubsetConstraint
434
+ dump_subset_constraint(c)
435
+ else
436
+ "#{c.class.basename} #{c.name}: unhandled constraint type"
437
+ end
438
+ end
439
+ end
440
+ end
441
+ end