diff options
author | Kartik K. Agaram <vc@akkartik.com> | 2016-07-05 01:08:00 -0700 |
---|---|---|
committer | Kartik K. Agaram <vc@akkartik.com> | 2016-07-05 01:08:00 -0700 |
commit | 298f8065857630e414d84e4ee785a6d17e5f99bb (patch) | |
tree | 8880a092ab59850a6f821ba892f3904ab464431c /html/074channel.mu.html | |
parent | f28f2636c6707e1a33bebacafd0486f4965978ea (diff) | |
download | mu-298f8065857630e414d84e4ee785a6d17e5f99bb.tar.gz |
3102
Diffstat (limited to 'html/074channel.mu.html')
-rw-r--r-- | html/074channel.mu.html | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/html/074channel.mu.html b/html/074channel.mu.html new file mode 100644 index 00000000..e2214f12 --- /dev/null +++ b/html/074channel.mu.html @@ -0,0 +1,491 @@ +<!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 - 074channel.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; } +.muScenario { color: #00af00; } +.muData { color: #ffff00; } +.SalientComment { color: #00ffff; } +.Delimiter { color: #800080; } +.muRecipe { color: #ff8700; } +.Comment { color: #9090ff; } +.Constant { color: #00a0a0; } +.Special { color: #c00000; } +.muControl { color: #c0a020; } +--> +</style> + +<script type='text/javascript'> +<!-- + +--> +</script> +</head> +<body> +<pre id='vimCodeElement'> +<span class="Comment"># Mu synchronizes using channels rather than locks, like Erlang and Go.</span> +<span class="Comment">#</span> +<span class="Comment"># The two ends of a channel will usually belong to different routines, but</span> +<span class="Comment"># each end should (currently) only be used by a single one. Don't try to read</span> +<span class="Comment"># from or write to it from multiple routines at once.</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, to prevent</span> +<span class="Comment"># addresses from being shared between routines, thereby causing race</span> +<span class="Comment"># conditions.</span> + +<span class="muScenario">scenario</span> channel [ + run [ + <span class="Constant">local-scope</span> + source:address:source:number, sink:address:sink:number<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>:number/<span class="Special">raw</span>, <span class="Constant">11</span>:boolean/<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 [ + <span class="Comment"># To avoid locking, writer and reader will never write to the same location.</span> + <span class="Comment"># So channels will include fields in pairs, one for the writer and one for the</span> + <span class="Comment"># reader.</span> + first-full:number <span class="Comment"># for write</span> + first-free:number <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:address:array:_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:address:channel:_elem +] + +<span class="muData">container</span> sink:_elem [ + chan:address:channel:_elem +] + +<span class="muRecipe">def</span> new-channel capacity:number<span class="muRecipe"> -> </span>in:address:source:_elem, out:address:sink:_elem [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + result:address: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:address:array:_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:address:sink:_elem, val:_elem<span class="muRecipe"> -> </span>out:address:sink:_elem [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + chan:address:channel:_elem<span class="Special"> <- </span>get *out, <span class="Constant">chan:offset</span> +<span class="Constant"> <channel-write-initial></span> + <span class="Delimiter">{</span> + <span class="Comment"># block if chan is full</span> + full:boolean<span class="Special"> <- </span>channel-full? chan + <span class="muControl">break-unless</span> full + full-address:location<span class="Special"> <- </span>get-location *chan, <span class="Constant">first-full:offset</span> + wait-for-location full-address + <span class="Delimiter">}</span> + <span class="Comment"># store a deep copy of val</span> + circular-buffer:address:array:_elem<span class="Special"> <- </span>get *chan, <span class="Constant">data:offset</span> + free:number<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:number<span class="Special"> <- </span>length *circular-buffer + at-end?:boolean<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="muRecipe">def</span> read in:address:source:_elem<span class="muRecipe"> -> </span>result:_elem, fail?:boolean, in:address:source:_elem [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + fail?<span class="Special"> <- </span>copy <span class="Constant">0/false</span> <span class="Comment"># default status</span> + chan:address:channel:_elem<span class="Special"> <- </span>get *in, <span class="Constant">chan:offset</span> + <span class="Delimiter">{</span> + <span class="Comment"># block if chan is empty</span> + empty?:boolean<span class="Special"> <- </span>channel-empty? chan + <span class="muControl">break-unless</span> empty? +<span class="Constant"> <channel-read-empty></span> + free-address:location<span class="Special"> <- </span>get-location *chan, <span class="Constant">first-free:offset</span> + wait-for-location free-address + <span class="Delimiter">}</span> + <span class="Comment"># pull result off</span> + full:number<span class="Special"> <- </span>get *chan, <span class="Constant">first-full:offset</span> + circular-buffer:address:array:_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:address:_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:number<span class="Special"> <- </span>length *circular-buffer + at-end?:boolean<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="muRecipe">def</span> clear in:address:source:_elem<span class="muRecipe"> -> </span>in:address:source:_elem [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + chan:address:channel:_elem<span class="Special"> <- </span>get *in, <span class="Constant">chan:offset</span> + <span class="Delimiter">{</span> + empty?:boolean<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:address:source:number<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span> + chan:address:channel:number<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span> + <span class="Constant">10</span>:number/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span> + <span class="Constant">11</span>:number/<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 [ + run [ + <span class="Constant">local-scope</span> + _, sink:address:sink:number<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span> + sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span> + chan:address:channel:number<span class="Special"> <- </span>get *sink, <span class="Constant">chan:offset</span> + <span class="Constant">10</span>:number/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span> + <span class="Constant">11</span>:number/<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 [ + run [ + <span class="Constant">local-scope</span> + source:address:source:number, sink:address:sink:number<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span> + sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span> + _, _, source<span class="Special"> <- </span>read source + chan:address:channel:number<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span> + <span class="Constant">10</span>:number/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-full:offset</span> + <span class="Constant">11</span>:number/<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 [ + run [ + <span class="Constant">local-scope</span> + <span class="Comment"># channel with just 1 slot</span> + source:address:source:number, sink:address:sink:number<span class="Special"> <- </span>new-channel <span class="Constant">1/capacity</span> + chan:address:channel:number<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 + <span class="Comment"># first-free will now be 1</span> + <span class="Constant">10</span>:number/<span class="Special">raw <- </span>get *chan, <span class="Constant">first-free:offset</span> + <span class="Constant">11</span>:number/<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>:number/<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>:number/<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:address:source:number<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span> + chan:address:channel:number<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span> + <span class="Constant">10</span>:boolean/<span class="Special">raw <- </span>channel-empty? chan + <span class="Constant">11</span>:boolean/<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 [ + run [ + source:address:source:number, sink:address:sink:number<span class="Special"> <- </span>new-channel <span class="Constant">3/capacity</span> + chan:address:channel:number<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span> + sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span> + <span class="Constant">10</span>:boolean/<span class="Special">raw <- </span>channel-empty? chan + <span class="Constant">11</span>:boolean/<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 [ + run [ + <span class="Constant">local-scope</span> + source:address:source:number, sink:address:sink:number<span class="Special"> <- </span>new-channel <span class="Constant">1/capacity</span> + chan:address:channel:number<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span> + sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span> + <span class="Constant">10</span>:boolean/<span class="Special">raw <- </span>channel-empty? chan + <span class="Constant">11</span>:boolean/<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 [ + run [ + <span class="Constant">local-scope</span> + source:address:source:number, sink:address:sink:number<span class="Special"> <- </span>new-channel <span class="Constant">1/capacity</span> + chan:address:channel:number<span class="Special"> <- </span>get *source, <span class="Constant">chan:offset</span> + sink<span class="Special"> <- </span>write sink, <span class="Constant">34</span> + _, _, source<span class="Special"> <- </span>read source + <span class="Constant">10</span>:boolean/<span class="Special">raw <- </span>channel-empty? chan + <span class="Constant">11</span>:boolean/<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?:boolean +] + +<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:address:source:_elem<span class="muRecipe"> -> </span>x:address:source:_elem [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + chan:address: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:address:sink:_elem<span class="muRecipe"> -> </span>x:address:sink:_elem [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + chan:address: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?:boolean<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?:boolean<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:address:_elem<span class="Special"> <- </span>new <span class="Constant">_elem:type</span> + <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:address:channel:_elem<span class="muRecipe"> -> </span>result:boolean [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + <span class="Comment"># return chan.first-full == chan.first-free</span> + full:number<span class="Special"> <- </span>get *chan, <span class="Constant">first-full:offset</span> + free:number<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:address:channel:_elem<span class="muRecipe"> -> </span>result:boolean [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + <span class="Comment"># tmp = chan.first-free + 1</span> + tmp:number<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:number<span class="Special"> <- </span>capacity chan + at-end?:boolean<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:number<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:address:channel:_elem<span class="muRecipe"> -> </span>result:number [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + q:address:array:_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:address:source:character, buffered-out:address:sink:character<span class="muRecipe"> -> </span>buffered-out:address:sink:character, in:address:source:character [ + <span class="Constant">local-scope</span> + <span class="Constant">load-ingredients</span> + <span class="Comment"># repeat forever</span> + eof?:boolean<span class="Special"> <- </span>copy <span class="Constant">0/false</span> + <span class="Delimiter">{</span> + line:address: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:character, eof?:boolean, 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?:boolean<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:number<span class="Special"> <- </span>get *line, <span class="Constant">length:offset</span> + buffer-empty?:boolean<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?:boolean<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:number<span class="Special"> <- </span>copy <span class="Constant">0</span> + line-contents:address:array:character<span class="Special"> <- </span>get *line, <span class="Constant">data:offset</span> + max:number<span class="Special"> <- </span>get *line, <span class="Constant">length:offset</span> + <span class="Delimiter">{</span> + done?:boolean<span class="Special"> <- </span>greater-or-equal i, max + <span class="muControl">break-if</span> done? + c:character<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:address:source:character, sink:address:sink:character<span class="Special"> <- </span>new-channel <span class="Constant">10/capacity</span> + _, buffered-stdin:address:sink:character/buffered-stdin<span class="Special"> <- </span>new-channel <span class="Constant">10/capacity</span> + buffered-chan:address:channel:character<span class="Special"> <- </span>get *buffered-stdin, <span class="Constant">chan:offset</span> + empty?:boolean<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:number<span class="Special"> <- </span>start-running buffer-lines, source, buffered-stdin + wait-for-routine buffer-routine + empty?<span class="Special"> <- </span>channel-empty? buffered-chan + assert empty?:boolean, <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 buffer-routine + empty?<span class="Special"> <- </span>channel-empty? buffered-chan + assert empty?:boolean, <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 buffer-routine + empty?<span class="Special"> <- </span>channel-empty? buffered-chan + assert empty?:boolean, <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 buffer-routine + empty?<span class="Special"> <- </span>channel-empty? buffered-chan + data-emitted?:boolean<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 : --> |