# -*- Tcl -*- package require nx package require nx::test # just 8.6 or newer if {[info command yield] eq ""} return # # Test coroutine / yield # nx::Test case number-generator { nx::Object create ::numbers { # set instance variable used in coroutine set :delta 2 :public object method ++ {} { yield set i 0 while 1 { yield $i incr i ${:delta} } } } # create coroutine coroutine nextNumber ::numbers ++ set ::j 0 # use coroutine for {set i 0} {$i < 10} {incr i} { incr ::j [nextNumber] } # remove coroutine rename nextNumber {} ? {set ::j} 90 } # # Test coroutine / yield # nx::Test case enumerator1 { # # enumerator with yield in a single class # nx::Class create Enumerator { :property members:0..n :public method yielder {} { yield [info coroutine] foreach m ${:members} { yield $m } return -level 2 -code break } :public method next {} {${:coro}} :method init {} { :require namespace set :coro [coroutine [self]::coro [self] yielder] } } # # Some application class using the enumerator (just used for easy # testing) # nx::Class create Foo { :public method sum {} { set sum 0 set e [Enumerator new -members {1 2 3}] while 1 { incr sum [$e next] } return $sum } :create f1 } ? {f1 sum} 6 } nx::Test case enumerator2 { # # Define separate classes for Yielder and Enumerator # nx::Class create Yielder { :property {block ";"} :variable continuation "" # # make apply available as a method # :public alias apply ::apply # # The method "yielder" is the working horse for next. We need this # since the interface of Tcl's coroutines is based on a separate # cmd for continuation in the coroutine. The block can be # configured by application classes. # :public method yielder {} { yield [info coroutine] eval ${:block} return -level 2 -code break } # # The method "next" simply forwards to the continuation # :public method next {} {${:continuation}} # # The method "each" is based on the method "next" and applies the # value returned by next to the lambda expression # :public method each {var body} { while 1 { uplevel [list set $var [:next]] uplevel $body } } # # When a yielder is generated, we create automatically a coroutine # for it. The coroutine is placed under the current object, this # ensures simple cleanup (but is most probably not the fastest # variant, since we have to require a namespace). # :method init {} { :require namespace set :continuation [coroutine [self]::coro [self] yielder] } } # # The class "Enumerator" provides some application logic for the # class "Yielder". We use here a list of elements as base # representation. # nx::Class create Enumerator -superclass Yielder { :property members:0..n :property {block { foreach m ${:members} { yield $m } }} } # # Some application class using the enumerator (just used for easy # testing) # nx::Class create Foo { # test Enumerator.next :public method sum {} { set sum 0 set e [Enumerator new -members {1 2 3}] while 1 { incr sum [$e next] } return $sum } :public method set {var} { set :$var } # test Enumerator.each :public method concat {} { set string "-" set i 0 set e [Enumerator new -members {a be bu}] $e each x { append string $x-([incr i])- } return $string } :create f1 } ? {f1 sum} 6 ? {f1 concat} "-a-(1)-be-(2)-bu-(3)-" # # Define a class ATeam that uses "Enumerator", refines the method # "each" and adds another method "concat" # nx::Class create ATeam -superclass Enumerator { # # Overload "each" to show overloading. Here, we simply capitalize # the memebers in the "each" method. # :public method each {var body} { while 1 { set value [string totitle [:next]] uplevel [list set $var $value] uplevel $body } } # Define some arbitrary method using ATeam.each :public method concat {} { set string "-" :each x { append string $x- } return $string } } ATeam create a1 -members {alice bob ceasar} ? {a1 concat } "-Alice-Bob-Ceasar-" } # # apply # nx::Test case apply { # Register apply as an alias ::nx::Object public alias apply ::apply ::nx::Object create o { # Set an object variable set :delta 100 # Define a standard map function based on apply :public object method map {lambda values} { set result {} foreach value $values { lappend result [:apply $lambda $value] } return $result } :object method foo {x} {return $x-$x} } # Two examples from the apply man page ? {o map {x {return [string length $x]:$x}} {a bb ccc dddd}} \ "1:a 2:bb 3:ccc 4:dddd" ? {o map {x {expr {$x**2 + 3*$x - 2}}} {-4 -3 -2 -1 0 1 2 3 4}} \ "2 -2 -4 -4 -2 2 8 16 26" ## Test case accessing object specific variable #? {o map {x {::nsf::__db_show_stack; return [expr {$x * ${:delta}}]}} {-4 -3 -2 -1 0 1 2 3 4}} \ # "-400 -300 -200 -100 0 100 200 300 400" # Test case accessing object specific variable ? {o map {x {expr {$x * ${:delta}}}} {-4 -3 -2 -1 0 1 2 3 4}} \ "-400 -300 -200 -100 0 100 200 300 400" # Test case calling own method via apply ? {o map {x {:foo $x}} {hello world}} \ "hello-hello world-world" }