solidity-typed 0.1.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.
@@ -0,0 +1,52 @@
1
+ ###
2
+ # add global type conversion functions here
3
+ #
4
+ # include via Kernel module - why? why not?
5
+
6
+
7
+ module ConversionFunctions
8
+ #####
9
+ # todo/check: use AddressType.try_convert( literal_or_obj ) or such - why? why not?
10
+ def address( literal=0 )
11
+ ## hack for now support address(0)
12
+ ## todo/fix: address( '0x0' ) too!!!!
13
+ return Types::Typed::AddressType.instance.zero if literal.is_a?(::Integer) && literal == 0
14
+
15
+ Types::Typed::AddressType.instance.check_and_normalize_literal( literal )
16
+ end # methdod address
17
+
18
+
19
+ =begin
20
+ def uint( obj=0 )
21
+ ## check if typed?
22
+ ## raise exception on error
23
+ end
24
+ =end
25
+
26
+
27
+
28
+ =begin
29
+ def string(i)
30
+ if i.is_a?(TypedVariable) && i.type.is_value_type?
31
+ return TypedVariable.create(:string, i.value.to_s)
32
+ else
33
+ raise "Input must be typed"
34
+ end
35
+ end
36
+
37
+ def address(i)
38
+ return TypedVariable.create(:address) if i == 0
39
+
40
+ if i.is_a?(TypedVariable) && i.type == Type.create(:addressOrDumbContract)
41
+ return TypedVariable.create(:address, i.value)
42
+ end
43
+
44
+ raise "Not implemented"
45
+ end
46
+ =end
47
+
48
+ end # module ConversionFunctions
49
+
50
+
51
+
52
+ Kernel.include( ConversionFunctions )
@@ -0,0 +1,116 @@
1
+ ###############################
2
+ ## base class for enum
3
+ #
4
+ #
5
+ # notes on enum:
6
+ # do NOT ever create new typed enums with new/create!!!!
7
+ # only create zeros with new_zero!!!!!
8
+ # otherwise always reuse and assign CONSTANTS!!!!
9
+ ##
10
+ ## todo - fix-fix-fix - enforce rules in code here - why? why not?
11
+ ## make new private or such - why? why not?
12
+ ## check TrueClass|FalseClass as example - why? why not?
13
+
14
+ ## for bool and enum
15
+ ## use TypedData - only allow assignment of existing instances
16
+ ## no NEW possible!!!
17
+ ## thus - deserialize has a switch for TypedData!!
18
+
19
+ module Types
20
+ class Enum < Typed
21
+
22
+ def self.zero() members[0]; end
23
+ def zero?() self == self.class.zero; end ## note: use compare by identity (object_id) and NOT value e.g. 0
24
+
25
+
26
+ ## return a new Enum read-only class
27
+ attr_reader :key
28
+ attr_reader :value
29
+
30
+
31
+ def initialize( key, value )
32
+ @key = key
33
+ @value = value
34
+ self.freeze ## make "immutable"
35
+ self
36
+ end
37
+
38
+ def self._typecheck_enum!( o )
39
+ if o.instance_of?( self )
40
+ o
41
+ else
42
+ raise TypeError.new( "[enum] enum >#{name}< type expected; got >#{o.class.inspect}<" )
43
+ end
44
+ end
45
+ def _typecheck_enum!( o ) self.class._typecheck_enum!( o ); end
46
+
47
+
48
+ def ==( other )
49
+ if other.is_a?( Integer ) && other == 0 ## note: only allow compare by zero (0) integer - why? why not?
50
+ @value == 0
51
+ else
52
+ @value == _typecheck_enum!( other ).value
53
+ end
54
+ end
55
+ ## keep eql? compare by object_id(entity) - why? why not?
56
+ ### alias_method :eql?, :==
57
+
58
+
59
+ def self.keys
60
+ @keys ||= members.map {|member| member.key}.freeze
61
+ @keys
62
+ end
63
+
64
+ def self.key( key )
65
+ ## note: returns nil now for unknown keys
66
+ ## use/raise IndexError or something - why? why not?
67
+ @hash_by_key ||= Hash[ keys.zip( members ) ].freeze
68
+ @hash_by_key[key]
69
+ end
70
+
71
+ class << self
72
+ alias_method :[], :key ## convenience alias for key
73
+ end
74
+
75
+ def self.values
76
+ @values ||= members.map {|member| member.value}.freeze
77
+ @values
78
+ end
79
+
80
+ def self.value( value )
81
+ ## note: returns nil now for unknown values
82
+ ## use/raise IndexError or something - why? why not?
83
+ @hash_by_value ||= Hash[ values.zip( members ) ].freeze
84
+ @hash_by_value[value]
85
+ end
86
+
87
+ def self.min() members[0]; end
88
+ def self.max() members[-1]; end
89
+
90
+ def self.size() keys.size; end
91
+ class << self
92
+ alias_method :length, :size ## alias (as is the ruby tradition)
93
+ end
94
+
95
+ def self.convert( arg )
96
+ ## todo/check: support keys too - why? why not?
97
+ ## e.g. Color(0), Color(1)
98
+ ## Color(:red), Color(:blue) - why? why not?
99
+ ## note: will ALWAYS look-up by (member) index and NOT by value (integer number value might be different!!)
100
+ members[ arg ]
101
+ end
102
+
103
+ ## add to_i, to_int - why? why not?
104
+ def to_i() @value; end
105
+ def to_int() @value; end ## allows Integer( .. )
106
+
107
+ ## add to_b/to_bool support (see safebool @ https://github.com/s6ruby/safebool) - why? why not?
108
+ # def parse_bool() @value != 0; end ## nonzero == true, zero == false like numbers
109
+
110
+ def as_data() @value; end
111
+
112
+ def pretty_print( printer )
113
+ printer.text( "<val #{type}:#{@key}(#{@value})>" );
114
+ end
115
+ end # class Enum
116
+ end # module Types
@@ -0,0 +1,101 @@
1
+
2
+ module Types
3
+ class Enum
4
+
5
+ ###################
6
+ ## meta-programming "macro" - build class (on the fly)
7
+ #
8
+ # todo/fix: scope: keep empty by default
9
+
10
+
11
+
12
+ def self.build_class( class_name, *args, scope: Types )
13
+ if args.size > 0
14
+ ## e.g. :Color, :red, :green, :blue
15
+ ## or
16
+ ## :Color, [:red, :green, :blue]
17
+ ## note: start counting a zero (0) !!!
18
+ keys = args
19
+ values = (0...keys.size).to_a # note: use ... (exclusive) range
20
+ e = Hash[ keys.zip( values ) ]
21
+ else
22
+ raise ArgumentError, "[enum] no members declared - min. one member required; got #{args}; sorry"
23
+ end
24
+
25
+ ## todo/fix:
26
+ ## check class name MUST start with uppercase letter
27
+
28
+ ## check if all keys are symbols and follow the ruby id(entifier) naming rules
29
+ ## todo/check - allow uppercase fist A-Z letters - why? why not?
30
+ e.keys.each do |key|
31
+ if key.is_a?( Symbol ) && key =~ /\A[a-z][a-zA-Z0-9_]*\z/
32
+ else
33
+ raise ArgumentError.new( "[enum] members to enum must be all symbols following the ruby/solidity id naming rules; >#{key}< failed" )
34
+ end
35
+ end
36
+
37
+ klass = Class.new( Enum )
38
+
39
+
40
+ ## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
41
+ klass.define_singleton_method( :new ) do |*new_args|
42
+ raise ArgumentError, "enum #{klass.name} - do NOT call new EVER; reuse existing enum members! sorry"
43
+ end
44
+ ## make new - private too - why? why not?
45
+
46
+
47
+ e.each do |key,value|
48
+ klass.class_eval( <<RUBY )
49
+ #{key.upcase} = old_new( :#{key}, #{value} )
50
+
51
+ def #{key}?
52
+ self == #{key.upcase}
53
+ end
54
+
55
+ def self.#{key}
56
+ #{key.upcase}
57
+ end
58
+ RUBY
59
+ end
60
+
61
+ klass.class_eval( <<RUBY )
62
+ def self.members
63
+ @members ||= [#{e.keys.map {|key|key.upcase}.join(',')}].freeze
64
+ end
65
+ RUBY
66
+
67
+
68
+ type = EnumType.new( class_name, klass )
69
+ klass.define_singleton_method( :type ) do
70
+ @type ||= type
71
+ end
72
+
73
+ =begin
74
+ ## note: use Kernel for "namespacing"
75
+ ## make all enums Kernel convenience converters (always) global
76
+ ## including uppercase methods (e.g. State(), Color(), etc.) does NOT work otherwise (with other module includes)
77
+
78
+ ## add global convenience converter function
79
+ ## e.g. State(0) is same as State.convert(0)
80
+ ## Color(0) is same as Color.convert(0)
81
+ Kernel.class_eval( <<RUBY )
82
+ def #{class_name}( arg )
83
+ #{class_name}.convert( arg )
84
+ end
85
+ RUBY
86
+ =end
87
+ ## note: use scoped(class) and NO Object for namespacing
88
+ scope.const_set( class_name, klass ) ## returns klass (plus sets global constant class name)
89
+ klass
90
+ end
91
+
92
+
93
+ class << self
94
+ alias_method :old_new, :new # note: store "old" orginal version of new
95
+ alias_method :new, :build_class # replace original version with create
96
+ end
97
+
98
+
99
+
100
+ end # class Enum
101
+ end # module Types
@@ -0,0 +1,108 @@
1
+
2
+ module Types
3
+ class Mapping < TypedReference
4
+
5
+ def self.zero() @zero ||= new; end
6
+ def zero?() @data.empty? end
7
+
8
+ ## add short-cut helpers why? why not?
9
+ def key_type() self.class.type.key_type; end
10
+ def value_type() self.class.type.value_type; end
11
+
12
+ ## todo/check: make "internal" data/hash available? why? why not?
13
+ ## attr_reader :data
14
+
15
+ def initialize( initial_value = {} )
16
+ ## was: initial_value ||= {}
17
+ ## check if nil gets passed in - default not used?
18
+
19
+ raise ArgumentError, "expected literal of type #{type}; got typed #{initial_value.pretty_print_inspect}" if initial_value.is_a?( Typed )
20
+
21
+ @data = type.check_and_normalize_literal( initial_value ).map do |key, value|
22
+ [
23
+ type.key_type.new( key ),
24
+ type.value_type.new( value )
25
+ ]
26
+ end.to_h
27
+ end
28
+
29
+
30
+ extend Forwardable ## pulls in def_delegator
31
+ ## add more Hash forwards here!!!!
32
+ def_delegators :@data, :size, :empty?, :clear
33
+
34
+
35
+
36
+ def [](key)
37
+ puts "[debug] Mapping#[]( #{key} )"
38
+ key_var = key.is_a?( Typed ) ? key : key_type.new( key )
39
+ obj = @data[key_var]
40
+
41
+ ## was:
42
+ ## if value_type.mapping? && obj.nil?
43
+ ## obj =
44
+ ## @data[key_var] = obj
45
+ ## end
46
+ ##
47
+ ## note:
48
+ ## change to
49
+ ## allow access to ref to struct and than update change to assign !!=!!!!!!
50
+
51
+ if obj.nil?
52
+ obj = value_type.new_zero
53
+ @data[ key_var ] = obj
54
+ end
55
+
56
+ obj
57
+ end
58
+
59
+
60
+
61
+ def []=(key, new_value)
62
+ puts "[debug] Mapping#[]=( #{key}:#{key.class.name}, #{new_value}:#{new_value.class.name})"
63
+ pp type.key_type
64
+ key_var = key.is_a?( Typed ) ? key : key_type.new( key )
65
+ pp key_var
66
+ obj = new_value.is_a?( Typed ) ? new_value : value_type.new( new_value )
67
+ pp obj
68
+
69
+ if value_type.mapping?
70
+ ## val_var = Proxy.new(keytype: valuetype.keytype, valuetype: valuetype.valuetype)
71
+ raise 'What?'
72
+ end
73
+
74
+ @data[key_var] = obj
75
+ end
76
+
77
+
78
+ def key?( key )
79
+ key_var = key.is_a?( Typed ) ? key : key_type.new( key )
80
+ @data.key?( key_var )
81
+ end
82
+ alias_method :has_key?, :key?
83
+
84
+ def delete( key )
85
+ key_var = key.is_a?( Typed ) ? key : key_type.new( key )
86
+ @data.delete( key_var )
87
+ end
88
+
89
+
90
+ ## note: pass through value!!!!
91
+ ## in new scheme - only "plain" ruby arrays/hash/string/integer/bool used!!!!
92
+ ## no wrappers!!! - no need to convert!!!
93
+ def as_data
94
+ puts "[debug] Mapping#as_data"
95
+ @data.reduce({}) do |h, (k, v)|
96
+ ## todo/fix:
97
+ ## check if value is zero/ do not serialize zero - why? why not?
98
+ h[k.as_data] = v.as_data
99
+ h
100
+ end
101
+ end
102
+
103
+ def pretty_print( printer )
104
+ printer.text( "<ref #{type}:#{@data.pretty_print_inspect}>" );
105
+ end
106
+ end # class Mapping
107
+ end # module Types
108
+
@@ -0,0 +1,54 @@
1
+
2
+ module Types
3
+
4
+ class Mapping
5
+
6
+ def self.build_class( key_type, value_type )
7
+
8
+ # key_type = Type.create( key_type ) if key_type.is_a?( Symbol ) ||
9
+ # key_type.is_a?( String )
10
+ # value_type = Type.create( value_type ) if value_type.is_a?( Symbol ) ||
11
+ # value_type.is_a?( String )
12
+ # key_type = key_type.type if key_type.is_a?( Class ) && key_type.ancestors.include?( Typed )
13
+ # value_type = value_type.type if value_type.is_a?( Class ) && value_type.ancestors.include?( Typed )
14
+
15
+ key_type = typeof( key_type )
16
+ value_type = typeof( value_type )
17
+
18
+ type = MappingType.instance( key_type, value_type )
19
+
20
+ class_name = type.typedclass_name
21
+
22
+ ## note: keep a class cache
23
+ ## note: klasses may have different init sizes (default 0)
24
+ cache = @@cache ||= {}
25
+ klass = cache[ class_name ]
26
+ ## fix-fix-fix - check if const klass defined for cache (no cache needed)!!!!!!!!
27
+
28
+ if klass.nil?
29
+ klass = Class.new( Mapping )
30
+ klass.define_singleton_method( :type ) do
31
+ @type ||= type
32
+ end
33
+
34
+ ## add self.new too - note: call/forward to "old" orginal self.new of Event (base) class
35
+ klass.define_singleton_method( :new ) do |*args|
36
+ old_new( *args )
37
+ end
38
+
39
+ ## add to cache for later (re)use
40
+ cache[ class_name ] = klass
41
+ Types.const_set( class_name, klass )
42
+ end
43
+
44
+ klass
45
+ end # method self.build_class
46
+
47
+ class << self
48
+ alias_method :old_new, :new # note: store "old" orginal version of new
49
+ alias_method :new, :build_class # replace original version with create
50
+ end
51
+
52
+
53
+ end # class Mapping
54
+ end # module Types
@@ -0,0 +1,56 @@
1
+ module Types
2
+ class Typed ## note: use class Typed as namespace (all metatype etc. nested here - the beginning)
3
+
4
+
5
+ class ArrayType < ReferenceType ## note: dynamic array for now (NOT fixed!!!! - add FixedArray - why? why not?)
6
+ def self.instance( sub_type, size=0 )
7
+ raise ArgumentError, "[ArrayType.instance] sub_type not a type - got #{sub_type}; sorry" unless sub_type.is_a?( Type )
8
+ @instances ||= {}
9
+ key = '' ## note: String.new('') will pick-up TypedString!!; thus, use literal for now
10
+ key += sub_type.format
11
+ key += "×#{size}" if size != 0
12
+ @instances[ key ] ||= new( sub_type, size )
13
+ end
14
+
15
+
16
+ attr_reader :sub_type
17
+ attr_reader :size ## rename size to dim(ension) or such - why? why not?
18
+ def initialize( sub_type, size=0 )
19
+ @sub_type = sub_type
20
+ @size = size
21
+ end
22
+ def format
23
+ if @size == 0 ## assume dynamic
24
+ "#{@sub_type.format}[]"
25
+ else
26
+ "#{@sub_type.format}[#{@size}]"
27
+ end
28
+ end
29
+ alias_method :to_s, :format
30
+
31
+ def ==(other)
32
+ other.is_a?( ArrayType ) &&
33
+ @sub_type == other.sub_type &&
34
+ @size == other.size ### add check for size too - why? why not?
35
+ end
36
+
37
+
38
+ def typedclass_name
39
+ ## return/use symbol (not string here) - why? why not?
40
+ ## or use TypedArrayOf<sub-type.typedclass_name> - why? why not?
41
+ sub_name = _sanitize_class_name( @sub_type.typedclass_name )
42
+ name = '' ## note: String.new('') will pick-up TypedString!!; thus, use literal for now
43
+ name += "Array‹#{sub_name}›"
44
+ name += "×#{size}" if @size != 0
45
+ name
46
+ end
47
+ def typedclass() Types.const_get( typedclass_name ); end
48
+
49
+ def mut?() true; end
50
+ def new_zero() typedclass.new; end
51
+ def new( initial_value ) typedclass.new( initial_value ); end
52
+ end # class ArrayType
53
+
54
+
55
+ end # class Typed ## note: use class Typed as namespace (all metatype etc. nested here - the beginning)
56
+ end # module Types
@@ -0,0 +1,39 @@
1
+ module Types
2
+ class Typed ## note: use class Typed as namespace (all metatype etc. nested here - the beginning)
3
+
4
+
5
+ class BoolType < DataType
6
+ def self.instance() @instance ||= new; end
7
+
8
+ def format() 'bool'; end
9
+ alias_method :to_s, :format
10
+
11
+ def ==(other) other.is_a?( BoolType ); end
12
+
13
+ ## note: class is really a module for bool - hack!!!
14
+ ## remove class from methods (typedclass_name|typedclass) - why? why not?
15
+ ## change typedclass_name to typed_name !!!
16
+ ## and typedclass to typed !!! - why? why not??
17
+ def typedclass_name() Bool.name; end
18
+ def typedclass() Bool; end
19
+
20
+ def mut?() false; end
21
+ def zero() Bool.zero; end ## note: Bool.zero is false
22
+ alias_method :new_zero, :zero ## add/keep (convenience) alias for new_zero - why? why not?
23
+ def new( initial_value ) ## check - add default zero value - why? why not?
24
+ ## allow integer 0|1 here too - why? why not?
25
+ ## fix-fix-fix check solidity functions with bool (if they use 0|1 or true|false)!!!
26
+ if initial_value == true # instance_of( TrueClass )
27
+ true
28
+ elsif initial_value == false # instance_of?( FalseClass )
29
+ false
30
+ else
31
+ ## use value error?
32
+ raise ArgumentError, "BoolType.new - true or false arg expected; got: #{initial_value}"
33
+ end
34
+ end
35
+ end # class BoolType
36
+
37
+ end # class Typed ## note: use class Typed as namespace (all metatype etc. nested here - the beginning)
38
+ end # module Types
39
+