tealrb 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ccb46efa84c6dfdf2378ab562eaf2345d947f0a9af9850c05220acfa1b540bc3
4
+ data.tar.gz: d7bfaa9d68b2fe60adeeb229f54f087a56b73319f0d3f6f8bf8d673b8a103258
5
+ SHA512:
6
+ metadata.gz: '0805c1e920c22e3d0fc64975135850a08147b3a3c9f1fb9390868ed60220d504f3fd20c89ea4ecdc96f71068e75b0748f62e502155335007ae0d3530fcbd4af4'
7
+ data.tar.gz: ec0abbd56e219ea3082200d9fb5bab2cd6ead37957af0c7dccc359e6c94064967cdac907b776bad0d07d9f1ecfb22fa46c37a84322777dfeb1d7fde0d1df57a2
data/lib/tealrb/abi.rb ADDED
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module TEALrb
6
+ module ABI
7
+ def abi_return(data)
8
+ log(concat('151f7c75', data.teal).teal)
9
+ end
10
+
11
+ class ABIDescription
12
+ attr_accessor :name
13
+ attr_reader :methods
14
+
15
+ def initialize
16
+ @name = ''
17
+ @networks = {}
18
+ @methods = []
19
+ end
20
+
21
+ def add_method(name:, desc:, args:, returns:)
22
+ @methods << {
23
+ name: name,
24
+ desc: desc,
25
+ args: args,
26
+ returns: { type: returns }
27
+ }
28
+ end
29
+
30
+ def add_id(network, id)
31
+ @networks[network] = { appID: id }
32
+ end
33
+
34
+ def to_h
35
+ {
36
+ 'name' => @name,
37
+ 'networks' => @networks,
38
+ 'methods' => @methods
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+ module TEALrb
5
+ module CmdLine
6
+ module TEAL2TEALrb
7
+ def self.convert(input)
8
+ output = StringIO.new
9
+
10
+ input.each_line do |line|
11
+ line.strip!
12
+ next if line[/^#/]
13
+
14
+ if line[%r{^//}]
15
+ line = %(comment("#{line.sub(%r{//}, '')}"))
16
+ output.puts line
17
+ next
18
+ elsif line[%r{//.*}]
19
+ comment = line[%r{(?<=//).*}]
20
+ line.sub!(%r{//.*}, '')
21
+ inline_comment = %(comment("#{comment}", inline: true))
22
+ end
23
+
24
+ opcode = line.split.first
25
+
26
+ opcode_mappings = TEALrb::Opcodes::BINARY_OPCODE_METHOD_MAPPING
27
+ opcode_mappings.merge TEALrb::Opcodes::UNARY_OPCODE_METHOD_MAPPING
28
+
29
+ opcode_mappings.each do |op, method|
30
+ line.sub!(opcode, method) if opcode == op.to_s
31
+ end
32
+
33
+ if line[/^(b|bz|bnz|callsub) /]
34
+ label = line.split.last
35
+ line.gsub!(label, ":#{label}")
36
+ elsif line[/\w+:/]
37
+ line = ":#{line[0..-2]}"
38
+ elsif opcode == 'return'
39
+ line = 'teal_return'
40
+ elsif opcode == 'method'
41
+ line.sub!('method', 'method_signature')
42
+ end
43
+
44
+ if line.count(' ').positive? && !%w[byte pushbytes method].include?(opcode)
45
+ args = line.split[1..]
46
+ args.map! do |arg|
47
+ arg_as_int = Integer(arg, exception: false)
48
+ if arg[/^:/]
49
+ arg
50
+ else
51
+ arg_as_int || "'#{arg}'"
52
+ end
53
+ end
54
+
55
+ line = "#{opcode} #{args.join(', ')}"
56
+ end
57
+
58
+ output.puts line
59
+ output.puts inline_comment if inline_comment
60
+ end
61
+
62
+ output.string
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TEALrb
4
+ MAINNET = 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8='
5
+ end
@@ -0,0 +1,253 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TEALrb
4
+ class Contract
5
+ include TEALrb
6
+ include Opcodes
7
+ include ABI
8
+ include Rewriters
9
+
10
+ attr_reader :teal
11
+
12
+ class << self
13
+ attr_accessor :subroutines, :version, :teal_methods, :abi_method_hash, :abi_description, :debug
14
+
15
+ private
16
+
17
+ def inherited(klass)
18
+ klass.version = 6
19
+ klass.subroutines = {}
20
+ klass.teal_methods = {}
21
+ klass.abi_description = ABI::ABIDescription.new
22
+ klass.abi_method_hash = {}
23
+ klass.debug = false
24
+ super
25
+ end
26
+ end
27
+
28
+ # abi description for the method
29
+ def self.abi(desc:, args:, returns:)
30
+ args = args.map do |name, h|
31
+ h[:name] = name.to_s
32
+ h[:type] = h[:type].to_s.split('::').last.downcase
33
+ h
34
+ end
35
+
36
+ self.abi_method_hash = { desc: desc, args: args, returns: returns.to_s.split('::').last.downcase }
37
+ end
38
+
39
+ # specifies a TEAL subroutine that will be defined upon intialization
40
+ # @return [nil]
41
+ # @overload subroutine(name)
42
+ # @param name [Symbol] name of the subroutine and the method to use as the subroutine definition
43
+ #
44
+ # @overload subroutine(name)
45
+ # @param name [Symbol] name of the subroutine
46
+ #
47
+ # @yield [*args] the definition of the subroutine
48
+ def self.subroutine(name, &blk)
49
+ @subroutines[name] = (blk || instance_method(name))
50
+ abi_description.add_method(**({ name: name.to_s }.merge abi_method_hash)) unless abi_method_hash.empty?
51
+ @abi_method_hash = {}
52
+ nil
53
+ end
54
+
55
+ # specifies a method to be defined upon intialization that will be transpiled to TEAL when called
56
+ # @return [nil]
57
+ # @overload subroutine(name)
58
+ # @param name [Symbol] name of the method to use as the TEAL definition
59
+ #
60
+ # @overload subroutine(name)
61
+ # @param name [Symbol] name of the method
62
+ #
63
+ # @yield [*args] the definition of the TEAL method
64
+ def self.teal(name, &blk)
65
+ @teal_methods[name] = (blk || instance_method(name))
66
+ nil
67
+ end
68
+
69
+ # sets the `#pragma version`, defines teal methods, and defines subroutines
70
+ def initialize
71
+ @teal = TEAL.new ["#pragma version #{self.class.version}"]
72
+
73
+ self.class.subroutines.each do |name, blk|
74
+ define_subroutine name, blk
75
+ end
76
+
77
+ self.class.teal_methods.each do |name, blk|
78
+ define_teal_method name, blk
79
+ end
80
+ end
81
+
82
+ # defines a method that is transpiled to TEAL
83
+ # @param name [Symbol] name of the method
84
+ # @param definition [Lambda, Proc, UnboundMethod] the method definition
85
+ # @return [nil]
86
+ def define_teal_method(name, definition)
87
+ @teal.set_as_current
88
+
89
+ new_source = rewrite(definition.source, method_rewriter: true)
90
+
91
+ pre_string = StringIO.new
92
+
93
+ definition.parameters.reverse.each_with_index do |param, i|
94
+ param_name = param.last
95
+ pre_string.puts "store #{200 + i}"
96
+ pre_string.puts "comment('#{param_name}', inline: true)"
97
+ pre_string.puts "#{param_name} = -> { load #{200 + i}; comment('#{param_name}', inline: true) }"
98
+ end
99
+
100
+ new_source = "#{pre_string.string}#{new_source}"
101
+ define_singleton_method(name) do |*_args|
102
+ eval_tealrb(new_source, debug_context: "teal method: #{name}")
103
+ end
104
+
105
+ nil
106
+ end
107
+
108
+ # defines a TEAL subroutine
109
+ # @param name [Symbol] name of the method
110
+ # @param definition [Lambda, Proc, UnboundMethod] the method definition
111
+ # @return [nil]
112
+ def define_subroutine(name, definition)
113
+ @teal.set_as_current
114
+
115
+ @teal << 'b main' unless @teal.include? 'b main'
116
+
117
+ label(name) # add teal label
118
+
119
+ comment_params = definition.parameters.map(&:last).join(', ')
120
+ comment_content = "#{name}(#{comment_params})"
121
+ comment(comment_content, inline: true)
122
+
123
+ new_source = rewrite(definition.source, method_rewriter: true)
124
+
125
+ pre_string = StringIO.new
126
+
127
+ definition.parameters.reverse.each_with_index do |param, i|
128
+ param_name = param.last
129
+ pre_string.puts "store #{200 + i}"
130
+ pre_string.puts "comment('#{param_name}', inline: true)"
131
+ pre_string.puts "#{param_name} = -> { load #{200 + i}; comment('#{param_name}', inline: true) }"
132
+ end
133
+
134
+ new_source = "#{pre_string.string}#{new_source}retsub"
135
+ eval_tealrb(new_source, debug_context: "subroutine: #{name}")
136
+
137
+ define_singleton_method(name) do |*_args|
138
+ callsub(name)
139
+ end
140
+
141
+ nil
142
+ end
143
+
144
+ # insert comment into TEAL source
145
+ # @param content [String] content of the comment
146
+ # @param inline [Boolean] whether the comment should be on the previous TEAL line
147
+ def comment(content, inline: false)
148
+ if inline
149
+ last_line = @teal.pop
150
+ @teal << "#{last_line} // #{content}"
151
+ else
152
+ @teal << "// #{content}"
153
+ end
154
+ end
155
+
156
+ # inserts a string into TEAL source
157
+ # @param string [String] the string to insert
158
+ def placeholder(string)
159
+ @teal << string
160
+ end
161
+
162
+ # the hash of the abi description
163
+ def abi_hash
164
+ self.class.abi_description.to_h
165
+ end
166
+
167
+ # transpiles the given string to TEAL
168
+ # @param string [String] string to transpile
169
+ # @return [nil]
170
+ def compile_string(string)
171
+ @teal.set_as_current
172
+ eval_tealrb(rewrite(string), debug_context: 'compile_string')
173
+ nil
174
+ end
175
+
176
+ # transpiles #main
177
+ def compile
178
+ @teal.set_as_current
179
+ @teal << 'main:' if @teal.include? 'b main'
180
+ eval_tealrb(rewrite(method(:main).source, method_rewriter: true), debug_context: 'main')
181
+ end
182
+
183
+ private
184
+
185
+ def rewrite_with_rewriter(string, rewriter)
186
+ process_source = RuboCop::ProcessedSource.new(string, RUBY_VERSION[/\d\.\d/].to_f)
187
+ rewriter.new.rewrite(process_source.buffer, process_source.ast)
188
+ end
189
+
190
+ def rewrite(string, method_rewriter: false)
191
+ if self.class.debug
192
+ puts 'DEBUG: Rewriting the following code:'
193
+ puts string
194
+ puts ''
195
+ end
196
+
197
+ [ComparisonRewriter, IfRewriter, OpRewriter, AssignRewriter].each do |rw|
198
+ string = rewrite_with_rewriter(string, rw)
199
+ end
200
+
201
+ string = rewrite_with_rewriter(string, MethodRewriter) if method_rewriter
202
+
203
+ if self.class.debug
204
+ puts 'DEBUG: Resulting TEALrb code:'
205
+ puts string
206
+ puts ''
207
+ end
208
+
209
+ string
210
+ end
211
+
212
+ def eval_tealrb(s, debug_context:)
213
+ pre_teal = Array.new @teal
214
+
215
+ if self.class.debug
216
+ puts "DEBUG: Evaluating the following code (#{debug_context}):"
217
+ puts s
218
+ puts ''
219
+ end
220
+
221
+ eval s # rubocop:disable Security/Eval
222
+
223
+ if self.class.debug
224
+ puts "DEBUG: Resulting TEAL (#{debug_context}):"
225
+ puts Array.new(@teal) - pre_teal
226
+ puts ''
227
+ end
228
+ rescue SyntaxError, StandardError => e
229
+ @eval_tealrb_rescue_count ||= 0
230
+
231
+ eval_locations = e.backtrace.select {_1[/\(eval\)/]}
232
+ error_line = eval_locations[@eval_tealrb_rescue_count].split(':')[1].to_i
233
+
234
+ warn "'#{e}' when evaluating transpiled TEALrb source" if @eval_tealrb_rescue_count.zero?
235
+
236
+ warn "Backtrace location (#{@eval_tealrb_rescue_count + 1} / #{eval_locations.size}):"
237
+
238
+ @eval_tealrb_rescue_count += 1
239
+
240
+ s.lines.each_with_index do |line, i|
241
+ line_num = i + 1
242
+ if error_line == line_num
243
+ warn "=> #{line_num}: #{line}"
244
+ else
245
+ warn " #{line_num}: #{line}"
246
+ end
247
+ end
248
+
249
+ warn ''
250
+ raise e
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TEALrb
4
+ class IfBlock
5
+ class << self
6
+ attr_accessor :id
7
+ end
8
+
9
+ @id = 0
10
+
11
+ def initialize(teal, _cond, &blk)
12
+ @teal = teal
13
+ @else_count = 0
14
+ @id = self.class.id
15
+ @end_label = "if#{@id}_end:"
16
+
17
+ @id = self.class.id
18
+ self.class.id += 1
19
+
20
+ @teal << "bz if#{@id}_else0"
21
+ blk.call
22
+ @teal << "b if#{@id}_end"
23
+ @teal << "if#{@id}_else0:"
24
+ @teal << @end_label
25
+ end
26
+
27
+ def elsif(_cond, &blk)
28
+ @else_count += 1
29
+ @teal.delete @end_label
30
+ @teal << "bz if#{@id}_else#{@else_count}"
31
+ blk.call
32
+ @teal << "b if#{@id}_end"
33
+ @teal << "if#{@id}_else#{@else_count}:"
34
+ @teal << @end_label
35
+ self
36
+ end
37
+
38
+ def else(&blk)
39
+ @teal.delete @end_label
40
+ blk.call
41
+ @teal << @end_label
42
+ end
43
+ end
44
+ end