6.8. kink/CONTROL¶
This module provides some basic control structures.
6.8.1. CONTROL.while($continue? $body)¶
`while` calls $body repeatedly while $continue? returns true.
`while` first calls $continue? with no args. If $continue? returns true, `while` calls $body with no args, then goes back to the beginning. If $continue? returns false, `while` returns.
Preconditions
$continue? must be a thunk that returns a `bool`.
$body must be a thunk.
Example
:CONTROL.require_from('kink/')
:Vec <- [1 2 3]
CONTROL.while{ Vec.any? }{
:N = Vec.pop_front
stdout.print_line(N.repr)
}
# Output:
# 1
# 2
# 3
6.8.2. CONTROL.try($body $returned_cont $raised_cont)¶
`try` invokes $body with no args.
If the invocation of $body returns, `try` tail-calls $returned_cont with the result of $body.
If the invocation of $body raises an exception, `try` tail-calls $raised_cont with the exception.
Preconditions
$body must be a thunk.
$returned_cont must be a function which takes one arg.
$raised_cont must be a function which takes an `exception`.
Example
:TRACE.require_from('kink/')
:CONTROL.require_from('kink/')
:attempt_divide_10_by <- {(:Divisor)
CONTROL.try(
{ 10 // Divisor }
{(:Result)
stdout.print_line('result: {}'.format(Result))
}
{(:Exc)
Exc.desc_iter.each{(:Line)
stdout.print_line(Line)
}
}
)
}
attempt_divide_10_by(2) # => result: 5
attempt_divide_10_by(0)
# Output:
# -- main exception
# [..root..]
# ,,,
# [..kont tag..]
# [builtin:kink-mods/kink/CONTROL.kn L94 C10 body] :R = -->body
# {(stdin) L6 C10 op_intdiv} { 10 -->// Divisor }
# Num.op_intdiv(Divisor): zero division: 10 is divided by 0
6.8.3. CONTROL.with_return($body)¶
`with_return` calls $body with a function $return. In the invocation of $body, you can call $return with a thunk $exit_cont. When $return is called, the invocation of $body is aborted on the spot, and `with_return` tail-calls $exit_cont.
If the invocation of $body reaches the end without calling $return, `with_return` raises an exception.
Precondition
$body must be a function which takes a function $return.
Example
:CONTROL.require_from('kink/')
:gcd <- {(:X :Y)
CONTROL.with_return{(:return)
:R = X % Y
if(R == 0){
return{ Y }
}
return{ gcd(Y R) }
}
}
stdout.print_line(gcd(1071 462).repr) # => 21
The recursive call of `gcd` does not consume the stack, because it is called at the tail context of `with_return`, which is also the tail context of `gcd`. So it is a tail recursion.
Note that there is a much simpler version of `gcd` without `with_return`:
:gcd <- {(:X :Y)
:R = X % Y
if(R == 0
{ Y }
{ gcd(Y R) }
)
}
stdout.print_line(gcd(1071 462).repr) # => 21
Example: nested invocations
:CONTROL.require_from('kink/')
:R <- CONTROL.with_return{(:return)
CONTROL.with_return{(:inner_return)
return{ 42 }
_does_not_reach_here_
}
_does_not_reach_here_
}
stdout.print_line(R.repr) # => 42
6.8.4. CONTROL.with_finally($body)¶
`with_finally` calls $body with a function $finally. In the invocation of $body, you can call $finally an arbitrary number of times with a thunk $cleanup. $cleanup is guaranteed to be called in the following situations:
(1) When the invocation of $body returns
(2) When the invocation of $body is jumped out by $return function of CONTROL.with_return
(3) When the invocation of $body raises an exception
If $finally is called `n` times, with thunks $cleanup_1, $cleanup_2 ,,, $cleanup_n in this order, the thunks are guaranteed to be called in the reverse order: $cleanup_n ,,, $cleanup_2, $cleanup_1.
You must not jump out from the invocation of $cleanup by CONTROL.with_return.
Result
(1) When the invocation of $body returns
If no cleanup thunks raise an exception, `with_finally` returns the value returned by the invocation of $body.
If one or more cleanup thunks raise an exception, they are chained as a single exception, and `with_finally` raises the chained exception.
(2) When the invocation of $body is jumped out by $return function of CONTROL.with_return
If no cleanup thunks raise an exception, `with_finally` restarts the jumping out.
If one or more cleanup thunks raise an exception, they are chained as a single exception, and `with_finally` raises the chained exception.
(3) When the invocation of $body raises an exception
If no cleanup thunks raise an exception, `with_finally` raises the exception raised by $body.
If one or more cleanup thunks raise an exception, they are added to the chain of the exception raised by $body, and `with_finally` raises the chained exception.
Precondition
$body must be a function which takes a function $finally.
Example
:CONTROL.require_from('kink/')
:FILE.require_from('kink/io/')
# copy /etc/os-release to ./os-release.copy
CONTROL.with_finally{(:finally)
:In = FILE.open_to_read('/etc/os-release')
finally{ In.close }
:Out = FILE.open_to_write('os-release.copy')
finally{ Out.close }
Out.write(In.read_all)
}
6.8.5. CONTROL.with_return_finally($body)¶
`with_return_finally` calls $body with $return and $finally.
It is equivalent to nesting `with_return` and `with_finally` like this:
CONTROL.with_return{(:return)
CONTROL.with_finally{(:finally)
body($return $finally)
}
}
$return aborts the invocation of $body on the spot, and `with_return_finally` tail-calls the thunk passed to $return.
$finally registers a cleanup thunk. See `with_finally` for details on cleanup semantics.
Precondition
$body must be a function which takes $return and $finally.
Example
:CONTROL.require_from('kink/')
:CHARSET.require_from('kink/')
:FILE.require_from('kink/io/')
:scan_file <- {(:Fname)
CONTROL.with_return_finally{(:return :finally)
:S = FILE.open_to_scan(Fname CHARSET.utf8){(:C)
C.on_error{ return{ 'no such file' } }
}
finally{ S.close }
:Text = S.scan_all
return{ Text }
}
}
stdout.print_line(scan_file('/etc/locale.conf')) # => LANG=en_US.UTF-8
stdout.print_line(scan_file('/no/such/file')) # => no such file