<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Mu - 075channel.mu</title>
<meta name="Generator" content="Vim/7.4">
<meta name="plugin-version" content="vim7.4_v2">
<meta name="syntax" content="none">
<meta name="settings" content="use_css,pre_wrap,no_foldcolumn,expand_tabs,prevent_copy=">
<meta name="colorscheme" content="minimal">
<style type="text/css">
<!--
pre { white-space: pre-wrap; font-family: monospace; color: #eeeeee; background-color: #080808; }
body { font-size: 12pt; font-family: monospace; color: #eeeeee; background-color: #080808; }
* { font-size: 12pt; font-size: 1em; }
.muData { color: #ffff00; }
.muControl { color: #c0a020; }
.Delimiter { color: #800080; }
.SalientComment { color: #00ffff; }
.Comment { color: #9090ff; }
.Constant { color: #00a0a0; }
.Special { color: #c00000; }
.CommentedCode { color: #6c6c6c; }
.muRecipe { color: #ff8700; }
.muScenario { color: #00af00; }
-->
</style>
<script type='text/javascript'>
<!--
-->
</script>
</head>
<body>
<pre id='vimCodeElement'>
<span class="Comment"># Mu synchronizes between routines using channels rather than locks, like</span>
<span class="Comment"># Erlang and Go.</span>
<span class="Comment">#</span>
<span class="Comment"># Key properties of channels:</span>
<span class="Comment">#</span>
<span class="Comment"># a) Writing to a full channel or reading from an empty one will put the</span>
<span class="Comment"># current routine in 'waiting' state until the operation can be completed.</span>
<span class="Comment">#</span>
<span class="Comment"># b) Writing to a channel implicitly performs a deep copy. This prevents</span>
<span class="Comment"># addresses from being shared between routines, and therefore eliminates all</span>
<span class="Comment"># possibility of race conditions.</span>
<span class="muScenario">scenario</span> channel [
run [
<span class="Constant">local-scope</span>
source:&:source:num, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
<span class="Constant">10</span>:num/<span class="Special">raw</span>, <span class="Constant">11</span>:bool/<span class="Special">raw</span>, source<span class="Special"> <- </span>read source
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">34</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># read was successful</span>
]
]
<span class="muData">container</span> channel:_elem [
lock:bool <span class="Comment"># inefficient but simple: serialize all reads as well as writes</span>
first-full:num <span class="Comment"># for write</span>
first-free:num <span class="Comment"># for read</span>
<span class="Comment"># A circular buffer contains values from index first-full up to (but not</span>
<span class="Comment"># including) index first-empty. The reader always modifies it at first-full,</span>
<span class="Comment"># while the writer always modifies it at first-empty.</span>
data:&:@:_elem
]
<span class="Comment"># Since channels have two ends, and since it's an error to use either end from</span>
<span class="Comment"># multiple routines, let's distinguish the ends.</span>
<span class="muData">container</span> source:_elem [
chan:&:channel:_elem
]
<span class="muData">container</span> sink:_elem [
chan:&:channel:_elem
]
<span class="muRecipe">def</span> new-channel capacity:num<span class="muRecipe"> -> </span>in:&:source:_elem, out:&:sink:_elem [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
result:&:channel:_elem<span class="Special"> <- </span>new <span class="Delimiter">{</span>(channel _elem): type<span class="Delimiter">}</span>
*result<span class="Special"> <- </span>put *result, <span class="Constant">first-full:offset</span>, <span class="Constant">0</span>
*result<span class="Special"> <- </span>put *result, <span class="Constant">first-free:offset</span>, <span class="Constant">0</span>
capacity<span class="Special"> <- </span>add capacity, <span class="Constant">1</span> <span class="Comment"># unused slot for 'full?' below</span>
data:&:@:_elem<span class="Special"> <- </span>new <span class="Constant">_elem:type</span>, capacity
*result<span class="Special"> <- </span>put *result, <span class="Constant">data:offset</span>, data
in<span class="Special"> <- </span>new <span class="Delimiter">{</span>(source _elem): type<span class="Delimiter">}</span>
*in<span class="Special"> <- </span>put *in, <span class="Constant">chan:offset</span>, result
out<span class="Special"> <- </span>new <span class="Delimiter">{</span>(sink _elem): type<span class="Delimiter">}</span>
*out<span class="Special"> <- </span>put *out, <span class="Constant">chan:offset</span>, result
]
<span class="muRecipe">def</span> write out:&:sink:_elem, val:_elem<span class="muRecipe"> -> </span>out:&:sink:_elem [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
assert out, <span class="Constant">[write to null channel]</span>
chan:&:channel:_elem<span class="Special"> <- </span>get *out, <span class="Constant">chan:offset</span>
<span class="Constant"> <channel-write-initial></span>
<span class="Comment"># block until lock is acquired AND queue has room</span>
lock:location<span class="Special"> <- </span>get-location *chan, <span class="Constant">lock:offset</span>
<span class="CommentedCode">#? $print [write], 10/newline</span>
<span class="Delimiter">{</span>
<span class="CommentedCode">#? $print [trying to acquire lock for writing], 10/newline</span>
wait-for-reset-then-set lock
<span class="CommentedCode">#? $print [lock acquired for writing], 10/newline</span>
full?:bool<span class="Special"> <- </span>channel-full? chan
<span class="muControl">break-unless</span> full?
<span class="CommentedCode">#? $print [but channel is full; relinquishing lock], 10/newline</span>
<span class="Comment"># channel is full; relinquish lock and give a reader the opportunity to</span>
<span class="Comment"># create room on it</span>
reset lock
current-routine-is-blocked
switch <span class="Comment"># avoid spinlocking</span>
<span class="muControl">loop</span>
<span class="Delimiter">}</span>
current-routine-is-unblocked
<span class="CommentedCode">#? $print [performing write], 10/newline</span>
<span class="Comment"># store a deep copy of val</span>
circular-buffer:&:@:_elem<span class="Special"> <- </span>get *chan, <span class="Constant">data:offset</span>
free:num<span class="Special"> <- </span>get *chan, <span class="Constant">first-free:offset</span>
val-copy:_elem<span class="Special"> <- </span>deep-copy val <span class="Comment"># on this instruction rests all Mu's concurrency-safety</span>
*circular-buffer<span class="Special"> <- </span>put-index *circular-buffer, free, val-copy
<span class="Comment"># mark its slot as filled</span>
free<span class="Special"> <- </span>add free, <span class="Constant">1</span>
<span class="Delimiter">{</span>
<span class="Comment"># wrap free around to 0 if necessary</span>
len:num<span class="Special"> <- </span>length *circular-buffer
at-end?:bool<span class="Special"> <- </span>greater-or-equal free, len
<span class="muControl">break-unless</span> at-end?
free<span class="Special"> <- </span>copy <span class="Constant">0</span>
<span class="Delimiter">}</span>
<span class="Comment"># write back</span>
*chan<span class="Special"> <- </span>put *chan, <span class="Constant">first-free:offset</span>, free
<span class="CommentedCode">#? $print [relinquishing lock after writing], 10/newline</span>
reset lock
]
<span class="muRecipe">def</span> read in:&:source:_elem<span class="muRecipe"> -> </span>result:_elem, eof?:bool, in:&:source:_elem [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
assert in, <span class="Constant">[read on null channel]</span>
eof?<span class="Special"> <- </span>copy <span class="Constant">0/false</span> <span class="Comment"># default result</span>
chan:&:channel:_elem<span class="Special"> <- </span>get *in, <span class="Constant">chan:offset</span>
<span class="Comment"># block until lock is acquired AND queue has data</span>
lock:location<span class="Special"> <- </span>get-location *chan, <span class="Constant">lock:offset</span>
<span class="CommentedCode">#? $print [read], 10/newline</span>
<span class="Delimiter">{</span>
<span class="CommentedCode">#? $print [trying to acquire lock for reading], 10/newline</span>
wait-for-reset-then-set lock
<span class="CommentedCode">#? $print [lock acquired for reading], 10/newline</span>
empty?:bool<span class="Special"> <- </span>channel-empty? chan
<span class="muControl">break-unless</span> empty?
<span class="CommentedCode">#? $print [but channel is empty; relinquishing lock], 10/newline</span>
<span class="Comment"># channel is empty; relinquish lock and give a writer the opportunity to</span>
<span class="Comment"># add to it</span>
reset lock
current-routine-is-blocked
<span class="Constant"> <channel-read-empty></span>
switch <span class="Comment"># avoid spinlocking</span>
<span class="muControl">loop</span>
<span class="Delimiter">}</span>
current-routine-is-unblocked
<span class="Comment"># pull result off</span>
full:num<span class="Special"> <- </span>get *chan, <span class="Constant">first-full:offset</span>
circular-buffer:&:@:_elem<span class="Special"> <- </span>get *chan, <span class="Constant">data:offset</span>
result<span class="Special"> <- </span>index *circular-buffer, full
<span class="Comment"># clear the slot</span>
empty:&:_elem<span class="Special"> <- </span>new <span class="Constant">_elem:type</span>
*circular-buffer<span class="Special"> <- </span>put-index *circular-buffer, full, *empty
<span class="Comment"># mark its slot as empty</span>
full<span class="Special"> <- </span>add full, <span class="Constant">1</span>
<span class="Delimiter">{</span>
<span class="Comment"># wrap full around to 0 if necessary</span>
len:num<span class="Special"> <- </span>length *circular-buffer
at-end?:bool<span class="Special"> <- </span>greater-or-equal full, len
<span class="muControl">break-unless</span> at-end?
full<span class="Special"> <- </span>copy <span class="Constant">0</span>
<span class="Delimiter">}</span>
<span class="Comment"># write back</span>
*chan<span class="Special"> <- </span>put *chan, <span class="Constant">first-full:offset</span>, full
<span class="CommentedCode">#? $print [relinquishing lock after reading], 10/newline</span>
reset lock
]
<span class="muRecipe">def</span> clear in:&:source:_elem<span class="muRecipe"> -> </span>in:&:source:_elem [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
chan:&:channel:_elem<span class="Special"> <- </span>get *in, <span class="Constant">chan:offset</span>
<span class="Delimiter">{</span>
empty?:bool<span class="Special"> <- </span>channel-empty? chan
<span class="muControl">break-if</span> empty?
_, _, in<span class="Special"> <- </span>read in
<span class="Delimiter">}</span>
]
<span class="muScenario">scenario</span> channel-initialization [
run [
<span class="Constant">local-scope</span>
source:&:source:num<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span>
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
<span class="Constant">10</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span>
<span class="Constant">11</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span>
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># first-full</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># first-free</span>
]
]
<span class="muScenario">scenario</span> channel-write-increments-free [
<span class="Constant">local-scope</span>
_, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span>
run [
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
chan:&:channel:num<span class="Special"> <- </span>get *sink, <span class="Constant">chan:offset</span>
<span class="Constant">10</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span>
<span class="Constant">11</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span>
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># first-full</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># first-free</span>
]
]
<span class="muScenario">scenario</span> channel-read-increments-full [
<span class="Constant">local-scope</span>
source:&:source:num, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
run [
_, _, source<span class="Special"> <- </span>read source
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
<span class="Constant">10</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span>
<span class="Constant">11</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span>
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># first-full</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># first-free</span>
]
]
<span class="muScenario">scenario</span> channel-wrap [
<span class="Constant">local-scope</span>
<span class="Comment"># channel with just 1 slot</span>
source:&:source:num, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">1/capacity</span>
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
<span class="Comment"># write and read a value</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
_, _, source<span class="Special"> <- </span>read source
run [
<span class="Comment"># first-free will now be 1</span>
<span class="Constant">10</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span>
<span class="Constant">11</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span>
<span class="Comment"># write second value, verify that first-free wraps</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
<span class="Constant">20</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span>
<span class="Comment"># read second value, verify that first-full wraps</span>
_, _, source<span class="Special"> <- </span>read source
<span class="Constant">30</span>:num/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span>
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># first-free after first write</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># first-full after first read</span>
<span class="Constant">20</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># first-free after second write, wrapped</span>
<span class="Constant">30</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># first-full after second read, wrapped</span>
]
]
<span class="muScenario">scenario</span> channel-new-empty-not-full [
run [
<span class="Constant">local-scope</span>
source:&:source:num<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span>
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
<span class="Constant">10</span>:bool/<span class="Special">raw <- </span>channel-empty? chan
<span class="Constant">11</span>:bool/<span class="Special">raw <- </span>channel-full? chan
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># empty?</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># full?</span>
]
]
<span class="muScenario">scenario</span> channel-write-not-empty [
<span class="Constant">local-scope</span>
source:&:source:num, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span>
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
run [
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
<span class="Constant">10</span>:bool/<span class="Special">raw <- </span>channel-empty? chan
<span class="Constant">11</span>:bool/<span class="Special">raw <- </span>channel-full? chan
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># empty?</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># full?</span>
]
]
<span class="muScenario">scenario</span> channel-write-full [
<span class="Constant">local-scope</span>
source:&:source:num, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">1/capacity</span>
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
run [
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
<span class="Constant">10</span>:bool/<span class="Special">raw <- </span>channel-empty? chan
<span class="Constant">11</span>:bool/<span class="Special">raw <- </span>channel-full? chan
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># empty?</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># full?</span>
]
]
<span class="muScenario">scenario</span> channel-read-not-full [
<span class="Constant">local-scope</span>
source:&:source:num, sink:&:sink:num<span class="Special"> <- </span>new-channel <span class="Constant">1/capacity</span>
chan:&:channel:num<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span>
run [
_, _, source<span class="Special"> <- </span>read source
<span class="Constant">10</span>:bool/<span class="Special">raw <- </span>channel-empty? chan
<span class="Constant">11</span>:bool/<span class="Special">raw <- </span>channel-full? chan
]
memory-should-contain [
<span class="Constant">10</span><span class="Special"> <- </span><span class="Constant">1</span> <span class="Comment"># empty?</span>
<span class="Constant">11</span><span class="Special"> <- </span><span class="Constant">0</span> <span class="Comment"># full?</span>
]
]
<span class="SalientComment">## cancelling channels</span>
<span class="Comment"># every channel comes with a boolean signifying if it's been closed</span>
<span class="Comment"># initially this boolean is false</span>
<span class="muData">container</span> channel:_elem [
closed?:bool
]
<span class="Comment"># a channel can be closed from either the source or the sink</span>
<span class="Comment"># both routines can modify the 'closed?' bit, but they can only ever set it, so this is a benign race</span>
<span class="muRecipe">def</span> close x:&:source:_elem<span class="muRecipe"> -> </span>x:&:source:_elem [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
chan:&:channel:_elem<span class="Special"> <- </span>get *x, <span class="Constant">chan:offset</span>
*chan<span class="Special"> <- </span>put *chan, <span class="Constant">closed?:offset</span>, <span class="Constant">1/true</span>
]
<span class="muRecipe">def</span> close x:&:sink:_elem<span class="muRecipe"> -> </span>x:&:sink:_elem [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
chan:&:channel:_elem<span class="Special"> <- </span>get *x, <span class="Constant">chan:offset</span>
*chan<span class="Special"> <- </span>put *chan, <span class="Constant">closed?:offset</span>, <span class="Constant">1/true</span>
]
<span class="Comment"># once a channel is closed from one side, no further operations are expected from that side</span>
<span class="Comment"># if a channel is closed for reading,</span>
<span class="Comment"># no further writes will be let through</span>
<span class="Comment"># if a channel is closed for writing,</span>
<span class="Comment"># future reads continue until the channel empties,</span>
<span class="Comment"># then the channel is also closed for reading</span>
<span class="muRecipe">after</span> <span class="Constant"><channel-write-initial></span> [
closed?:bool<span class="Special"> <- </span>get *chan, <span class="Constant">closed?:offset</span>
<span class="muControl">return-if</span> closed?
]
<span class="muRecipe">after</span> <span class="Constant"><channel-read-empty></span> [
closed?:bool<span class="Special"> <- </span>get *chan, <span class="Constant">closed?:offset</span>
<span class="Delimiter">{</span>
<span class="muControl">break-unless</span> closed?
empty-result:&:_elem<span class="Special"> <- </span>new <span class="Constant">_elem:type</span>
current-routine-is-unblocked
<span class="muControl">return</span> *empty-result, <span class="Constant">1/true</span>
<span class="Delimiter">}</span>
]
<span class="SalientComment">## helpers</span>
<span class="Comment"># An empty channel has first-empty and first-full both at the same value.</span>
<span class="muRecipe">def</span> channel-empty? chan:&:channel:_elem<span class="muRecipe"> -> </span>result:bool [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
<span class="Comment"># return chan.first-full == chan.first-free</span>
full:num<span class="Special"> <- </span>get *chan, <span class="Constant">first-full:offset</span>
free:num<span class="Special"> <- </span>get *chan, <span class="Constant">first-free:offset</span>
result<span class="Special"> <- </span>equal full, free
]
<span class="Comment"># A full channel has first-empty just before first-full, wasting one slot.</span>
<span class="Comment"># (Other alternatives: <a href="https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction)">https://en.wikipedia.org/wiki/Circular_buffer#Full_.2F_Empty_Buffer_Distinction)</a></span>
<span class="muRecipe">def</span> channel-full? chan:&:channel:_elem<span class="muRecipe"> -> </span>result:bool [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
<span class="Comment"># tmp = chan.first-free + 1</span>
tmp:num<span class="Special"> <- </span>get *chan, <span class="Constant">first-free:offset</span>
tmp<span class="Special"> <- </span>add tmp, <span class="Constant">1</span>
<span class="Delimiter">{</span>
<span class="Comment"># if tmp == chan.capacity, tmp = 0</span>
len:num<span class="Special"> <- </span>capacity chan
at-end?:bool<span class="Special"> <- </span>greater-or-equal tmp, len
<span class="muControl">break-unless</span> at-end?
tmp<span class="Special"> <- </span>copy <span class="Constant">0</span>
<span class="Delimiter">}</span>
<span class="Comment"># return chan.first-full == tmp</span>
full:num<span class="Special"> <- </span>get *chan, <span class="Constant">first-full:offset</span>
result<span class="Special"> <- </span>equal full, tmp
]
<span class="muRecipe">def</span> capacity chan:&:channel:_elem<span class="muRecipe"> -> </span>result:num [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
q:&:@:_elem<span class="Special"> <- </span>get *chan, <span class="Constant">data:offset</span>
result<span class="Special"> <- </span>length *q
]
<span class="Comment"># helper for channels of characters in particular</span>
<span class="muRecipe">def</span> buffer-lines in:&:source:char, buffered-out:&:sink:char<span class="muRecipe"> -> </span>buffered-out:&:sink:char, in:&:source:char [
<span class="Constant">local-scope</span>
<span class="Constant">load-ingredients</span>
<span class="Comment"># repeat forever</span>
eof?:bool<span class="Special"> <- </span>copy <span class="Constant">0/false</span>
<span class="Delimiter">{</span>
line:&:buffer<span class="Special"> <- </span>new-buffer <span class="Constant">30</span>
<span class="Comment"># read characters from 'in' until newline, copy into line</span>
<span class="Delimiter">{</span>
<span class="Constant"> +next-character</span>
c:char, eof?:bool, in<span class="Special"> <- </span>read in
<span class="muControl">break-if</span> eof?
<span class="Comment"># drop a character on backspace</span>
<span class="Delimiter">{</span>
<span class="Comment"># special-case: if it's a backspace</span>
backspace?:bool<span class="Special"> <- </span>equal c, <span class="Constant">8</span>
<span class="muControl">break-unless</span> backspace?
<span class="Comment"># drop previous character</span>
<span class="Delimiter">{</span>
buffer-length:num<span class="Special"> <- </span>get *line, <span class="Constant">length:offset</span>
buffer-empty?:bool<span class="Special"> <- </span>equal buffer-length, <span class="Constant">0</span>
<span class="muControl">break-if</span> buffer-empty?
buffer-length<span class="Special"> <- </span>subtract buffer-length, <span class="Constant">1</span>
*line<span class="Special"> <- </span>put *line, <span class="Constant">length:offset</span>, buffer-length
<span class="Delimiter">}</span>
<span class="Comment"># and don't append this one</span>
<span class="muControl">loop</span> <span class="Constant">+next-character:label</span>
<span class="Delimiter">}</span>
<span class="Comment"># append anything else</span>
line<span class="Special"> <- </span>append line, c
line-done?:bool<span class="Special"> <- </span>equal c, <span class="Constant">10/newline</span>
<span class="muControl">break-if</span> line-done?
<span class="muControl">loop</span>
<span class="Delimiter">}</span>
<span class="Comment"># copy line into 'buffered-out'</span>
i:num<span class="Special"> <- </span>copy <span class="Constant">0</span>
line-contents:text<span class="Special"> <- </span>get *line, <span class="Constant">data:offset</span>
max:num<span class="Special"> <- </span>get *line, <span class="Constant">length:offset</span>
<span class="Delimiter">{</span>
done?:bool<span class="Special"> <- </span>greater-or-equal i, max
<span class="muControl">break-if</span> done?
c:char<span class="Special"> <- </span>index *line-contents, i
buffered-out<span class="Special"> <- </span>write buffered-out, c
i<span class="Special"> <- </span>add i, <span class="Constant">1</span>
<span class="muControl">loop</span>
<span class="Delimiter">}</span>
<span class="Delimiter">{</span>
<span class="muControl">break-unless</span> eof?
buffered-out<span class="Special"> <- </span>close buffered-out
<span class="muControl">return</span>
<span class="Delimiter">}</span>
<span class="muControl">loop</span>
<span class="Delimiter">}</span>
]
<span class="muScenario">scenario</span> buffer-lines-blocks-until-newline [
run [
<span class="Constant">local-scope</span>
source:&:source:char, sink:&:sink:char<span class="Special"> <- </span>new-channel <span class="Constant">10/capacity</span>
_, buffered-stdin:&:sink:char/buffered-stdin<span class="Special"> <- </span>new-channel <span class="Constant">10/capacity</span>
buffered-chan:&:channel:char<span class="Special"> <- </span>get *buffered-stdin, <span class="Constant">chan:offset</span>
empty?:bool<span class="Special"> <- </span>channel-empty? buffered-chan
assert empty?, <span class="Constant">[ </span>
<span class="Constant">F buffer-lines-blocks-until-newline: channel should be empty after init]</span>
<span class="Comment"># buffer stdin into buffered-stdin, try to read from buffered-stdin</span>
buffer-routine:num<span class="Special"> <- </span>start-running buffer-lines, source, buffered-stdin
wait-for-routine-to-block buffer-routine
empty?<span class="Special"> <- </span>channel-empty? buffered-chan
assert empty?:bool, <span class="Constant">[ </span>
<span class="Constant">F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up]</span>
<span class="Comment"># write 'a'</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">97/a</span>
restart buffer-routine
wait-for-routine-to-block buffer-routine
empty?<span class="Special"> <- </span>channel-empty? buffered-chan
assert empty?:bool, <span class="Constant">[ </span>
<span class="Constant">F buffer-lines-blocks-until-newline: channel should be empty after writing 'a']</span>
<span class="Comment"># write 'b'</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">98/b</span>
restart buffer-routine
wait-for-routine-to-block buffer-routine
empty?<span class="Special"> <- </span>channel-empty? buffered-chan
assert empty?:bool, <span class="Constant">[ </span>
<span class="Constant">F buffer-lines-blocks-until-newline: channel should be empty after writing 'b']</span>
<span class="Comment"># write newline</span>
sink<span class="Special"> <- </span>write sink, <span class="Constant">10/newline</span>
restart buffer-routine
wait-for-routine-to-block buffer-routine
empty?<span class="Special"> <- </span>channel-empty? buffered-chan
data-emitted?:bool<span class="Special"> <- </span>not empty?
assert data-emitted?, <span class="Constant">[ </span>
<span class="Constant">F buffer-lines-blocks-until-newline: channel should contain data after writing newline]</span>
trace <span class="Constant">1</span>, <span class="Constant">[test]</span>, <span class="Constant">[reached end]</span>
]
trace-should-contain [
test: reached end
]
]
</pre>
</body>
</html>
<!-- vim: set foldmethod=manual : -->