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 +7 -0
- data/lib/tealrb/abi.rb +43 -0
- data/lib/tealrb/cmd_line/teal2tealrb.rb +66 -0
- data/lib/tealrb/constants.rb +5 -0
- data/lib/tealrb/contract.rb +253 -0
- data/lib/tealrb/if_block.rb +44 -0
- data/lib/tealrb/opcode_modules.rb +701 -0
- data/lib/tealrb/opcodes.rb +661 -0
- data/lib/tealrb/patches.rb +6 -0
- data/lib/tealrb/rewriters.rb +149 -0
- data/lib/tealrb.rb +51 -0
- metadata +152 -0
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,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
|