tealrb 0.10.1 → 0.12.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.
@@ -3,91 +3,104 @@
3
3
  module TEALrb
4
4
  class Contract
5
5
  include TEALrb
6
- include Opcodes
7
- include Opcodes::AllOpcodes
6
+ include Opcodes::TEALOpcodes
7
+ include MaybeOps
8
+ include ByteOpcodes
8
9
  include ABI
9
10
  include Rewriters
11
+ include Enums
10
12
 
11
- attr_reader :teal
13
+ alias global_opcode global
12
14
 
13
- class << self
14
- attr_accessor :subroutines, :version, :teal_methods, :abi_method_hash, :abi_description, :debug,
15
- :disable_abi_routing
15
+ attr_reader :eval_location
16
+ attr_accessor :teal, :if_count
16
17
 
17
- private
18
+ class << self
19
+ attr_accessor :subroutines, :version, :abi_interface, :debug,
20
+ :disable_abi_routing, :method_hashes, :src_map
18
21
 
19
22
  def inherited(klass)
20
23
  klass.version = 6
21
24
  klass.subroutines = {}
22
- klass.teal_methods = {}
23
- klass.abi_description = ABI::ABIDescription.new
24
- klass.abi_method_hash = {}
25
+ klass.abi_interface = ABI::ABIDescription.new
26
+ klass.abi_interface.name = klass.to_s
27
+ klass.method_hashes = []
25
28
  klass.debug = false
26
29
  klass.disable_abi_routing = false
30
+ klass.src_map = true
27
31
  super
28
32
  end
29
- end
30
33
 
31
- # abi description for the method
32
- def self.abi(desc:, args:, returns:)
33
- args = args.map do |name, h|
34
- h[:name] = name.to_s
35
- h[:type] = h[:type].to_s.split('::').last.downcase
36
- h
37
- end
34
+ def parse(klass)
35
+ YARD::Tags::Library.define_tag('ABI Method', :abi)
36
+ YARD::Tags::Library.define_tag('Subroutine', :subroutine)
37
+ YARD::Tags::Library.define_tag('OnCompletes', :on_completion)
38
+ YARD::Tags::Library.define_tag('Create', :create)
39
+
40
+ YARD.parse Object.const_source_location(klass.to_s).first
41
+
42
+ parsed_methods = {}
43
+ YARD::Registry.all.each do |y|
44
+ next unless y.type == :method
45
+ next unless klass.instance_methods.include? y.name
46
+ next if y.parent.type == :class && y.parent.to_s != klass.to_s
47
+
48
+ if parsed_methods.keys.include? y.name
49
+ raise "#{y.name} defined in two locations: \n #{parsed_methods[y.name]}\n #{y.file}:#{y.line}"
50
+ end
51
+
52
+ parsed_methods[y.name] = "#{y.file}:#{y.line}"
53
+
54
+ tags = y.tags.map(&:tag_name)
38
55
 
39
- self.abi_method_hash = { desc: desc, args: args, returns: returns.to_s.split('::').last.downcase }
56
+ next unless tags.include?('abi') || tags.include?('subroutine')
57
+
58
+ method_hash = { name: y.name.to_s, desc: y.base_docstring, args: [], returns: { type: 'void' },
59
+ on_completion: ['NoOp'], create: y.has_tag?('create') }
60
+
61
+ y.tags.each do |t|
62
+ method_hash[:returns] = { type: t.types&.first&.downcase } if t.tag_name == 'return'
63
+
64
+ method_hash[:on_completion] = t.text[1..-2].split(',').map(&:strip) if t.tag_name == 'on_completion'
65
+ next unless t.tag_name == 'param'
66
+
67
+ method_hash[:args] << { name: t.name, type: t.types&.first&.downcase, desc: t.text }
68
+ end
69
+
70
+ klass.method_hashes << method_hash
71
+
72
+ klass.abi_interface.add_method(**method_hash) if tags.include? 'abi'
73
+ end
74
+ end
40
75
  end
41
76
 
42
- # specifies a TEAL subroutine that will be defined upon intialization
43
- # @return [nil]
44
- # @overload subroutine(name)
45
- # @param name [Symbol] name of the subroutine and the method to use as the subroutine definition
46
- #
47
- # @overload subroutine(name)
48
- # @param name [Symbol] name of the subroutine
49
- #
50
- # @yield [*args] the definition of the subroutine
51
- def self.subroutine(name, &blk)
52
- @subroutines[name] = (blk || instance_method(name))
53
- abi_description.add_method(**({ name: name.to_s }.merge abi_method_hash)) unless abi_method_hash.empty?
54
- @abi_method_hash = {}
55
- nil
77
+ TEALrb::Opcodes::BINARY_OPCODE_METHOD_MAPPING.each do |meth, opcode|
78
+ define_method(meth) do |other|
79
+ @contract.send(opcode, self, other)
80
+ end
56
81
  end
57
82
 
58
- # specifies a method to be defined upon intialization that will be transpiled to TEAL when called
59
- # @return [nil]
60
- # @overload subroutine(name)
61
- # @param name [Symbol] name of the method to use as the TEAL definition
62
- #
63
- # @overload subroutine(name)
64
- # @param name [Symbol] name of the method
65
- #
66
- # @yield [*args] the definition of the TEAL method
67
- def self.teal(name, &blk)
68
- @teal_methods[name] = (blk || instance_method(name))
69
- nil
83
+ TEALrb::Opcodes::UNARY_OPCODE_METHOD_MAPPING.each do |meth, opcode|
84
+ define_method(meth) do
85
+ @contract.send(opcode, self)
86
+ end
70
87
  end
71
88
 
72
89
  # sets the `#pragma version`, defines teal methods, and defines subroutines
73
90
  def initialize
74
- @teal = TEAL.new ["#pragma version #{self.class.version}"]
75
- IfBlock.id = 0
76
- @scratch = Scratch.new
91
+ self.class.parse(self.class)
77
92
 
78
- self.class.subroutines.each_key do |name|
79
- define_singleton_method(name) do |*_args|
80
- callsub(name)
81
- end
82
- end
93
+ @teal = TEAL.new ["#pragma version #{self.class.version}"], self
94
+ @scratch = Scratch.new self
83
95
 
84
- self.class.subroutines.each do |name, blk|
85
- define_subroutine name, blk
86
- end
96
+ @contract = self
97
+ @if_count = 0
87
98
 
88
- self.class.teal_methods.each do |name, blk|
89
- define_teal_method name, blk
99
+ self.class.method_hashes.each do |mh|
100
+ define_subroutine(mh[:name], method(mh[:name]))
90
101
  end
102
+
103
+ compile
91
104
  end
92
105
 
93
106
  VOID_OPS = %w[assert err return app_global_put b bnz bz store
@@ -95,47 +108,209 @@ module TEALrb
95
108
  log itxn_submit itxn_next].freeze
96
109
 
97
110
  def teal_source
98
- teal_lines = []
111
+ @teal.compact.join("\n")
112
+ end
99
113
 
100
- @teal.each_with_index do |line, i|
101
- ln = line.strip
114
+ def account(_account = nil)
115
+ @account ||= Account.new self
116
+ end
102
117
 
103
- teal_lines << '' if i != 0 && VOID_OPS.include?(@teal[i - 1][/\S+/])
118
+ alias accounts account
104
119
 
105
- if (ln[%r{\S+:($| //)}] && !ln[/^if\d+/]) || ln == 'b main' || ln[/^#/]
106
- teal_lines << ln
107
- elsif ln == 'retsub'
108
- teal_lines << " #{ln}"
109
- teal_lines << ''
110
- else
111
- teal_lines << " #{ln}"
120
+ def app(_app = nil)
121
+ @app ||= App.new self
122
+ end
123
+
124
+ alias apps app
125
+
126
+ def asset(_asset = nil)
127
+ @asset ||= Asset.new self
128
+ end
129
+
130
+ alias assets asset
131
+
132
+ def group_txn(_group_txn = nil)
133
+ @group_txn ||= GroupTxn.new self
134
+ end
135
+
136
+ alias group_txns group_txn
137
+
138
+ def global(field = nil, *_args)
139
+ if field
140
+ global_opcode(field)
141
+ else
142
+ @global ||= Global.new self
143
+ end
144
+ end
145
+
146
+ def this_txn
147
+ ThisTxn.new self
148
+ end
149
+
150
+ def box
151
+ Box.new self
152
+ end
153
+
154
+ def inner_txn
155
+ InnerTxn.new self
156
+ end
157
+
158
+ def logs
159
+ Logs.new self
160
+ end
161
+
162
+ def app_args
163
+ AppArgs.new self
164
+ end
165
+
166
+ def local
167
+ Local.new self
168
+ end
169
+
170
+ def txn_type
171
+ TxnType.new self
172
+ end
173
+
174
+ def generate_source_map(src)
175
+ last_location = nil
176
+ src_map_hash = {}
177
+
178
+ src.each_line.with_index do |ln, i|
179
+ if ln[/src_map:/]
180
+ last_location = ln[/(?<=src_map:)\S+/]
181
+ next
112
182
  end
183
+
184
+ src_map_hash[i + 1] ||= { location: last_location } if last_location
113
185
  end
114
186
 
115
- teal_lines.delete_if.with_index do |t, i|
116
- t.empty? && teal_lines[i - 1].empty?
187
+ compile_response = Algod.new.compile(src)
188
+
189
+ case compile_response.status
190
+ when 400
191
+ msg = JSON.parse(compile_response.body)['message']
192
+ e_msg = StringIO.new
193
+ e_msg.puts 'Error(s) while attempting to compile TEAL'
194
+ msg.each_line do |ln|
195
+ teal_line = ln.split(':').first.to_i
196
+ ln_msg = ln.split(':')[1..].join(':').strip
197
+ next if ln_msg == '"0"'
198
+
199
+ if src_map_hash[teal_line]
200
+ e_msg.puts " #{teal_line} - #{src_map_hash[teal_line][:location]}: #{ln_msg}"
201
+ else
202
+ e_msg.puts " #{ln}"
203
+ end
204
+ end
205
+ raise e_msg.string
206
+ when 200
207
+ json_body = JSON.parse(compile_response.body)
208
+ pc_mapping = json_body['sourcemap']['mapping']
209
+
210
+ pc_array = pc_mapping.split(';').map do |v|
211
+ SourceMap::VLQ.decode_array(v)[2]
212
+ end
213
+
214
+ last_line = 1
215
+
216
+ line_to_pc = {}
217
+ pc_to_line = {}
218
+ pc_array.each_with_index do |line_delta, pc|
219
+ last_line += line_delta.to_i
220
+
221
+ next if last_line == 1
222
+
223
+ line_to_pc[last_line] ||= []
224
+ line_to_pc[last_line] << pc
225
+ pc_to_line[pc] = last_line
226
+ end
227
+
228
+ line_to_pc.each do |line, pcs|
229
+ src_map_hash[line][:pcs] = pcs if src_map_hash[line]
230
+ end
117
231
  end
118
232
 
119
- teal_lines.join("\n")
233
+ src_map_hash
120
234
  end
121
235
 
122
- # return the input without transpiling to TEAL
123
- def rb(input)
124
- input
236
+ def dump(directory = Dir.pwd, name: self.class.to_s.downcase, abi: true, src_map: true)
237
+ src = formatted_teal
238
+
239
+ File.write(File.join(directory, "#{name}.teal"), src)
240
+ File.write(File.join(directory, "#{name}.abi.json"), JSON.pretty_generate(abi_hash)) if abi
241
+
242
+ return unless src_map
243
+
244
+ src_map_json = JSON.pretty_generate(generate_source_map(src))
245
+ File.write(File.join(directory, "#{name}.src_map.json"), src_map_json)
125
246
  end
126
247
 
127
- # defines a method that is transpiled to TEAL
128
- # @param name [Symbol] name of the method
129
- # @param definition [Lambda, Proc, UnboundMethod] the method definition
130
- # @return [nil]
131
- def define_teal_method(name, definition)
132
- new_source = generate_method_source(name, definition)
248
+ def formatted_teal
249
+ new_lines = []
250
+ comments = []
133
251
 
134
- define_singleton_method(name) do |*_args|
135
- eval_tealrb(new_source, debug_context: "teal method: #{name}")
252
+ @teal.compact.each do |ln|
253
+ ln = ln.strip
254
+
255
+ next if ln.empty?
256
+
257
+ if ln[/^#pragma/]
258
+ new_lines << { text: ln, void: true, comments: comments }
259
+ comments = []
260
+ elsif ln[%r{^//}]
261
+ comments << ln
262
+ else
263
+ op = ln.split.first
264
+ label_regex = %r{\S+:($| //)}
265
+
266
+ label_type = label_number = label_end = nil
267
+
268
+ if ln[label_regex]
269
+ label_type = ln[/^if/] || ln[/^while/]
270
+ label_number = ln[/(?<=^if)\d+/] || ln[/(?<=^while)\d+/]
271
+ label_end = ln[/_end:/]
272
+ end
273
+
274
+ new_lines << { text: ln, void: VOID_OPS.include?(op), comments: comments, label: ln[label_regex],
275
+ label_type: label_type, label_number: label_number, label_end: label_end }
276
+ comments = []
277
+ end
136
278
  end
137
279
 
138
- nil
280
+ output = []
281
+ indent_level = 0
282
+ current_labels = { 'while' => [], 'if' => [] }
283
+
284
+ new_lines.each do |ln|
285
+ indent_level = 1 if ln[:label] && indent_level.zero? && !ln[:label_type]
286
+
287
+ if ln[:label_type]
288
+ indent_level += 1 unless current_labels[ln[:label_type]].include?(ln[:label_number])
289
+ current_labels[ln[:label_type]] << ln[:label_number]
290
+ end
291
+
292
+ ln_indent_level = indent_level
293
+ ln_indent_level -= 1 if ln[:label]
294
+
295
+ output << '' if !output.last&.empty? && (ln[:label] || ln[:comments].any?)
296
+
297
+ ln[:comments].each { output << (("\t" * ln_indent_level) + _1) }
298
+ output << (("\t" * ln_indent_level) + ln[:text])
299
+
300
+ output << '' if ln[:void] && !output.last.empty?
301
+
302
+ next unless ln[:label_end]
303
+
304
+ indent_level -= 1
305
+ current_labels[ln[:label_type]].delete ln[:label_number]
306
+ end
307
+
308
+ output.join("\n")
309
+ end
310
+
311
+ # return the input without transpiling to TEAL
312
+ def rb(input)
313
+ input
139
314
  end
140
315
 
141
316
  # defines a TEAL subroutine
@@ -143,11 +318,15 @@ module TEALrb
143
318
  # @param definition [Lambda, Proc, UnboundMethod] the method definition
144
319
  # @return [nil]
145
320
  def define_subroutine(name, definition)
321
+ return if method(name).source_location.first == __FILE__
322
+
323
+ @eval_location = method(name).source_location
324
+
146
325
  define_singleton_method(name) do |*_args|
147
326
  callsub(name)
148
327
  end
149
328
 
150
- TEAL.instance << 'b main' unless TEAL.instance.include? 'b main'
329
+ @teal << 'b main' unless @teal.include? 'b main'
151
330
 
152
331
  label(name) # add teal label
153
332
 
@@ -155,7 +334,9 @@ module TEALrb
155
334
  comment_content = "#{name}(#{comment_params})"
156
335
  comment(comment_content, inline: true)
157
336
 
158
- new_source = generate_method_source(name, definition)
337
+ method_hash = self.class.method_hashes.find { _1[:name] == name.to_s }
338
+ new_source = generate_subroutine_source(definition, method_hash)
339
+
159
340
  new_source = "#{new_source}retsub"
160
341
 
161
342
  eval_tealrb(new_source, debug_context: "subroutine: #{name}")
@@ -169,23 +350,22 @@ module TEALrb
169
350
  def comment(content, inline: false)
170
351
  content = " #{content}" unless content[0] == ' '
171
352
  if inline
172
- last_line = TEAL.instance.pop
173
- TEAL.instance << "#{last_line} //#{content}"
353
+ last_line = @teal.pop
354
+ @teal << "#{last_line} //#{content}"
174
355
  else
175
- TEAL.instance << '' unless TEAL.instance.last[%r{^//}]
176
- TEAL.instance << "//#{content}"
356
+ @teal << "//#{content}"
177
357
  end
178
358
  end
179
359
 
180
360
  # inserts a string into TEAL source
181
361
  # @param string [String] the string to insert
182
362
  def placeholder(string)
183
- TEAL.instance << string
363
+ @teal << string
184
364
  end
185
365
 
186
366
  # the hash of the abi description
187
367
  def abi_hash
188
- self.class.abi_description.to_h
368
+ self.class.abi_interface.to_h
189
369
  end
190
370
 
191
371
  # transpiles the given string to TEAL
@@ -199,51 +379,157 @@ module TEALrb
199
379
  # transpiles #main and routes abi methods. To disable abi routing, set `@disable_abi_routing` to true in your
200
380
  # Contract subclass
201
381
  def compile
202
- TEAL.instance << 'main:' if TEAL.instance.include? 'b main'
382
+ @teal << 'main:' if @teal.include? 'b main'
203
383
  route_abi_methods unless self.class.disable_abi_routing
204
- eval_tealrb(rewrite(method(:main).source, method_rewriter: true), debug_context: 'main') if respond_to? :main
384
+ return unless respond_to? :main
385
+
386
+ @eval_location = method(:main).source_location
387
+
388
+ eval_tealrb(
389
+ rewrite(
390
+ method(:main).source,
391
+ method_rewriter: true
392
+ ),
393
+ debug_context: 'main'
394
+ )
395
+ end
396
+
397
+ def main
398
+ nil
399
+ end
400
+
401
+ def compiled_program
402
+ compile_response = Algod.new.compile(formatted_teal)
403
+
404
+ generate_source_map(formatted_teal) if compile_response.status != 200
405
+
406
+ JSON.parse(compile_response.body)['result']
205
407
  end
206
408
 
207
409
  private
208
410
 
209
411
  def route_abi_methods
210
- self.class.abi_description.methods.each do |meth|
412
+ self.class.abi_interface.methods.each_with_index do |meth, i|
211
413
  signature = "#{meth[:name]}(#{meth[:args].map { _1[:type] }.join(',')})#{meth[:returns][:type]}"
212
414
  selector = OpenSSL::Digest.new('SHA512-256').hexdigest(signature)[..7]
213
415
 
214
- IfBlock.new(AppArgs[0] == byte(selector)) do
215
- callsub(meth[:name])
216
- approve
217
- end
416
+ app_args[int(0)] == byte(selector) # rubocop:disable Lint/Void
417
+ bz("abi_routing#{i}")
418
+ callsub(meth[:name])
419
+ approve
420
+ label("abi_routing#{i}")
218
421
  end
219
422
  end
220
423
 
221
424
  def generate_method_source(name, definition)
222
425
  new_source = rewrite(definition.source, method_rewriter: true)
223
426
 
224
- pre_string = StringIO.new
427
+ pre_string = []
225
428
 
226
429
  scratch_names = []
227
430
  definition.parameters.reverse.each_with_index do |param, _i|
228
- param_name = param.last
431
+ param_name = param.first
229
432
  scratch_name = [name, param_name].map(&:to_s).join(': ')
230
433
  scratch_names << scratch_name
231
434
 
232
- pre_string.puts "@scratch.store('#{scratch_name}')"
233
- pre_string.puts "#{param_name} = -> { @scratch['#{scratch_name}'] }"
435
+ pre_string << "@scratch.store('#{scratch_name}')"
436
+ pre_string << "#{param_name} = -> { @scratch['#{scratch_name}'] }"
437
+ end
438
+
439
+ "#{pre_string.join(';')};#{new_source}"
440
+ end
441
+
442
+ def generate_subroutine_source(definition, method_hash)
443
+ new_source = rewrite(definition.source, method_rewriter: true)
444
+
445
+ pre_string = []
446
+
447
+ scratch_names = []
448
+
449
+ txn_types = %w[txn pay keyreg acfg axfer afrz appl]
450
+
451
+ args = method_hash[:args] || []
452
+ arg_types = args.map { _1[:type] } || []
453
+ txn_params = arg_types.select { txn_types.include? _1 }.count
454
+ app_param_index = -1
455
+ asset_param_index = -1
456
+ account_param_index = 0
457
+ args_index = 0
458
+
459
+ method_hash[:on_completion].each_with_index do |oc, i|
460
+ this_txn.on_completion
461
+ int(oc)
462
+ equal
463
+ boolean_or unless i.zero?
234
464
  end
465
+ assert
466
+
467
+ assert(this_txn.application_id == (int(0))) if method_hash[:create]
468
+
469
+ if abi_hash['methods'].find { _1[:name] == method_hash[:name].to_s }
470
+ definition.parameters.each_with_index do |param, i|
471
+ param_name = param.last
472
+
473
+ scratch_name = "#{definition.original_name}: #{param_name} [#{arg_types[i] || 'any'}] #{
474
+ args[i][:desc] if args[i]}"
475
+ scratch_names << scratch_name
476
+
477
+ type = arg_types[i]&.downcase
478
+
479
+ if txn_types.include? type
480
+ @scratch[scratch_name] = group_txns[this_txn.group_index.subtract int(txn_params)]
481
+ txn_params -= 1
482
+ elsif type == 'application'
483
+ @scratch[scratch_name] = apps[app_param_index += 1]
484
+ elsif type == 'asset'
485
+ @scratch[scratch_name] = assets[asset_param_index += 1]
486
+ elsif type == 'account'
487
+ @scratch[scratch_name] = accounts[account_param_index += 1]
488
+ elsif type == 'uint64'
489
+ @scratch[scratch_name] = btoi(app_args[args_index += 1])
490
+ else
491
+ @scratch[scratch_name] = app_args[args_index += 1]
492
+ end
493
+
494
+ pre_string << "#{param_name} = -> {@scratch['#{scratch_name}'] }"
495
+ end
496
+
497
+ else
498
+ args.reverse!
499
+ arg_types.reverse!
500
+
501
+ definition.parameters.reverse.each_with_index do |param, i|
502
+ param_name = param.last
503
+
504
+ scratch_name = "#{definition.original_name}: #{param_name} [#{arg_types[i] || 'any'}] #{
505
+ args[i][:desc] if args[i]}"
506
+ scratch_names << scratch_name
507
+
508
+ type = arg_types[i]&.downcase
509
+
510
+ if txn_types.include? type
511
+ @scratch[scratch_name] = group_txn
512
+ elsif type == 'application'
513
+ @scratch[scratch_name] = application
514
+ elsif type == 'asset'
515
+ @scratch[scratch_name] = asset
516
+ elsif type == 'account'
517
+ @scratch[scratch_name] = account
518
+ else
519
+ @scratch.store(scratch_name)
520
+ end
521
+
522
+ pre_string << "#{param_name} = -> { @scratch['#{scratch_name}'] }"
523
+ end
235
524
 
236
- post_string = StringIO.new
237
- scratch_names.each do |n|
238
- post_string.puts "@scratch.delete '#{n}'"
239
525
  end
240
526
 
241
- "#{pre_string.string}#{new_source}#{post_string.string}"
527
+ "#{pre_string.join(';')};#{new_source}"
242
528
  end
243
529
 
244
530
  def rewrite_with_rewriter(string, rewriter)
245
531
  process_source = RuboCop::ProcessedSource.new(string, RUBY_VERSION[/\d\.\d/].to_f)
246
- rewriter.new.rewrite(process_source)
532
+ rewriter.new.rewrite(process_source, self)
247
533
  end
248
534
 
249
535
  def rewrite(string, method_rewriter: false)
@@ -270,7 +556,7 @@ module TEALrb
270
556
  end
271
557
 
272
558
  def eval_tealrb(s, debug_context:)
273
- pre_teal = Array.new TEAL.instance
559
+ pre_teal = Array.new @teal
274
560
 
275
561
  if self.class.debug
276
562
  puts "DEBUG: Evaluating the following code (#{debug_context}):"
@@ -282,32 +568,20 @@ module TEALrb
282
568
 
283
569
  if self.class.debug
284
570
  puts "DEBUG: Resulting TEAL (#{debug_context}):"
285
- puts Array.new(TEAL.instance) - pre_teal
571
+ puts Array.new(@teal) - pre_teal
286
572
  puts ''
287
573
  end
288
574
  rescue SyntaxError, StandardError => e
289
575
  @eval_tealrb_rescue_count ||= 0
290
576
 
291
577
  eval_locations = e.backtrace.select { _1[/\(eval\)/] }
292
- error_line = eval_locations[@eval_tealrb_rescue_count].split(':')[1].to_i
293
-
294
- warn "'#{e}' when evaluating transpiled TEALrb source" if @eval_tealrb_rescue_count.zero?
295
-
296
- warn "Backtrace location (#{@eval_tealrb_rescue_count + 1} / #{eval_locations.size}):"
297
-
298
- @eval_tealrb_rescue_count += 1
299
-
300
- s.lines.each_with_index do |line, i|
301
- line_num = i + 1
302
- if error_line == line_num
303
- warn "=> #{line_num}: #{line}"
304
- else
305
- warn " #{line_num}: #{line}"
306
- end
578
+ if eval_locations[@eval_tealrb_rescue_count]
579
+ error_line = eval_locations[@eval_tealrb_rescue_count].split(':')[1].to_i
307
580
  end
308
581
 
309
- warn ''
310
- raise e
582
+ msg = "#{@eval_location.first}:#{error_line + @eval_location.last}"
583
+
584
+ raise e, "#{msg}: #{e}"
311
585
  end
312
586
  end
313
587
  end