activefacts 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|