tealrb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|