activefacts 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +83 -0
  3. data/README.rdoc +81 -0
  4. data/Rakefile +41 -0
  5. data/bin/afgen +46 -0
  6. data/bin/cql +52 -0
  7. data/examples/CQL/Address.cql +46 -0
  8. data/examples/CQL/Blog.cql +54 -0
  9. data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
  10. data/examples/CQL/Death.cql +16 -0
  11. data/examples/CQL/Genealogy.cql +95 -0
  12. data/examples/CQL/Marriage.cql +18 -0
  13. data/examples/CQL/Metamodel.cql +238 -0
  14. data/examples/CQL/MultiInheritance.cql +19 -0
  15. data/examples/CQL/OilSupply.cql +47 -0
  16. data/examples/CQL/Orienteering.cql +108 -0
  17. data/examples/CQL/PersonPlaysGame.cql +17 -0
  18. data/examples/CQL/SchoolActivities.cql +31 -0
  19. data/examples/CQL/SimplestUnary.cql +12 -0
  20. data/examples/CQL/SubtypePI.cql +32 -0
  21. data/examples/CQL/Warehousing.cql +99 -0
  22. data/examples/CQL/WindowInRoomInBldg.cql +22 -0
  23. data/lib/activefacts.rb +10 -0
  24. data/lib/activefacts/api.rb +25 -0
  25. data/lib/activefacts/api/concept.rb +384 -0
  26. data/lib/activefacts/api/constellation.rb +106 -0
  27. data/lib/activefacts/api/entity.rb +239 -0
  28. data/lib/activefacts/api/instance.rb +54 -0
  29. data/lib/activefacts/api/numeric.rb +158 -0
  30. data/lib/activefacts/api/role.rb +94 -0
  31. data/lib/activefacts/api/standard_types.rb +67 -0
  32. data/lib/activefacts/api/support.rb +59 -0
  33. data/lib/activefacts/api/value.rb +122 -0
  34. data/lib/activefacts/api/vocabulary.rb +120 -0
  35. data/lib/activefacts/cql.rb +31 -0
  36. data/lib/activefacts/cql/CQLParser.treetop +104 -0
  37. data/lib/activefacts/cql/Concepts.treetop +112 -0
  38. data/lib/activefacts/cql/DataTypes.treetop +66 -0
  39. data/lib/activefacts/cql/Expressions.treetop +113 -0
  40. data/lib/activefacts/cql/FactTypes.treetop +185 -0
  41. data/lib/activefacts/cql/Language/English.treetop +92 -0
  42. data/lib/activefacts/cql/LexicalRules.treetop +169 -0
  43. data/lib/activefacts/cql/Rakefile +6 -0
  44. data/lib/activefacts/cql/parser.rb +88 -0
  45. data/lib/activefacts/generate/absorption.rb +87 -0
  46. data/lib/activefacts/generate/cql.rb +441 -0
  47. data/lib/activefacts/generate/cql/html.rb +397 -0
  48. data/lib/activefacts/generate/null.rb +19 -0
  49. data/lib/activefacts/generate/ordered.rb +557 -0
  50. data/lib/activefacts/generate/ruby.rb +326 -0
  51. data/lib/activefacts/generate/sql/server.rb +164 -0
  52. data/lib/activefacts/generate/text.rb +21 -0
  53. data/lib/activefacts/input/cql.rb +1268 -0
  54. data/lib/activefacts/input/orm.rb +926 -0
  55. data/lib/activefacts/persistence.rb +1 -0
  56. data/lib/activefacts/persistence/composition.rb +653 -0
  57. data/lib/activefacts/support.rb +51 -0
  58. data/lib/activefacts/version.rb +3 -0
  59. data/lib/activefacts/vocabulary.rb +6 -0
  60. data/lib/activefacts/vocabulary/extensions.rb +343 -0
  61. data/lib/activefacts/vocabulary/metamodel.rb +303 -0
  62. data/script/txt2html +71 -0
  63. data/spec/absorption_spec.rb +95 -0
  64. data/spec/api/autocounter.rb +82 -0
  65. data/spec/api/constellation.rb +130 -0
  66. data/spec/api/entity_type.rb +101 -0
  67. data/spec/api/instance.rb +428 -0
  68. data/spec/api/roles.rb +122 -0
  69. data/spec/api/value_type.rb +112 -0
  70. data/spec/api_spec.rb +14 -0
  71. data/spec/cql_cql_spec.rb +58 -0
  72. data/spec/cql_parse_spec.rb +31 -0
  73. data/spec/cql_ruby_spec.rb +60 -0
  74. data/spec/cql_sql_spec.rb +54 -0
  75. data/spec/cql_symbol_tables_spec.rb +259 -0
  76. data/spec/cql_unit_spec.rb +336 -0
  77. data/spec/cqldump_spec.rb +169 -0
  78. data/spec/norma_cql_spec.rb +48 -0
  79. data/spec/norma_ruby_spec.rb +50 -0
  80. data/spec/norma_sql_spec.rb +45 -0
  81. data/spec/norma_tables_spec.rb +94 -0
  82. data/spec/spec.opts +1 -0
  83. data/spec/spec_helper.rb +10 -0
  84. metadata +173 -0
@@ -0,0 +1,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