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.
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,94 @@
1
+ ## The ActiveFacts Runtime API Concept class
2
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
3
+ #
4
+ module ActiveFacts
5
+ module API
6
+
7
+ # A Role represents the relationship of one object to another (or to a boolean condition).
8
+ # Relationships (or binary fact types) have a Role at each end; one is declared using _has_one_
9
+ # or _one_to_one_, and the other is created on the counterpart class. Each Concept class maintains
10
+ # an array of the roles it plays.
11
+ class Role
12
+ attr_accessor :name
13
+ attr_accessor :counterpart # All roles except unaries have a binary counterpart
14
+ attr_accessor :player # May be a Symbol, which will be converted to a Class/Concept
15
+ attr_accessor :unique
16
+ attr_accessor :mandatory
17
+ attr_accessor :value_restriction
18
+
19
+ def initialize(player, counterpart, name, mandatory = false, unique = true)
20
+ @player = player
21
+ @counterpart = counterpart
22
+ @name = name
23
+ @mandatory = mandatory
24
+ @unique = unique
25
+ end
26
+
27
+ def unary?
28
+ # N.B. A role with a forward reference looks unary until it is resolved.
29
+ counterpart == nil
30
+ end
31
+
32
+ def resolve_player(vocabulary) #:nodoc:
33
+ return @player if Class === @player # Done already
34
+ klass = vocabulary.concept(@player) # Trigger the binding
35
+ raise "Cannot resolve role player #{@player.inspect} for role #{name} in vocabulary #{vocabulary.basename}; still forward-declared?" unless klass
36
+ @player = klass # Memoize a successful result
37
+ end
38
+
39
+ def adapt(constellation, value) #:nodoc:
40
+ # If the value is a compatible class, use it (if in another constellation, clone it),
41
+ # else create a compatible object using the value as constructor parameters.
42
+ if @player === value # REVISIT: may be a non-primary subtype of player
43
+ # Check that the value is in a compatible constellation, clone if not:
44
+ if constellation && (vc = value.constellation) && vc != constellation
45
+ value = value.clone # REVISIT: There's sure to be things we should reset/clone here, like non-identifying roles
46
+ end
47
+ value.constellation = constellation if constellation
48
+ else
49
+ value = [value] unless Array === value
50
+ raise "No parameters were provided to identify an #{@player.basename} instance" if value == []
51
+ if constellation
52
+ value = constellation.send(@player.basename.to_sym, *value)
53
+ else
54
+ value = @player.new(*value)
55
+ end
56
+ end
57
+ value
58
+ end
59
+ end
60
+
61
+ # Every Concept has a Role collection
62
+ # REVISIT: You can enumerate the concept's own roles, or inherited roles as well.
63
+ class RoleCollection < Hash #:nodoc:
64
+ def verbalise
65
+ keys.sort_by(&:to_s).inspect
66
+ end
67
+ end
68
+
69
+ # A RoleValueArray is an array with all mutating methods hidden.
70
+ # We use these for the "many" side of a 1:many relationship.
71
+ # Only "replace" and "delete" are actually used (so far!).
72
+ #
73
+ # Don't rely on this implementation, as it must change to support
74
+ # persistence.
75
+ #
76
+ class RoleValueArray < Array #:nodoc:
77
+ [ :"<<", :"[]=", :clear, :collect!, :compact!, :concat, :delete,
78
+ :delete_at, :delete_if, :fill, :flatten!, :insert, :map!, :pop,
79
+ :push, :reject!, :replace, :reverse!, :shift, :shuffle!, :slice!,
80
+ :sort!, :uniq!, :unshift
81
+ ].each{|s|
82
+ begin
83
+ alias_method("__#{s}", s)
84
+ rescue NameError # shuffle! is in 1.9 only
85
+ end
86
+ }
87
+
88
+ def verbalise
89
+ "["+map{|e| e.verbalise}*", "+"]"
90
+ end
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,67 @@
1
+ #
2
+ # The ActiveFacts Runtime API Standard types extensions.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # These extensions add ActiveFacts Concept and Instance behaviour into base Ruby classes.
6
+ #
7
+ require 'date'
8
+
9
+ module ActiveFacts
10
+ module API
11
+ # Adapter module to add value_type to all potential value classes
12
+ module ValueClass #:nodoc:
13
+ def value_type *args, &block #:nodoc:
14
+ include ActiveFacts::API::Value
15
+ # the included method adds the Value::ClassMethods
16
+ initialise_value_type(*args, &block)
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ require 'activefacts/api/numeric'
23
+
24
+ # Add the methods that convert our classes into Concept types:
25
+
26
+ ValueClasses = [String, Date, DateTime, Time, Int, Real, AutoCounter]
27
+ ValueClasses.each{|c|
28
+ c.send :extend, ActiveFacts::API::ValueClass
29
+ }
30
+
31
+ class TrueClass #:nodoc:
32
+ def verbalise(role_name = nil); role_name ? "#{role_name}: true" : "true"; end
33
+ end
34
+
35
+ class NilClass #:nodoc:
36
+ def verbalise; "nil"; end
37
+ end
38
+
39
+ class Class
40
+ def identified_by *args
41
+ raise "not an entity type" if respond_to? :value_type # Don't make a ValueType into an EntityType
42
+ include ActiveFacts::API::Entity
43
+ initialise_entity_type(*args)
44
+ end
45
+ end
46
+
47
+ # REVISIT: Fix these NORMA types
48
+ class Decimal < Int #:nodoc:
49
+ end
50
+ class SignedInteger < Int #:nodoc:
51
+ end
52
+ class SignedSmallInteger < Int #:nodoc:
53
+ end
54
+ class UnsignedInteger < Int #:nodoc:
55
+ end
56
+ class UnsignedSmallInteger < Int #:nodoc:
57
+ end
58
+ class LargeLengthText < String #:nodoc:
59
+ end
60
+ class FixedLengthText < String #:nodoc:
61
+ end
62
+ class PictureRawData < String #:nodoc:
63
+ end
64
+ class DateAndTime < DateTime #:nodoc:
65
+ end
66
+ class Money < Decimal #:nodoc:
67
+ end
@@ -0,0 +1,59 @@
1
+ #
2
+ # ActiveFacts runtime API.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # Note that we still require facets/basicobject, see numeric.rb
6
+ #
7
+
8
+ class Symbol #:nodoc:
9
+ def to_proc
10
+ Proc.new{|*args| args.shift.__send__(self, *args)}
11
+ end
12
+ end
13
+
14
+ class String #:nodoc:
15
+ def camelcase(first=false, on='_\s')
16
+ if first
17
+ gsub(/(^|[#{on}]+)([A-Za-z])/){ $2.upcase }
18
+ else
19
+ gsub(/([#{on}]+)([A-Za-z])/){ $2.upcase }
20
+ end
21
+ end
22
+
23
+ def snakecase
24
+ gsub(/([A-Z]+)([A-Z])/,'\1_\2').gsub(/([a-z])([A-Z])/,'\1_\2').downcase
25
+ end
26
+ end
27
+
28
+ class Module #:nodoc:
29
+ def modspace
30
+ space = name[ 0...(name.rindex( '::' ) || 0)]
31
+ space == '' ? Object : eval(space)
32
+ end
33
+
34
+ def basename
35
+ name.gsub(/.*::/, '')
36
+ end
37
+ end
38
+
39
+ module ActiveFacts #:nodoc:
40
+ # If the args array ends with a hash, remove it.
41
+ # If the remaining args are fewer than the arg_names,
42
+ # extract values from the hash and append them to args.
43
+ # Return the new args array and the hash.
44
+ # In any case leave the original args unmodified.
45
+ def self.extract_hash_args(arg_names, args)
46
+ if Hash === args[-1]
47
+ arg_hash = args[-1] # Don't pop args, leave it unmodified
48
+ args = args[0..-2]
49
+ arg_hash = arg_hash.clone if (args.size < arg_names.size)
50
+ while args.size < arg_names.size
51
+ args << arg_hash[n = arg_names[args.size]]
52
+ arg_hash.delete(n)
53
+ end
54
+ else
55
+ arg_hash = {}
56
+ end
57
+ return args, arg_hash
58
+ end
59
+ end
@@ -0,0 +1,122 @@
1
+ #
2
+ # The ActiveFacts Runtime API Value extension module
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # The methods of this module are added to Value type classes.
6
+ #
7
+ module ActiveFacts
8
+ module API
9
+
10
+ # All Value instances include the methods defined here
11
+ module Value
12
+ include Instance
13
+
14
+ # Value instance methods:
15
+ def initialize(*args) #:nodoc:
16
+ super(args)
17
+ end
18
+
19
+ # verbalise this Value
20
+ def verbalise(role_name = nil)
21
+ "#{role_name || self.class.basename} '#{to_s}'"
22
+ end
23
+
24
+ # A value is its own key
25
+ def identifying_role_values #:nodoc:
26
+ self
27
+ end
28
+
29
+ # All ValueType classes include the methods defined here
30
+ module ClassMethods
31
+ include Instance::ClassMethods
32
+
33
+ def initialise_value_type *args, &block #:nodoc:
34
+ # REVISIT: args could be a hash, with keys :length, :scale, :unit, :allow
35
+ #raise "value_type args unexpected" if args.size > 0
36
+ end
37
+
38
+ class_eval do
39
+ define_method :length do |*args|
40
+ @length = args[0] if args.length > 0
41
+ @length
42
+ end
43
+ end
44
+
45
+ class_eval do
46
+ define_method :scale do |*args|
47
+ @scale = args[0] if args.length > 0
48
+ @scale
49
+ end
50
+ end
51
+
52
+ # verbalise this ValueType
53
+ def verbalise
54
+ # REVISIT: Add length and scale here, if set
55
+ # REVISIT: Set vocabulary name of superclass if not same as this
56
+ "#{basename} = #{superclass.basename}();"
57
+ end
58
+
59
+ def identifying_role_values(*args) #:nodoc:
60
+ # If the single arg is the correct class or a subclass, use it directly
61
+ #puts "#{basename}.identifying_role_values#{args.inspect}"
62
+ return args[0] if (args.size == 1 and self.class === args[0]) # No secondary supertypes allowed for value types
63
+ new(*args)
64
+ end
65
+
66
+ def assert_instance(constellation, args) #:nodoc:
67
+ # Build the key for this instance from the args
68
+ # The key of an instance is the value or array of keys of the identifying values.
69
+ # The key values aren't necessarily present in the constellation, even after this.
70
+ key = identifying_role_values(*args)
71
+ #puts "#{klass} key is #{key.inspect}"
72
+
73
+ # Find and return an existing instance matching this key
74
+ instances = constellation.instances[self] # All instances of this class in this constellation
75
+ instance = instances[key]
76
+ # DEBUG: puts "assert #{self.basename} #{key.inspect} #{instance ? "exists" : "new"}"
77
+ return instance, key if instance # A matching instance of this class
78
+
79
+ instance = new(*args)
80
+
81
+ instance.constellation = constellation
82
+ return *index_instance(instance)
83
+ end
84
+
85
+ def index_instance(instance, key = nil) #:nodoc:
86
+ instances = instance.constellation.instances[self]
87
+ key = instance.identifying_role_values
88
+ instances[key] = instance
89
+ # DEBUG: puts "indexing value #{basename} using #{key.inspect} in #{constellation.object_id}"
90
+
91
+ # Index the instance for each supertype:
92
+ supertypes.each do |supertype|
93
+ supertype.index_instance(instance, key)
94
+ end
95
+
96
+ return instance, key
97
+ end
98
+
99
+ def inherited(other) #:nodoc:
100
+ #puts "REVISIT: ValueType #{self} < #{self.superclass} was inherited by #{other}; not implemented" #+"from #{caller*"\n\t"}"
101
+ # Copy the type parameters here, etc?
102
+ other.send :realise_supertypes, self
103
+ vocabulary.add_concept(other)
104
+ super
105
+ end
106
+ end
107
+
108
+ def self.included other #:nodoc:
109
+ other.send :extend, ClassMethods
110
+
111
+ #puts "ValueType included in #{other.basename} from #{caller*"\n\t"}"
112
+
113
+ # Register ourselves with the parent module, which has become a Vocabulary:
114
+ vocabulary = other.modspace
115
+ unless vocabulary.respond_to? :concept # Extend module with Vocabulary if necessary
116
+ vocabulary.send :extend, Vocabulary
117
+ end
118
+ vocabulary.add_concept(other)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,120 @@
1
+ #
2
+ # The ActiveFacts Runtime API Vocabulary extension module.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # The methods of this module are extended into any module that contains
6
+ # a Concept class (Entity type or Value type).
7
+ #
8
+ module ActiveFacts
9
+ module API
10
+ module Vocabulary
11
+ # With a parameter, look up a concept class by name.
12
+ # Without, return the hash of all concepts in this vocabulary
13
+ def concept(name = nil)
14
+ @concept ||= {}
15
+ return @concept unless name
16
+
17
+ return name if name.is_a? Class
18
+
19
+ # puts "Looking up concept #{name} in #{self.name}"
20
+ camel = name.to_s.camelcase(true)
21
+ if (c = @concept[camel])
22
+ __bind(camel)
23
+ return c
24
+ end
25
+ return (const_get(camel) rescue nil)
26
+ end
27
+
28
+ def add_concept(klass) #:nodoc:
29
+ name = klass.basename
30
+ __bind(name)
31
+ # puts "Adding concept #{name} to #{self.name}"
32
+ @concept ||= {}
33
+ @concept[klass.basename] = klass
34
+ end
35
+
36
+ def __delay(concept_name, args, &block) #:nodoc:
37
+ # puts "Arranging for delayed binding on #{concept_name.inspect}"
38
+ @delayed ||= Hash.new { |h,k| h[k] = [] }
39
+ @delayed[concept_name] << [args, block]
40
+ end
41
+
42
+ # __bind raises an error if the named class doesn't exist yet.
43
+ def __bind(concept_name) #:nodoc:
44
+ concept = const_get(concept_name)
45
+ if (@delayed && @delayed.include?(concept_name))
46
+ # $stderr.puts "#{concept_name} was delayed, binding now"
47
+ d = @delayed[concept_name]
48
+ d.each{|(a,b)|
49
+ b.call(concept, *a)
50
+ }
51
+ @delayed.delete(concept_name)
52
+ end
53
+ end
54
+
55
+ def verbalise
56
+ "Vocabulary #{name}:\n\t" +
57
+ @concept.keys.sort.map{|concept|
58
+ c = @concept[concept]
59
+ __bind(c.basename)
60
+ c.verbalise + "\n\t\t// Roles played: " + c.roles.verbalise
61
+ }*"\n\t"
62
+ end
63
+
64
+ # Create or find an instance of klass in constellation using value to identify it
65
+ def adopt(klass, constellation, value) #:nodoc:
66
+ puts "Adopting #{ value.verbalise rescue value.class.to_s+' '+value.inspect} as #{klass} into constellation #{constellation.object_id}"
67
+
68
+ path = "unknown"
69
+ # Create a value instance we can hack if the value isn't already in this constellation
70
+ if (c = constellation)
71
+ if klass === value # Right class?
72
+ vc = value.constellation rescue nil
73
+ if (c == vc) # Right constellation?
74
+ # Already right class, in the right constellation
75
+ path = "right constellation, right class, just use it"
76
+ else
77
+ # We need a new object from our constellation, so copy the value.
78
+ if klass.respond_to?(:identifying_roles)
79
+ # Make a new entity having only the identifying roles set.
80
+ # Someone will complain that this is wrong, and all functional role values should also
81
+ # be cloned, and I'm listening... but not there yet. Why just those?
82
+ cloned = c.send(
83
+ :"#{klass.basename}",
84
+ *klass.identifying_roles.map{|role| value.send(role) }
85
+ )
86
+ path = "wrong constellation, right class, cloned entity"
87
+ else
88
+ # Just copy a value:
89
+ cloned = c.send(:"#{klass.basename}", *value)
90
+ path = "wrong constellation, right class, copied value"
91
+ end
92
+ value.constellation = c
93
+ end
94
+ else
95
+ # Wrong class, assume it's a valid constructor arg. Get our constellation to find/make it:
96
+ value = [ value ] unless Array === value
97
+ value = c.send(:"#{klass.basename}", *value)
98
+ path = "right constellation but wrong class, constructed from args"
99
+ end
100
+ else
101
+ # This object's not in a constellation
102
+ if klass === value # Right class?
103
+ if vc = value.constellation rescue nil
104
+ raise "REVISIT: Assigning to #{self.class.basename}.#{role_name} with constellation=#{c.inspect}: Can't dis-associate object from its constellation #{vc.object_id} yet"
105
+ end
106
+ # Right class, no constellation, just use it
107
+ path = "no constellation, correct class"
108
+ else
109
+ # Wrong class, construct one
110
+ value = klass.send(:new, *value)
111
+ path = "no constellation, constructed from wrong class"
112
+ end
113
+ end
114
+ # print "#{path}"; puts ", adopted as #{value.verbalise rescue value.inspect}"
115
+ value
116
+ end
117
+
118
+ end
119
+ end
120
+ end