tealrb 0.10.1 → 0.12.0

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