lib/finitio/support/type_factory.rb
module Finitio
class TypeFactory
DSL_METHODS = [
:attribute,
:heading,
:constraint,
:constraints,
:contract,
:contracts,
:any,
:builtin,
:adt,
:subtype,
:union,
:seq,
:set,
:struct,
:tuple,
:multi_tuple,
:relation,
:multi_relation,
:type,
:proxy
]
################################################################## Factory
def type(type, name = nil, metadata = nil, &bl)
return subtype(type(type, name, metadata), bl) if bl
case type
when Type
alias_type(type, name, metadata)
when Module
BuiltinType.new(type, name || type.name.to_s, metadata)
when Hash
tuple(type, name, metadata)
when Array
case type.size
when 0
fail!("Array of arity > 0 expected, got `#{type}`")
when 1
seq(type.first, name, metadata)
else
struct(type, name, metadata)
end
when Set
fail!("Set of arity 1 expected, got `#{type}`") unless type.size==1
sub = type(type.first)
if sub.is_a?(TupleType)
relation(sub.heading, name, metadata)
else
set(sub, name, metadata)
end
when Range
clazz = [type.begin, type.end].map(&:class).uniq
fail!("Unsupported range `#{type}`") unless clazz.size==1
subtype(clazz.first, type, name, metadata)
when Regexp
subtype(String, type, name, metadata)
else
fail!("Unable to factor a Finitio::Type from `#{type}`")
end
end
########################################################### Type Arguments
def ruby_type(type)
unless type.is_a?(Module)
fail!("Ruby module expected, got `#{type}`")
end
type
end
def name(name)
unless name.nil? or (name.is_a?(String) and name.strip.size > 1)
fail!("Wrong type name `#{name}`")
end
name.nil? ? nil : name.strip
end
def metadata(metadata)
unless metadata.nil? or metadata.is_a?(Hash)
fail!("Wrong metadata `#{metadata}`")
end
metadata
end
def constraint(constraint, name = nil)
case constraint
when Constraint then constraint
else Constraint.new(constraint, name)
end
end
def constraints(constraints = nil, &bl)
constrs = []
constrs << Constraint.new(bl) if bl
case constraints
when Hash
constraints.each_pair do |name, cstr|
constrs << constraint(cstr, name)
end
when Array
constraints.each do |c|
constrs << constraint(c)
end
else
constrs << Constraint.new(constraints)
end
constrs
end
def attribute(name, type, required = true, metadata = nil)
Attribute.new(name, type(type), required, metadata)
end
def attributes(attributes)
case attributes
when Hash
attributes.each_pair.map do |name, type|
attribute(name, type)
end
else
fail!("Hash expected, got `#{attributes}`")
end
end
def heading(heading)
case heading
when Heading
heading
when TupleType, RelationType
heading.heading
when Hash
Heading.new(attributes(heading))
else
fail!("Heading expected, got `#{heading}`")
end
end
def contract(infotype, dresser, undresser, name = nil, metadata = nil)
infotype = type(infotype)
Contract.new(infotype, dresser, undresser, name, metadata)
end
def contracts(contracts)
case contracts
when Array
unless contracts.all?{|c| c.is_a?(Contract) }
fail!("[Contract] expected, got `#{contracts}`")
end
contracts
when Hash
contracts.map do |k,v|
contract(*v.push(k.to_sym))
end
end
end
########################################################## Type generators
def any(name = nil, metadata = nil)
name = name(name)
meta = metadata(metadata)
AnyType.new(name, meta)
end
def builtin(ruby_type, name = nil, metadata = nil)
ruby_type = ruby_type(ruby_type)
name = name(name)
meta = metadata(metadata)
BuiltinType.new(ruby_type, name, meta)
end
def adt(ruby_type, contracts, name = nil, metadata = nil)
ruby_type = ruby_type(ruby_type) if ruby_type
contracts = contracts(contracts)
name = name(name)
meta = metadata(metadata)
AdType.new(ruby_type, contracts, name, meta)
end
### Sub and union
def subtype(super_type, constraints = nil, name = nil, metadata = nil, &bl)
super_type = type(super_type)
constraints = constraints(constraints, &bl)
name = name(name)
meta = metadata(metadata)
SubType.new(super_type, constraints, name, metadata)
end
def union(*args)
candidates, name, meta = [], nil, nil
args.each do |arg|
case arg
when Array then candidates = arg.map{|t| type(t) }
when String then name = name(arg)
else
candidates << type(arg)
end
end
UnionType.new(candidates, name, meta)
end
### Collections
def seq(elm_type, name = nil, metadata = nil)
elm_type = type(elm_type)
name = name(name)
meta = metadata(metadata)
SeqType.new(elm_type, name, meta)
end
def set(elm_type, name = nil, metadata = nil)
elm_type = type(elm_type)
name = name(name)
meta = metadata(metadata)
SetType.new(elm_type, name, meta)
end
def struct(component_types, name = nil, metadata = nil)
component_types = component_types.map{|t| type(t) }
name = name(name)
meta = metadata(metadata)
StructType.new(component_types, name, meta)
end
### Tuples and relations
def tuple(heading, name = nil, metadata = nil)
heading = heading(heading)
name = name(name)
meta = metadata(metadata)
TupleType.new(heading, name, meta)
end
def multi_tuple(heading, name = nil, metadata = nil)
heading = heading(heading)
name = name(name)
meta = metadata(metadata)
MultiTupleType.new(heading, name, meta)
end
def relation(heading, name = nil, metadata = nil)
heading = heading(heading)
name = name(name)
meta = metadata(metadata)
RelationType.new(heading, name, meta)
end
def multi_relation(heading, name = nil, metadata = nil)
heading = heading(heading)
name = name(name)
meta = metadata(metadata)
MultiRelationType.new(heading, name, meta)
end
def proxy(target_name)
ProxyType.new(target_name)
end
private
def alias_type(type, name, metadata)
raise "Type expected `#{type}`" unless type.is_a?(Type)
if (name && type.named?) or (metadata && type.metadata?)
AliasType.new(type, name, metadata)
else
type.name = name if name
type.metadata = metadata if metadata
type
end
end
def fail!(message)
raise ArgumentError, message, caller
end
end # class TypeBuilder
end # module Finitio