activefacts 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +4 -0
- data/Manifest.txt +83 -0
- data/README.rdoc +81 -0
- data/Rakefile +41 -0
- data/bin/afgen +46 -0
- data/bin/cql +52 -0
- data/examples/CQL/Address.cql +46 -0
- data/examples/CQL/Blog.cql +54 -0
- data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
- data/examples/CQL/Death.cql +16 -0
- data/examples/CQL/Genealogy.cql +95 -0
- data/examples/CQL/Marriage.cql +18 -0
- data/examples/CQL/Metamodel.cql +238 -0
- data/examples/CQL/MultiInheritance.cql +19 -0
- data/examples/CQL/OilSupply.cql +47 -0
- data/examples/CQL/Orienteering.cql +108 -0
- data/examples/CQL/PersonPlaysGame.cql +17 -0
- data/examples/CQL/SchoolActivities.cql +31 -0
- data/examples/CQL/SimplestUnary.cql +12 -0
- data/examples/CQL/SubtypePI.cql +32 -0
- data/examples/CQL/Warehousing.cql +99 -0
- data/examples/CQL/WindowInRoomInBldg.cql +22 -0
- data/lib/activefacts.rb +10 -0
- data/lib/activefacts/api.rb +25 -0
- data/lib/activefacts/api/concept.rb +384 -0
- data/lib/activefacts/api/constellation.rb +106 -0
- data/lib/activefacts/api/entity.rb +239 -0
- data/lib/activefacts/api/instance.rb +54 -0
- data/lib/activefacts/api/numeric.rb +158 -0
- data/lib/activefacts/api/role.rb +94 -0
- data/lib/activefacts/api/standard_types.rb +67 -0
- data/lib/activefacts/api/support.rb +59 -0
- data/lib/activefacts/api/value.rb +122 -0
- data/lib/activefacts/api/vocabulary.rb +120 -0
- data/lib/activefacts/cql.rb +31 -0
- data/lib/activefacts/cql/CQLParser.treetop +104 -0
- data/lib/activefacts/cql/Concepts.treetop +112 -0
- data/lib/activefacts/cql/DataTypes.treetop +66 -0
- data/lib/activefacts/cql/Expressions.treetop +113 -0
- data/lib/activefacts/cql/FactTypes.treetop +185 -0
- data/lib/activefacts/cql/Language/English.treetop +92 -0
- data/lib/activefacts/cql/LexicalRules.treetop +169 -0
- data/lib/activefacts/cql/Rakefile +6 -0
- data/lib/activefacts/cql/parser.rb +88 -0
- data/lib/activefacts/generate/absorption.rb +87 -0
- data/lib/activefacts/generate/cql.rb +441 -0
- data/lib/activefacts/generate/cql/html.rb +397 -0
- data/lib/activefacts/generate/null.rb +19 -0
- data/lib/activefacts/generate/ordered.rb +557 -0
- data/lib/activefacts/generate/ruby.rb +326 -0
- data/lib/activefacts/generate/sql/server.rb +164 -0
- data/lib/activefacts/generate/text.rb +21 -0
- data/lib/activefacts/input/cql.rb +1268 -0
- data/lib/activefacts/input/orm.rb +926 -0
- data/lib/activefacts/persistence.rb +1 -0
- data/lib/activefacts/persistence/composition.rb +653 -0
- data/lib/activefacts/support.rb +51 -0
- data/lib/activefacts/version.rb +3 -0
- data/lib/activefacts/vocabulary.rb +6 -0
- data/lib/activefacts/vocabulary/extensions.rb +343 -0
- data/lib/activefacts/vocabulary/metamodel.rb +303 -0
- data/script/txt2html +71 -0
- data/spec/absorption_spec.rb +95 -0
- data/spec/api/autocounter.rb +82 -0
- data/spec/api/constellation.rb +130 -0
- data/spec/api/entity_type.rb +101 -0
- data/spec/api/instance.rb +428 -0
- data/spec/api/roles.rb +122 -0
- data/spec/api/value_type.rb +112 -0
- data/spec/api_spec.rb +14 -0
- data/spec/cql_cql_spec.rb +58 -0
- data/spec/cql_parse_spec.rb +31 -0
- data/spec/cql_ruby_spec.rb +60 -0
- data/spec/cql_sql_spec.rb +54 -0
- data/spec/cql_symbol_tables_spec.rb +259 -0
- data/spec/cql_unit_spec.rb +336 -0
- data/spec/cqldump_spec.rb +169 -0
- data/spec/norma_cql_spec.rb +48 -0
- data/spec/norma_ruby_spec.rb +50 -0
- data/spec/norma_sql_spec.rb +45 -0
- data/spec/norma_tables_spec.rb +94 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +173 -0
@@ -0,0 +1,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,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
|