activefacts 0.8.6 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/Manifest.txt +33 -2
  2. data/README.rdoc +30 -36
  3. data/Rakefile +16 -20
  4. data/bin/afgen +17 -11
  5. data/bin/cql +313 -36
  6. data/download.html +43 -19
  7. data/examples/CQL/Address.cql +15 -15
  8. data/examples/CQL/Blog.cql +8 -8
  9. data/examples/CQL/CompanyDirectorEmployee.cql +6 -5
  10. data/examples/CQL/Death.cql +3 -3
  11. data/examples/CQL/Diplomacy.cql +48 -0
  12. data/examples/CQL/Genealogy.cql +41 -41
  13. data/examples/CQL/Insurance.cql +311 -0
  14. data/examples/CQL/JoinEquality.cql +35 -0
  15. data/examples/CQL/Marriage.cql +1 -1
  16. data/examples/CQL/Metamodel.cql +290 -185
  17. data/examples/CQL/MetamodelNext.cql +420 -0
  18. data/examples/CQL/Monogamy.cql +24 -0
  19. data/examples/CQL/MonthInSeason.cql +27 -0
  20. data/examples/CQL/Moon.cql +23 -0
  21. data/examples/CQL/MultiInheritance.cql +4 -4
  22. data/examples/CQL/NonRoleId.cql +14 -0
  23. data/examples/CQL/OddIdentifier.cql +18 -0
  24. data/examples/CQL/OilSupply.cql +24 -24
  25. data/examples/CQL/OneToOnes.cql +17 -0
  26. data/examples/CQL/Orienteering.cql +55 -55
  27. data/examples/CQL/OrienteeringER.cql +58 -0
  28. data/examples/CQL/PersonPlaysGame.cql +2 -2
  29. data/examples/CQL/RedundantDependency.cql +34 -0
  30. data/examples/CQL/SchoolActivities.cql +5 -5
  31. data/examples/CQL/SeparateSubtype.cql +28 -0
  32. data/examples/CQL/ServiceDirector.cql +283 -0
  33. data/examples/CQL/SimplestUnary.cql +2 -2
  34. data/examples/CQL/SubtypePI.cql +11 -11
  35. data/examples/CQL/Supervision.cql +38 -0
  36. data/examples/CQL/Tests.Test5.Load.cql +38 -0
  37. data/examples/CQL/WaiterTips.cql +33 -0
  38. data/examples/CQL/Warehousing.cql +55 -53
  39. data/examples/CQL/WindowInRoomInBldg.cql +9 -9
  40. data/examples/CQL/unit.cql +433 -544
  41. data/examples/index.html +314 -170
  42. data/examples/intro.html +6 -176
  43. data/examples/local.css +8 -4
  44. data/index.html +40 -25
  45. data/lib/activefacts/api/concept.rb +2 -2
  46. data/lib/activefacts/api/constellation.rb +4 -4
  47. data/lib/activefacts/api/instance.rb +2 -2
  48. data/lib/activefacts/api/instance_index.rb +4 -0
  49. data/lib/activefacts/api/numeric.rb +3 -1
  50. data/lib/activefacts/api/role.rb +1 -1
  51. data/lib/activefacts/api/standard_types.rb +23 -16
  52. data/lib/activefacts/api/support.rb +3 -1
  53. data/lib/activefacts/api/vocabulary.rb +4 -0
  54. data/lib/activefacts/cql/CQLParser.treetop +87 -39
  55. data/lib/activefacts/cql/Concepts.treetop +95 -69
  56. data/lib/activefacts/cql/Context.treetop +11 -2
  57. data/lib/activefacts/cql/Expressions.treetop +23 -59
  58. data/lib/activefacts/cql/FactTypes.treetop +141 -95
  59. data/lib/activefacts/cql/Language/English.treetop +33 -21
  60. data/lib/activefacts/cql/LexicalRules.treetop +6 -1
  61. data/lib/activefacts/cql/Terms.treetop +75 -26
  62. data/lib/activefacts/cql/ValueTypes.treetop +52 -54
  63. data/lib/activefacts/cql/compiler.rb +46 -1691
  64. data/lib/activefacts/cql/compiler/constraint.rb +602 -0
  65. data/lib/activefacts/cql/compiler/entity_type.rb +425 -0
  66. data/lib/activefacts/cql/compiler/fact.rb +300 -0
  67. data/lib/activefacts/cql/compiler/fact_type.rb +230 -0
  68. data/lib/activefacts/cql/compiler/reading.rb +832 -0
  69. data/lib/activefacts/cql/compiler/shared.rb +109 -0
  70. data/lib/activefacts/cql/compiler/value_type.rb +104 -0
  71. data/lib/activefacts/cql/parser.rb +132 -81
  72. data/lib/activefacts/generate/cql.rb +397 -274
  73. data/lib/activefacts/generate/oo.rb +13 -12
  74. data/lib/activefacts/generate/ordered.rb +107 -117
  75. data/lib/activefacts/generate/ruby.rb +34 -38
  76. data/lib/activefacts/generate/sql/mysql.rb +62 -45
  77. data/lib/activefacts/generate/sql/server.rb +59 -42
  78. data/lib/activefacts/input/cql.rb +6 -3
  79. data/lib/activefacts/input/orm.rb +991 -557
  80. data/lib/activefacts/persistence/columns.rb +16 -12
  81. data/lib/activefacts/persistence/foreignkey.rb +7 -4
  82. data/lib/activefacts/persistence/index.rb +3 -4
  83. data/lib/activefacts/persistence/reference.rb +5 -2
  84. data/lib/activefacts/support.rb +20 -14
  85. data/lib/activefacts/version.rb +1 -1
  86. data/lib/activefacts/vocabulary.rb +1 -0
  87. data/lib/activefacts/vocabulary/extensions.rb +328 -44
  88. data/lib/activefacts/vocabulary/metamodel.rb +145 -20
  89. data/lib/activefacts/vocabulary/verbaliser.rb +621 -0
  90. data/spec/absorption_spec.rb +4 -4
  91. data/spec/api/value_type.rb +1 -1
  92. data/spec/cql/context_spec.rb +45 -22
  93. data/spec/cql/deontic_spec.rb +88 -0
  94. data/spec/cql/matching_spec.rb +517 -0
  95. data/spec/cql/samples_spec.rb +88 -31
  96. data/spec/cql/unit_spec.rb +58 -37
  97. data/spec/cql_cql_spec.rb +12 -7
  98. data/spec/cql_mysql_spec.rb +3 -7
  99. data/spec/cql_parse_spec.rb +0 -4
  100. data/spec/cql_ruby_spec.rb +1 -4
  101. data/spec/cql_sql_spec.rb +5 -18
  102. data/spec/cql_symbol_tables_spec.rb +3 -0
  103. data/spec/cqldump_spec.rb +0 -2
  104. data/spec/helpers/array_matcher.rb +35 -0
  105. data/spec/helpers/ctrl_c_support.rb +52 -0
  106. data/spec/helpers/diff_matcher.rb +38 -0
  107. data/spec/helpers/file_matcher.rb +5 -3
  108. data/spec/helpers/string_matcher.rb +39 -0
  109. data/spec/helpers/test_parser.rb +13 -0
  110. data/spec/norma_cql_spec.rb +13 -5
  111. data/spec/norma_ruby_spec.rb +11 -3
  112. data/spec/{absorption_ruby_spec.rb → norma_ruby_sql_spec.rb} +37 -32
  113. data/spec/norma_sql_spec.rb +11 -5
  114. data/spec/norma_tables_spec.rb +33 -29
  115. data/spec/spec_helper.rb +4 -1
  116. data/status.html +92 -23
  117. metadata +102 -36
  118. data/lib/activefacts/generate/cql/html.rb +0 -403
@@ -0,0 +1,109 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ # In a declaration, a Binding has one or more RoleRef's.
6
+ # A Binding is for a single Concept, normally related to just one Role,
7
+ # and the references (RoleRefs) to it will normally be the concept name
8
+ # with the same adjectives (modulo loose binding),
9
+ # or a role name or subscript reference.
10
+ #
11
+ # In some situations a Binding will have some RoleRefs with the same adjectives,
12
+ # and one or more RoleRefs with no adjectives - this is called "loose binding".
13
+ class Binding
14
+ attr_reader :player # The Concept (object type)
15
+ attr_reader :refs # an array of the RoleRefs
16
+ attr_reader :role_name
17
+ attr_accessor :rebound_to # Loose binding may set this to another binding
18
+ attr_accessor :join_node
19
+
20
+ def initialize player, role_name = nil
21
+ @player = player
22
+ @role_name = role_name
23
+ @refs = []
24
+ end
25
+
26
+ def inspect
27
+ "#{@player.name}#{@role_name and @role_name.is_a?(Integer) ? " (#{@role_name})" : " (as #{@role_name})"}"
28
+ end
29
+
30
+ def key
31
+ "#{@player.name}#{@role_name && " (as #{@role_name})"}"
32
+ end
33
+
34
+ def <=>(other)
35
+ key <=> other.key
36
+ end
37
+ end
38
+
39
+ class CompilationContext
40
+ attr_accessor :allowed_forward_terms
41
+ attr_reader :bindings # The Bindings in this declaration
42
+ attr_reader :player_by_role_name
43
+
44
+ def initialize vocabulary
45
+ @vocabulary = vocabulary
46
+ @vocabulary_identifier = @vocabulary.identifying_role_values
47
+ @allowed_forward_terms = []
48
+ @bindings = {}
49
+ @player_by_role_name = {}
50
+ end
51
+
52
+ # Look up this concept by its name
53
+ def concept(name)
54
+ constellation = @vocabulary.constellation
55
+ player = constellation.Concept[[@vocabulary_identifier, name]]
56
+
57
+ # Bind to an existing role which has a role name (that's why we bind those first)
58
+ player ||= @player_by_role_name[name]
59
+
60
+ if !player && @allowed_forward_terms.include?(name)
61
+ player = constellation.EntityType(@vocabulary, name)
62
+ end
63
+
64
+ player
65
+ end
66
+ end
67
+
68
+ class Definition
69
+ attr_accessor :constellation, :vocabulary, :source
70
+ def compile
71
+ raise "#{self.class} should implement the compile method"
72
+ end
73
+ end
74
+
75
+ class Vocabulary < Definition
76
+ def initialize name
77
+ @name = name
78
+ end
79
+
80
+ def compile
81
+ @constellation.Vocabulary @name
82
+ end
83
+ end
84
+
85
+ class Import < Definition
86
+ def initialize name, alias_list
87
+ @name = name
88
+ @alias_list = alias_list
89
+ end
90
+ end
91
+
92
+ class Concept < Definition
93
+ attr_reader :name
94
+
95
+ def initialize name
96
+ @name = name
97
+ end
98
+ end
99
+
100
+ end
101
+ end
102
+ end
103
+
104
+ require 'activefacts/cql/compiler/value_type'
105
+ require 'activefacts/cql/compiler/entity_type'
106
+ require 'activefacts/cql/compiler/reading'
107
+ require 'activefacts/cql/compiler/fact_type'
108
+ require 'activefacts/cql/compiler/fact'
109
+ require 'activefacts/cql/compiler/constraint'
@@ -0,0 +1,104 @@
1
+ module ActiveFacts
2
+ module CQL
3
+ class Compiler < ActiveFacts::CQL::Parser
4
+
5
+ class Unit < Definition
6
+ def initialize singular, plural, numerator, denominator, offset, base_units, approximately, ephemera_url
7
+ @singular = singular
8
+ @plural = plural
9
+ @numerator, @denominator = numerator, denominator
10
+ @offset = offset
11
+ @base_units = base_units # An array of pairs, each [unit_name, power]
12
+ @approximately = approximately
13
+ @ephemera_url = ephemera_url
14
+ end
15
+
16
+ def compile
17
+ if (@numerator.to_f / @denominator.to_i != 1.0)
18
+ coefficient = @constellation.Coefficient(
19
+ :numerator => @numerator,
20
+ :denominator => @denominator.to_i,
21
+ :is_precise => !@approximately
22
+ )
23
+ else
24
+ coefficient = nil
25
+ end
26
+ @offset = nil if @offset.to_f == 0
27
+
28
+ debug :units, "Defining new unit #{@singular}#{@plural ? "/"+@plural : ""}" do
29
+ debug :units, "Coefficient is #{coefficient.numerator}#{coefficient.denominator != 1 ? "/#{coefficient.denominator}" : ""} #{coefficient.is_precise ? "exactly" : "approximately"}" if coefficient
30
+ debug :units, "Offset is #{@offset}" if @offset
31
+ raise "Redefinition of unit #{@singular}" if @constellation.Unit.values.detect{|u| u.name == @singular}
32
+ raise "Redefinition of unit #{@plural}" if @constellation.Unit.values.detect{|u| u.name == @plural}
33
+ unit = @constellation.Unit(:new,
34
+ :name => @singular,
35
+ :plural_name => @plural,
36
+ :coefficient => coefficient,
37
+ :offset => @offset,
38
+ :is_fundamental => @base_units.empty?,
39
+ :ephemera_url => @ephemera_url,
40
+ :vocabulary => @vocabulary
41
+ )
42
+ @base_units.each do |base_unit, exponent|
43
+ base = @constellation.Unit.values.detect{|u| u.name == base_unit || u.plural_name == base_unit }
44
+ debug :units, "Base unit #{base_unit}^#{exponent} #{base ? "" : "(implicitly fundamental)"}"
45
+ base ||= @constellation.Unit(:new, :name => base_unit, :is_fundamental => true, :vocabulary => @vocabulary)
46
+ @constellation.Derivation(:derived_unit => unit, :base_unit => base, :exponent => exponent)
47
+ end
48
+ =begin
49
+ if @plural
50
+ plural_unit = @constellation.Unit(:new,
51
+ :name => @plural,
52
+ :is_fundamental => false,
53
+ :vocabulary => @vocabulary
54
+ )
55
+ @constellation.Derivation(:derived_unit => plural_unit, :base_unit => unit, :exponent => 1)
56
+ end
57
+ =end
58
+ unit
59
+ end
60
+ end
61
+ end
62
+
63
+ class ValueType < Concept
64
+ def initialize name, base, parameters, unit, value_constraint, pragmas
65
+ super name
66
+ @base_type_name = base
67
+ @parameters = parameters
68
+ @unit = unit
69
+ @value_constraint = value_constraint
70
+ @pragmas = pragmas
71
+ end
72
+
73
+ def compile
74
+ length, scale = *@parameters
75
+
76
+ # Create the base type:
77
+ base_type = nil
78
+ if (@base_type_name != @name)
79
+ unless base_type = @constellation.ValueType[[@vocabulary.identifying_role_values, @constellation.Name(@base_type_name)]]
80
+ base_type = @constellation.ValueType(@vocabulary, @base_type_name)
81
+ return base_type if @base_type_name == @name
82
+ end
83
+ end
84
+
85
+ # Create and initialise the ValueType:
86
+ vt = @constellation.ValueType(@vocabulary, @name)
87
+ vt.is_independent = true if (@pragmas.include? 'independent')
88
+ vt.supertype = base_type if base_type
89
+ vt.length = length if length
90
+ vt.scale = scale if scale
91
+
92
+ raise "REVISIT: ValueType units are recognised but not yet compiled" unless @unit.empty?
93
+
94
+ if @value_constraint
95
+ @value_constraint.constellation = @constellation
96
+ vt.value_constraint = @value_constraint.compile
97
+ end
98
+
99
+ vt
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
@@ -7,7 +7,7 @@
7
7
  require 'rubygems'
8
8
  require 'treetop'
9
9
 
10
- # These are Treetop files, which it will compile on the fly if precompiled ones aren't found:
10
+ # These are Treetop files, which Polyglot will compile on the fly if precompiled ones aren't found:
11
11
  require 'activefacts/cql/LexicalRules'
12
12
  require 'activefacts/cql/Language/English'
13
13
  require 'activefacts/cql/Expressions'
@@ -20,17 +20,136 @@ require 'activefacts/cql/CQLParser'
20
20
 
21
21
  module ActiveFacts
22
22
  module CQL
23
+ module Terms
24
+ class SavedContext < Treetop::Runtime::SyntaxNode
25
+ attr_accessor :context
26
+ end
27
+ end
28
+
23
29
  # Extend the generated parser:
24
30
  class Parser < CQLParser
25
31
  include ActiveFacts
26
32
 
27
- class BlackHole
28
- def method_missing(m, *p, &b)
29
- self # Make all calls vanish
33
+ # The Context manages some key information revealed or needed during parsing
34
+ # These methods are semantic predicates; if they return false this parse rule will fail.
35
+ class Context
36
+ attr_reader :term, :global_term
37
+
38
+ def initialize(parser)
39
+ @parser = parser
40
+ @terms = {}
41
+ @role_names = {}
42
+ @allowed_forward_terms = []
43
+ end
44
+
45
+ def object_type(name, kind)
46
+ index_name(@terms, name) && debug(:context, "new #{kind} '#{name}'")
47
+ true
48
+ end
49
+
50
+ def reset_role_names
51
+ debug :context, "\tresetting role names #{@role_names.keys.sort*", "}" if @role_names && @role_names.size > 0
52
+ @role_names = {}
53
+ end
54
+
55
+ def allowed_forward_terms(terms)
56
+ @allowed_forward_terms = terms
57
+ end
58
+
59
+ def new_leading_adjective_term(adj, term)
60
+ index_name(@role_names, "#{adj} #{term}", term) && debug(:context, "new role '#{adj}- #{term}'")
61
+ true
62
+ end
63
+
64
+ def new_trailing_adjective_term(adj, term)
65
+ index_name(@role_names, n = "#{term} #{adj}", term) && debug(:context, "new role '#{term} -#{adj}'")
66
+ true
67
+ end
68
+
69
+ def role_name(name)
70
+ index_name(@role_names, name) && debug(:context, "new role '#{name}'")
71
+ true
72
+ end
73
+
74
+ def term_starts?(s, context_saver)
75
+ @term = @global_term = nil
76
+
77
+ @term_part = s
78
+ @context_saver = context_saver
79
+ t = @terms[s] || @role_names[s] || system_term(s)
80
+ if t
81
+ # s is a prefix of the keys of t.
82
+ if t[s]
83
+ @global_term = @term = @term_part
84
+ @context_saver.context = {:term => @term, :global_term => @global_term }
85
+ end
86
+ debug :context, "Term #{t[s] ? "is" : "starts"} '#{@term_part}'"
87
+ elsif @allowed_forward_terms.include?(@term_part)
88
+ @term = @term_part
89
+ @context_saver.context = {:term => @term, :global_term => @term }
90
+ debug :context, "Term #{s} is an allowed forward"
91
+ return true
92
+ end
93
+ t
94
+ end
95
+
96
+ def term_continues?(s)
97
+ @term_part = "#{@term_part} #{s}"
98
+ t = @terms[@term_part]
99
+ r = @role_names[@term_part]
100
+ if t && (!r || !r[@term_part]) # Part of a term and not a complete role name
101
+ w = "term"
102
+ else
103
+ t = r
104
+ w = "role_name"
105
+ end
106
+ if t
107
+ debug :context, "Multi-word #{w} #{t[@term_part] ? 'ends at' : 'continues to'} #{@term_part.inspect}"
108
+
109
+ # Record the name of the full term and the underlying global term:
110
+ if t[@term_part]
111
+ @term = @term_part if t[@term_part]
112
+ @global_term = (t = t[@term_part]) == true ? @term_part : t
113
+ debug :context, "saving context #{@term}/#{@global_term}"
114
+ @context_saver.context = {:term => @term, :global_term => @global_term }
115
+ end
116
+ end
117
+ t
118
+ end
119
+
120
+ def term_complete?
121
+ return true if @allowed_forward_terms.include?(@term)
122
+ return true if system_term(@term)
123
+ (t = @terms[@term] and t[@term]) or
124
+ (t = @role_names[@term] and t[@term])
125
+ end
126
+
127
+ def system_term(s)
128
+ false
129
+ end
130
+
131
+ def unit? s
132
+ @parser.unit? s
133
+ end
134
+
135
+ private
136
+ # Index the name by all prefixes
137
+ def index_name(index, name, value = true)
138
+ added = false
139
+ words = name.scan(/\w+/)
140
+ words.inject("") do |n, w|
141
+ # Index all prefixes up to the full term
142
+ n = n.empty? ? w : "#{n} #{w}"
143
+ index[n] ||= {}
144
+ added = true unless index[n][name]
145
+ index[n][name] = value # Save all possible completions of this prefix
146
+ n
147
+ end
148
+ added
30
149
  end
31
150
  end
32
151
 
33
- class InputProxy < Object
152
+ class InputProxy
34
153
  attr_reader :context
35
154
 
36
155
  def initialize(input, context)
@@ -64,7 +183,12 @@ module ActiveFacts
64
183
  end
65
184
 
66
185
  def context
67
- @context ||= BlackHole.new
186
+ @context ||= Context.new(self)
187
+ end
188
+
189
+ def unit?(s)
190
+ # puts "Asking whether #{s.inspect} is a unit"
191
+ true
68
192
  end
69
193
 
70
194
  def parse(input, options = {})
@@ -72,91 +196,18 @@ module ActiveFacts
72
196
  super(input, options)
73
197
  end
74
198
 
75
- # Repeatedly parse rule_name until all input is consumed,
76
- # returning an array of syntax trees for each definition.
77
199
  def parse_all(input, rule_name = nil, &block)
78
200
  self.root = rule_name if rule_name
79
201
 
80
202
  @index = 0 # Byte offset to start next parse
81
203
  self.consume_all_input = false
82
- results = []
83
204
  begin
84
205
  node = parse(InputProxy.new(input, context), :index => @index)
85
206
  return nil unless node
86
- node = block.call(node) if block
87
- results << node if node
207
+ block.call(node) if block
88
208
  end until self.index == @input_length
89
- results
90
- end
91
-
92
- def definition(node)
93
- name, ast = *node.value
94
- kind, *value = *ast
95
-
96
- begin
97
- debug "CQL: Processing definition #{[kind, name].compact*" "}" do
98
- case kind
99
- when :vocabulary
100
- [kind, name]
101
- when :value_type
102
- value_type_ast(name, value)
103
- when :entity_type
104
- supertypes = value.shift
105
- entity_type_ast(name, supertypes, value)
106
- when :fact_type
107
- f = fact_type_ast(name, value)
108
- when :unit
109
- ast
110
- when :constraint
111
- ast
112
- else
113
- raise "CQL: internal error, unknown definition kind"
114
- end
115
- end
116
- end
117
- rescue => e
118
- raise "in #{kind.to_s.camelcase(true)} definition, #{e.message}:\n\t#{node.text_value}" +
119
- (ENV['DEBUG'] =~ /\bexception\b/ ? "\nfrom\t"+e.backtrace*"\n\t" : "")
209
+ true
120
210
  end
121
-
122
- def value_type_ast(name, value)
123
- # REVISIT: Massage/check value type here?
124
- [:value_type, name, *value]
125
- end
126
-
127
- def entity_type_ast(name, supertypes, value)
128
- #print "entity_type parameters for #{name}: "; p value
129
- identification, mapping_pragmas, clauses = *value
130
- clauses ||= []
131
-
132
- # raise "Entity type clauses must all be fact types" if clauses.detect{|c| c[0] != :fact_clause }
133
-
134
- [:entity_type, name, supertypes, identification, mapping_pragmas, clauses]
135
- end
136
-
137
- def fact_type_ast(name, value)
138
- clauses, conditions = value
139
-
140
- if conditions.empty? && includes_literals(clauses)
141
- [:fact, nil, clauses]
142
- elsif clauses.size == 1 &&
143
- (popname = clauses[0][2]).size == 1 &&
144
- popname[0].keys == [:word] &&
145
- includes_literals(conditions)
146
- [:fact, popname[0][:word], conditions]
147
- else
148
- [:fact_type, name, clauses, conditions]
149
- end
150
- end
151
-
152
- def includes_literals(clauses)
153
- clauses.detect do |clause|
154
- raise "alternate clauses are not yet supported" if clause[0] == :"||"
155
- fc, qualifiers, phrases, context_note = *clause
156
- phrases.detect{|w| w[:literal]}
157
- end
158
- end
159
-
160
211
  end
161
212
 
162
213
  end