fancy 0.8.0 → 0.9.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.
Files changed (111) hide show
  1. data/LICENSE +1 -1
  2. data/README.md +5 -4
  3. data/bin/fspec +19 -1
  4. data/bin/ifancy +139 -35
  5. data/boot/README +2 -9
  6. data/boot/extconf.rb +0 -1
  7. data/boot/fancy_ext/module.rb +5 -15
  8. data/boot/fancy_ext/thread.rb +22 -9
  9. data/boot/rbx-compiler/README +0 -4
  10. data/boot/rbx-compiler/parser/fancy_parser.bundle +0 -0
  11. data/boot/rbx-compiler/parser/parser.y +1 -0
  12. data/doc/api/fancy.css +1 -6
  13. data/doc/api/fancy.jsonp +1 -1
  14. data/doc/api/fdoc.js +2 -4
  15. data/doc/api/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  16. data/doc/api/images/ui-bg_flat_0_eeeeee_40x100.png +0 -0
  17. data/doc/api/images/ui-bg_flat_55_ffffff_40x100.png +0 -0
  18. data/doc/api/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  19. data/doc/api/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  20. data/doc/api/images/ui-bg_highlight-soft_100_f6f6f6_1x100.png +0 -0
  21. data/doc/api/images/ui-bg_highlight-soft_25_0073ea_1x100.png +0 -0
  22. data/doc/api/images/ui-bg_highlight-soft_50_dddddd_1x100.png +0 -0
  23. data/doc/api/images/ui-icons_0073ea_256x240.png +0 -0
  24. data/doc/api/images/ui-icons_454545_256x240.png +0 -0
  25. data/doc/api/images/ui-icons_666666_256x240.png +0 -0
  26. data/doc/api/images/ui-icons_ff0084_256x240.png +0 -0
  27. data/doc/api/images/ui-icons_ffffff_256x240.png +0 -0
  28. data/doc/api/index.html +5 -4
  29. data/doc/api/jquery-1.8.2.min.js +2 -0
  30. data/doc/api/jquery-ui-1.9.0.custom.min.css +5 -0
  31. data/doc/api/jquery-ui-1.9.0.custom.min.js +6 -0
  32. data/doc/features.md +8 -3
  33. data/examples/argv.fy +1 -1
  34. data/examples/closures.fy +1 -4
  35. data/examples/echo.fy +2 -2
  36. data/examples/guess_number.fy +18 -0
  37. data/examples/nested_classes.fy +3 -15
  38. data/lib/argv.fy +23 -18
  39. data/lib/array.fy +18 -37
  40. data/lib/block.fy +125 -0
  41. data/lib/boot.fy +1 -0
  42. data/lib/compiler/ast/block.fy +1 -1
  43. data/lib/compiler/ast/identifier.fy +1 -1
  44. data/lib/compiler/ast/message_send.fy +0 -13
  45. data/lib/compiler/ast/method_def.fy +1 -1
  46. data/lib/compiler/ast/singleton_method_def.fy +1 -0
  47. data/lib/compiler/ast/tuple_literal.fy +1 -1
  48. data/lib/compiler/command.fy +1 -1
  49. data/lib/compiler/compiler.fy +8 -6
  50. data/lib/contracts.fy +1 -1
  51. data/lib/directory.fy +1 -1
  52. data/lib/dynamic_slot_object.fy +1 -1
  53. data/lib/enumerable.fy +316 -25
  54. data/lib/enumerator.fy +11 -8
  55. data/lib/eval.fy +0 -3
  56. data/lib/fancy_spec.fy +27 -0
  57. data/lib/fdoc.fy +8 -8
  58. data/lib/file.fy +25 -1
  59. data/lib/hash.fy +91 -0
  60. data/lib/html.fy +40 -11
  61. data/lib/integer.fy +4 -0
  62. data/lib/main.fy +18 -11
  63. data/lib/object.fy +33 -7
  64. data/lib/option_parser.fy +20 -1
  65. data/lib/package/dependency.fy +8 -0
  66. data/lib/package/dependency_installer.fy +3 -6
  67. data/lib/package/handler.fy +4 -4
  68. data/lib/package/installer.fy +2 -5
  69. data/lib/package/list.fy +3 -4
  70. data/lib/parser/ext/parser.y +1 -0
  71. data/lib/proxies.fy +0 -2
  72. data/lib/queue.fy +7 -0
  73. data/lib/rbx.fy +1 -0
  74. data/lib/rbx/actor.fy +3 -1
  75. data/lib/rbx/alpha.fy +24 -0
  76. data/lib/rbx/array.fy +3 -1
  77. data/lib/rbx/class.fy +5 -8
  78. data/lib/rbx/date_time.fy +14 -0
  79. data/lib/rbx/file.fy +6 -0
  80. data/lib/rbx/hash.fy +42 -0
  81. data/lib/rbx/thread.fy +5 -7
  82. data/lib/string.fy +56 -4
  83. data/lib/symbol.fy +29 -1
  84. data/lib/time.fy +17 -0
  85. data/lib/vars.fy +4 -3
  86. data/lib/version.fy +1 -1
  87. data/ruby_lib/interactive/hilight.rb +125 -0
  88. data/tests/array.fy +19 -7
  89. data/tests/block.fy +103 -4
  90. data/tests/class.fy +31 -26
  91. data/tests/control_flow.fy +0 -1
  92. data/tests/dynamic_key_hash.fy +22 -1
  93. data/tests/enumerable.fy +239 -7
  94. data/tests/enumerator.fy +7 -0
  95. data/tests/file.fy +16 -0
  96. data/tests/future.fy +1 -11
  97. data/tests/future_proxy.fy +8 -0
  98. data/tests/hash.fy +132 -9
  99. data/tests/html.fy +30 -13
  100. data/tests/integer.fy +3 -0
  101. data/tests/method.fy +6 -11
  102. data/tests/object.fy +12 -5
  103. data/tests/option_parser.fy +12 -3
  104. data/tests/string.fy +69 -1
  105. data/tests/symbol.fy +24 -0
  106. metadata +42 -12
  107. data/boot/rsexp_pretty_printer.rb +0 -76
  108. data/doc/api/jquery-ui.min.js +0 -401
  109. data/doc/api/jquery.tools.min.js +0 -192
  110. data/doc/api/themeswitchertool.js +0 -250
  111. data/examples/future_sends.fy +0 -15
data/lib/string.fy CHANGED
@@ -57,10 +57,9 @@ class String {
57
57
  Indicates, if a @String@ consists only of whitespace.
58
58
  """
59
59
 
60
- self =~ /^\s*$/ if_true: {
61
- true
62
- } else: {
63
- false
60
+ match self {
61
+ case /^\s*$/ -> true
62
+ case _ -> false
64
63
  }
65
64
  }
66
65
 
@@ -232,4 +231,57 @@ class String {
232
231
 
233
232
  if: main? then: main_block else: else_block
234
233
  }
234
+
235
+ def snake_cased {
236
+ """
237
+ Returns a snake cased version of @self.
238
+ """
239
+
240
+ r1 = Regexp new("([A-Z]+)([A-Z][a-z])")
241
+ r2 = Regexp new("([a-z\d])([A-Z])")
242
+ gsub(r1,"\1_\2") gsub(r2,"\1_\2") tr("-", "_") lowercase
243
+ }
244
+
245
+ def camel_cased {
246
+ """
247
+ Returns camel cased version of @self which is expected
248
+ to be a snake cased @String@.
249
+ """
250
+
251
+ self split: "_" . map: @{ capitalize } . join
252
+ }
253
+
254
+ def uppercase? {
255
+ """
256
+ @return @true if @self is completely uppercase, @false otherwise.
257
+
258
+ Example:
259
+ \"F\" uppercase? # => true
260
+ \"FOO\" uppercase? # => true
261
+ \"f\” uppercase? # => false
262
+ \"Foo\" uppercase? # => false
263
+ """
264
+
265
+ { return false } if: blank?
266
+ uppercase == self
267
+ }
268
+
269
+ def lowercase? {
270
+ """
271
+ @return @true if @self is completely lowercase, @false otherwise.
272
+
273
+ Example:
274
+ \"f\” lowercase? # => true
275
+ \"foo\" lowercase? # => true
276
+ \"F\" lowercase? # => false
277
+ \"Foo\" lowercase? # => false
278
+ """
279
+
280
+ { return false } if: blank?
281
+ lowercase == self
282
+ }
283
+
284
+ def starts_with?: string {
285
+ from: 0 to: (string size - 1) == string
286
+ }
235
287
  }
data/lib/symbol.fy CHANGED
@@ -45,11 +45,39 @@ class Symbol {
45
45
  recv receive_message: self
46
46
  }
47
47
 
48
+ def call_with_receiver: receiver {
49
+ call: [receiver]
50
+ }
51
+
52
+ def call: args with_receiver: receiver {
53
+ call: $ args unshift: receiver
54
+ }
55
+
48
56
  def arity {
49
- 1
57
+ m = message_name to_s
58
+ match m {
59
+ case /^:[a-zA-Z0-9_]+$/ -> m count: |c| { c == ":" }
60
+ case /^:\W+$/ -> 2
61
+ case _ -> m count: |c| { c == ":" } + 1
62
+ }
50
63
  }
51
64
 
52
65
  def to_sym {
53
66
  self
54
67
  }
68
+
69
+ def to_block {
70
+ """
71
+ @return @Block@ that sends @self to its first argument, passing along any remaining arguments.
72
+
73
+ Example:
74
+ 'inspect to_block
75
+ # is equal to:
76
+ @{ inspect }
77
+ """
78
+
79
+ |args| {
80
+ call: args
81
+ }
82
+ }
55
83
  }
data/lib/time.fy ADDED
@@ -0,0 +1,17 @@
1
+ class Time {
2
+ def Time duration: block {
3
+ """
4
+ @block @Block to be called & timed.
5
+ @return @Float@ that is the duration (in seconds) of calling @block.
6
+
7
+ Calls @block and times the runtime duration of doing so in seconds.
8
+
9
+ Example:
10
+ Time duration: { Thread sleep: 1 } # => >= 1.0
11
+ """
12
+
13
+ start = Time now
14
+ block call
15
+ Time now - start
16
+ }
17
+ }
data/lib/vars.fy CHANGED
@@ -1,6 +1,7 @@
1
- *stdin* = STDIN
2
- *stdout* = STDOUT
3
- *stderr* = STDERR
1
+ *stdin* = STDIN
2
+ *stdout* = STDOUT
3
+ *stderr* = STDERR
4
+ *fancy_root* = File absolute_path: $ File join: (File dirname: __FILE__, "..")
4
5
 
5
6
  __AFTER__BOOTSTRAP__: {
6
7
  *stdin* documentation: """
data/lib/version.fy CHANGED
@@ -1,6 +1,6 @@
1
1
  class Fancy {
2
2
  VERSION_MAJOR = 0
3
- VERSION_MINOR = 8
3
+ VERSION_MINOR = 9
4
4
  VERSION_PATCH = 0
5
5
 
6
6
  VERSION = [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH] join: "."
@@ -0,0 +1,125 @@
1
+ # encoding: utf-8
2
+ module CodeRay
3
+ module Scanners
4
+
5
+ # Fancy scanner by swarley.
6
+ class Fancy < Scanner
7
+
8
+ register_for :fancy
9
+ file_extension 'fy'
10
+
11
+ SPECIAL_FORMS = %w[
12
+ def throw try catch class
13
+ ] # :nodoc:
14
+
15
+ CORE_FORMS = %w[
16
+ + - > < == != >= <= % ** * = && || =~
17
+ ] # :nodoc:
18
+
19
+ PREDEFINED_CONSTANTS = %w[
20
+ true false nil
21
+ ] # :nodoc:
22
+
23
+ IDENT_KIND = WordList.new(:ident).
24
+ add(SPECIAL_FORMS, :keyword).
25
+ add(CORE_FORMS, :keyword).
26
+ add(PREDEFINED_CONSTANTS, :predefined_constant)
27
+
28
+ KEYWORD_NEXT_TOKEN_KIND = WordList.new(nil).
29
+ add(%w[ def defn defn- definline defmacro defmulti defmethod defstruct defonce declare ], :function).
30
+ add(%w[ ns ], :namespace).
31
+ add(%w[ defprotocol defrecord ], :class)
32
+
33
+ BASIC_IDENTIFIER = /[a-zA-Z$%*\/_+!?&<>\-=]=?[a-zA-Z0-9$&*+!\/_?<>\-\#]*/
34
+ CLASS_IDENTIFIER = /[A-Z]+[A-Za-z0-9]*|[A-Z]+[A-Za-z0-9]\:\:|\:\:[A-Z]+[A-Za-z0-9]*/
35
+ IDENTIFIER = /(?!-\d)(?:(?:#{BASIC_IDENTIFIER}\.)*#{BASIC_IDENTIFIER}(?:\/#{BASIC_IDENTIFIER})?\.?)|\.\.?/
36
+ SYMBOL = /\'[A-z]+[A-z0-9\:\!\%\^\&\*\_\-\+\=\|\?\\\/\>\<\.]*/o
37
+ DIGIT = /\d+/
38
+ DIGIT10 = DIGIT
39
+ DIGIT16 = /[0-9a-f]/i
40
+ DIGIT8 = /[0-7]/
41
+ DIGIT2 = /[01]/
42
+ DECIMAL = /#{DIGIT}\.#{DIGIT}|-#{DIGIT}\.#{DIGIT}/
43
+ NUM = /(?:\-)*(?:#{DIGIT}|#{DIGIT16}|#{DIGIT8}|#{DIGIT2}|#{DECIMAL})/
44
+ MESSAGE = /[A-Za-z0-9\&\_]+?(?:\:|\?\:)/
45
+ CAPTURE = /\|.+?\|/
46
+
47
+ protected
48
+
49
+ def scan_tokens(encoder, options)
50
+ state = :initial
51
+ kind = nil
52
+
53
+ until eos?
54
+ case state
55
+ when :initial
56
+ if match = scan(/ \s+ | \n | , /x)
57
+ encoder.text_token match, :space
58
+ elsif match = scan(/\#.+?$/)
59
+ encoder.text_token match, :comment
60
+ elsif match = scan(/\{|\}|\@\{|\[|\]|\(|\)/)
61
+ encoder.text_token match, :operator
62
+ elsif match = scan(Regexp.new((%W(+ - @ > < == != >= <= % ** * = && || =~).map {|x| Regexp.escape x }).join '|'))
63
+ encoder.text_token match, :operator
64
+ elsif match = scan(/\.|\$/)
65
+ encoder.text_token match, :predefined_constant
66
+ elsif match = scan(CAPTURE)
67
+ encoder.text_token match, :predefined_constant
68
+ elsif match = scan(CLASS_IDENTIFIER)
69
+ encoder.text_token match, :constant
70
+ elsif match = scan(/\:\:/)
71
+ encoder.text_token match, :constant
72
+ elsif match = scan(MESSAGE)
73
+ encoder.text_token match, :keyword
74
+ elsif match = scan(/#{IDENTIFIER}/o)
75
+ kind = IDENT_KIND[match]
76
+ encoder.text_token match, kind
77
+ if rest? && kind == :keyword
78
+ if kind = KEYWORD_NEXT_TOKEN_KIND[match]
79
+ encoder.text_token match, :space if match = scan(/\s+/o)
80
+ encoder.text_token match, kind if match = scan(/#{IDENTIFIER}/o)
81
+ end
82
+ end
83
+ elsif match = scan(MESSAGE)
84
+ encoder.text_token match, :keyword
85
+ elsif match = scan(/#{SYMBOL}/o)
86
+ encoder.text_token match, :symbol
87
+ elsif match = scan(/\./)
88
+ encoder.text_token match, :operator
89
+ elsif match = scan(/ \# \^ #{IDENTIFIER} /ox)
90
+ encoder.text_token match, :type
91
+ elsif match = scan(/ (\#)? " /x)
92
+ state = self[1] ? :regexp : :string
93
+ encoder.begin_group state
94
+ encoder.text_token match, :delimiter
95
+ elsif match = scan(/#{NUM}/o) and not matched.empty?
96
+ encoder.text_token match, match[/[.e\/]/i] ? :float : :integer
97
+ else
98
+ encoder.text_token getch, :error
99
+ end
100
+
101
+ when :string, :regexp
102
+ if match = scan(/[^"\\]+|\\.?/)
103
+ encoder.text_token match, :content
104
+ elsif match = scan(/"/)
105
+ encoder.text_token match, :delimiter
106
+ encoder.end_group state
107
+ state = :initial
108
+ else
109
+ raise_inspect "else case \" reached; %p not handled." % peek(1),
110
+ encoder, state
111
+ end
112
+ else
113
+ raise 'else case reached'
114
+ end
115
+ end
116
+
117
+ if [:string, :regexp].include? state
118
+ encoder.end_group state
119
+ end
120
+
121
+ encoder
122
+ end
123
+ end
124
+ end
125
+ end
data/tests/array.fy CHANGED
@@ -356,10 +356,6 @@ FancySpec describe: Array with: {
356
356
  [1,2,3,4] any?: |x| { x > 4 } . is: false
357
357
  }
358
358
 
359
- it: "is selected from it with each index" with: 'select_with_index: when: {
360
- ["yooo",2,3,1,'foo,"bar"] select_with_index: |x i| { x is_a?: Fixnum } . is: [[2,1], [3,2], [1,3]]
361
- }
362
-
363
359
  it: "returns its remaining (all but the first) elements as a new Array" with: 'rest when: {
364
360
  [1,2,3,4] rest is: [2,3,4]
365
361
  [] rest is: []
@@ -529,15 +525,20 @@ FancySpec describe: Array with: {
529
525
  arr is: [1,5,4,2,3]
530
526
  }
531
527
 
532
- it: "sorts the array with a given comparison block" with: 'sort_by: when: {
528
+ it: "sorts the array with a given comparison block" with: 'sort: when: {
533
529
  arr = [1,5,4,2,3]
534
530
  sorted = [1,2,3,4,5]
535
- arr sort_by: |a b| { a <=> b } . is: sorted
531
+ arr sort: |a b| { a <=> b } . is: sorted
536
532
  arr is: [1,5,4,2,3]
537
533
 
538
534
  arr = [(1,2), (0,1), (3,0)]
539
535
  sorted = [(3,0), (0,1), (1,2)]
540
- arr sort_by: |a b| { a second <=> (b second) } . is: sorted
536
+ arr sort: |a b| { a second <=> (b second) } . is: sorted
537
+ }
538
+
539
+ it: "sorts the array by a given block" with: 'sort_by: when: {
540
+ arr = [(1,2), (0,1), (3,0)]
541
+ sorted = [(3,0), (0,1), (1,2)]
541
542
  arr sort_by: 'second . is: sorted
542
543
  }
543
544
 
@@ -550,4 +551,15 @@ FancySpec describe: Array with: {
550
551
  [] to_hash: @{ size } . is: <[]>
551
552
  [[1,2],[3,4,5]] to_hash: @{ size } . is: <[2 => [1,2], 3 => [3,4,5]]>
552
553
  }
554
+
555
+ it: "returns an sub-array within a given range" with: 'from:to: when: {
556
+ [] from: 0 to: 1 . is: []
557
+ [1] from: 0 to: 0 . is: [1]
558
+ [1] from: 0 to: 1 . is: [1]
559
+ [1] from: 0 to: 2 . is: [1]
560
+ [1] from: 0 to: -1 . is: [1]
561
+ [1] from: 0 to: -2 . is: []
562
+ [0,1,2,3] from: 0 to: 3 . is: [0,1,2,3]
563
+ [0,1,2,3] from: -1 to: 3 . is: [3]
564
+ }
553
565
  }
data/tests/block.fy CHANGED
@@ -32,6 +32,24 @@ FancySpec describe: Block with: {
32
32
  i is be: { i >= 10 }
33
33
  }
34
34
 
35
+ it: "calls a block while another is true or calls the alternative" with: 'while_true:else: when: {
36
+ i = 0
37
+ { i < 10 } while_true: {
38
+ i = i + 1
39
+ } else: {
40
+ i = "nope"
41
+ }
42
+ i is: 10
43
+
44
+ i = 0
45
+ { i > 10 } while_true: {
46
+ i = i + 1
47
+ } else: {
48
+ i = "nope"
49
+ }
50
+ i is: "nope"
51
+ }
52
+
35
53
  it: "calls a block while another is not true (boolean false)" with: 'while_false: when: {
36
54
  i = 0
37
55
  {i == 10} while_false: {
@@ -206,17 +224,17 @@ FancySpec describe: Block with: {
206
224
  block call: [42] with_receiver: (ClassD new) . is: "in ClassD#inspect: 42"
207
225
  }
208
226
 
209
- it: "calls a block using the ruby-send syntax" with: 'call: when: {
227
+ it: "calls a block using []" with: '[] when: {
210
228
  b = |x y| {
211
229
  x + y
212
230
  }
213
231
 
214
232
  b call: [2,3] . is: 5
215
- b(2,3) . is: 5
233
+ b[(2,3)] . is: 5
216
234
 
217
235
  b2 = |x| { x * 5 }
218
- b2("hello") is: ("hello" * 5)
219
- b2("foo") is: (b2 call: ["foo"])
236
+ b2["hello"] is: ("hello" * 5)
237
+ b2["foo"] is: (b2 call: ["foo"])
220
238
  }
221
239
 
222
240
  it: "dynamically creates a object with slots defined in a Block" with: 'to_object when: {
@@ -239,6 +257,26 @@ FancySpec describe: Block with: {
239
257
  }
240
258
  }
241
259
 
260
+ it: "dynamically creates a new object with slots recursively defined in blocks" with: 'to_object_deep when: {
261
+ o = {
262
+ name: "Sarah Connor"
263
+ age: 42
264
+ city: "Los Angeles"
265
+ persecuted_by: {
266
+ name: "The Terminator"
267
+ age: 'unknown
268
+ }
269
+ } to_object_deep
270
+
271
+ o name is: "Sarah Connor"
272
+ o age is: 42
273
+ o city is: "Los Angeles"
274
+ o persecuted_by do: {
275
+ name is: "The Terminator"
276
+ age is: 'unknown
277
+ }
278
+ }
279
+
242
280
  it: "dynamically creates a hash with keys and values defined in a Block" with: 'to_hash when: {
243
281
  { } to_hash is: <[]>
244
282
  { foo: "bar" } to_hash is: <['foo => "bar"]>
@@ -302,4 +340,65 @@ FancySpec describe: Block with: {
302
340
  ['city, "San Francisco"],
303
341
  'male, 'programmer, 'happy]
304
342
  }
343
+
344
+ it: "returns a Block that calls self then a given Block" with: 'then: when: {
345
+ a = []
346
+ block = { a << 1 } then: { a << 2 }
347
+ block call
348
+ a is: [1,2]
349
+ block call
350
+ a is: [1,2,1,2]
351
+ }
352
+
353
+ it: "returns a Block that calls itself after a given Block" with: 'after: when: {
354
+ a = []
355
+ block = { a << 1 } after: { a << 2}
356
+ block call
357
+ a is: [2,1]
358
+ block call
359
+ a is: [2,1,2,1]
360
+ }
361
+
362
+ it: "calls itself while logging errors to *stdout*" with: 'call_with_errors_logged when: {
363
+ io = StringIO new
364
+ let: '*stdout* be: io in: {
365
+ {
366
+ "hello" println
367
+ 2 / 0
368
+ } call_with_errors_logged
369
+ }
370
+ io string is: "hello\ndivided by 0\n"
371
+ }
372
+
373
+ it: "calls itself with arguments while logging errors to *stdout*" with: 'call_with_errors_logged: when: {
374
+ io = StringIO new
375
+ let: '*stdout* be: io in: {
376
+ |x y| {
377
+ "x: #{x} y: #{y}" println
378
+ 2 / 0
379
+ } call_with_errors_logged: [10, 20]
380
+ }
381
+ io string is: "x: 10 y: 20\ndivided by 0\n"
382
+ }
383
+
384
+ it: "calls itself while logging errors to a given IO object" with: 'call_with_errors_logged_to: when: {
385
+ io = StringIO new
386
+ {
387
+ 2 / 0
388
+ } call_with_errors_logged_to: io
389
+ io string is: "divided by 0\n"
390
+
391
+ io = StringIO new
392
+ { "fail!" raise! } call_with_errors_logged_to: io
393
+ io string is: "fail!\n"
394
+ }
395
+
396
+ it: "calls itself with arguments while logging errors to a given IO object" with: 'call:with_errors_logged_to: when: {
397
+ io = StringIO new
398
+ |x y| {
399
+ io println: "x: #{x} y: #{y}"
400
+ 2 / 0
401
+ } call: [10, 20] with_errors_logged_to: io
402
+ io string is: "x: 10 y: 20\ndivided by 0\n"
403
+ }
305
404
  }