about summary refs log blame commit diff stats
path: root/html/072channel.mu.html
blob: c710e692f298f8060e48289747790e7f112bce12 (plain) (tree)














































                                                                                                          


                                                                                                                                                                                                      














                                                                                                             
                          





                                                                                                           
                            


                                                  
                            

 
                                                                                                                                                   

                                                


                                                                                                                                                           
                                                                                                                                                         

                                                                                                                    
                                                                                                                                
                                                                                                   
                                                                                                                               
                                                                                                     

 
                                                                                                                                      

                                                
                                                                                                                   
                                                             



                                                                      

                                                                                                                                

                                          


                                                                                                                             
                                                        

                                                                                   


                                                                         
                                                                                 
                                                       
                                                                                
                                  

                                                                                                           

 
                                                                                                                                          

                                                
                                                                                                                  



                                                                         
                                                            

                                                                                                                                
                                  



                                                                                                                             
                                                       

                                                                                   


                                                                         
                                                                                 
                                                       
                                                                                
                                  

                                                                                                           

 
                                                                                                                             

                                                
                                                                                                                  








                                                                         



                                                                                                                                                                                                   








                                                                                                                                                 




                                                                                                                                                                                                 








                                                                                                                                                 





                                                                                                                                                                                                      









                                                                                                                                                 

                                                                                                                                                                                                      
                                                         

                                                                                                                                                                                      
                                                           

                                                                                                                                                                                          
                                                                                   

                                                                                                                                                                                          
                                                                                  

                                                                                                                                                                                          








                                                                                                                                                                             

                                                                     



                                                                                                                                                                                                      








                                                                                                                                             




                                                                                                                                                                                                      








                                                                                                                                             




                                                                                                                                                                                                      








                                                                                                                                             





                                                                                                                                                                                                      






                                                                                                                                             














































































                                                                                                                                                                                                                                   
                                                                              
                                                                                                                                                                                                                      



                                                
                                                                                                      










                                                                                                      

                                                                                                                        
                                                               

                                                                                                                        














                                                                                                             
                                                                                                                                 








                                                                                                        




                                                                        





                                                                            



                                                                                                                                                                                                                  


                                                                                                      
                                                                                                                                                                                                                                                              
                                                                          
                                                                                                                                                                      


                                                                                                                       
                                                                                                                                                                                              

                                                                          
                                                                                                                                                                      


                                                                                                             
                                                                                                                                                                                              

                                                                          
                                                                                                                                                                      


                                                                                                             
                                                                                                                                                                                                    

                                                                          
                                                                                                                                                                       












                                                                                                                                       
<!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 - 072channel.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 only be used by a single one. Don't try to read from or</span>
<span class="Comment"># write to it from multiple routines at once.</span>
<span class="Comment">#</span>
<span class="Comment"># The key property of channels is that writing to a full channel or reading</span>
<span class="Comment"># from an empty one will put the current routine in 'waiting' state until the</span>
<span class="Comment"># operation can be completed.</span>

<span class="muScenario">scenario</span> channel [
  run [
    <span class="Constant">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">3/capacity</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    <span class="Constant">3</span>:number, <span class="Constant">1</span>:address:source:number<span class="Special"> &lt;- </span>read <span class="Constant">1</span>:address:source:number
  ]
  memory-should-contain [
    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">34</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"> -&gt; </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"> &lt;- </span>new <span class="Delimiter">{</span>(channel _elem): type<span class="Delimiter">}</span>
  *result<span class="Special"> &lt;- </span>put *result, <span class="Constant">first-full:offset</span>, <span class="Constant">0</span>
  *result<span class="Special"> &lt;- </span>put *result, <span class="Constant">first-free:offset</span>, <span class="Constant">0</span>
  capacity<span class="Special"> &lt;- </span>add capacity, <span class="Constant">1</span>  <span class="Comment"># unused slot for 'full?' below</span>
  data:address:array:_elem<span class="Special"> &lt;- </span>new <span class="Constant">_elem:type</span>, capacity
  *result<span class="Special"> &lt;- </span>put *result, <span class="Constant">data:offset</span>, data
  in<span class="Special"> &lt;- </span>new <span class="Delimiter">{</span>(source _elem): type<span class="Delimiter">}</span>
  *in<span class="Special"> &lt;- </span>put *in, <span class="Constant">chan:offset</span>, result
  out<span class="Special"> &lt;- </span>new <span class="Delimiter">{</span>(sink _elem): type<span class="Delimiter">}</span>
  *out<span class="Special"> &lt;- </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"> -&gt; </span>out:address:sink:_elem [
  <span class="Constant">local-scope</span>
  <span class="Constant">load-ingredients</span>
  chan:address:channel:_elem<span class="Special"> &lt;- </span>get *out, <span class="Constant">chan:offset</span>
<span class="Constant">  &lt;channel-write-initial&gt;</span>
  <span class="Delimiter">{</span>
    <span class="Comment"># block if chan is full</span>
    full:boolean<span class="Special"> &lt;- </span>channel-full? chan
    <span class="muControl">break-unless</span> full
    full-address:location<span class="Special"> &lt;- </span>get-location *chan, <span class="Constant">first-full:offset</span>
    wait-for-location full-address
  <span class="Delimiter">}</span>
  <span class="Comment"># store val</span>
  circular-buffer:address:array:_elem<span class="Special"> &lt;- </span>get *chan, <span class="Constant">data:offset</span>
  free:number<span class="Special"> &lt;- </span>get *chan, <span class="Constant">first-free:offset</span>
  *circular-buffer<span class="Special"> &lt;- </span>put-index *circular-buffer, free, val
  <span class="Comment"># mark its slot as filled</span>
  <span class="Comment"># todo: clear the slot itself</span>
  free<span class="Special"> &lt;- </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"> &lt;- </span>length *circular-buffer
    at-end?:boolean<span class="Special"> &lt;- </span>greater-or-equal free, len
    <span class="muControl">break-unless</span> at-end?
    free<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
  <span class="Delimiter">}</span>
  <span class="Comment"># write back</span>
  *chan<span class="Special"> &lt;- </span>put *chan, <span class="Constant">first-free:offset</span>, free
]

<span class="muRecipe">def</span> read in:address:source:_elem<span class="muRecipe"> -&gt; </span>result:_elem, in:address:source:_elem [
  <span class="Constant">local-scope</span>
  <span class="Constant">load-ingredients</span>
  chan:address:channel:_elem<span class="Special"> &lt;- </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"> &lt;- </span>channel-empty? chan
    <span class="muControl">break-unless</span> empty?
<span class="Constant">    &lt;channel-read-empty&gt;</span>
    free-address:location<span class="Special"> &lt;- </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"> &lt;- </span>get *chan, <span class="Constant">first-full:offset</span>
  circular-buffer:address:array:_elem<span class="Special"> &lt;- </span>get *chan, <span class="Constant">data:offset</span>
  result<span class="Special"> &lt;- </span>index *circular-buffer, full
  <span class="Comment"># mark its slot as empty</span>
  <span class="Comment"># todo: clear the slot itself</span>
  full<span class="Special"> &lt;- </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"> &lt;- </span>length *circular-buffer
    at-end?:boolean<span class="Special"> &lt;- </span>greater-or-equal full, len
    <span class="muControl">break-unless</span> at-end?
    full<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
  <span class="Delimiter">}</span>
  <span class="Comment"># write back</span>
  *chan<span class="Special"> &lt;- </span>put *chan, <span class="Constant">first-full:offset</span>, full
]

<span class="muRecipe">def</span> clear in:address:source:_elem<span class="muRecipe"> -&gt; </span>in:address:source:_elem [
  <span class="Constant">local-scope</span>
  <span class="Constant">load-ingredients</span>
  chan:address:channel:_elem<span class="Special"> &lt;- </span>get *in, <span class="Constant">chan:offset</span>
  <span class="Delimiter">{</span>
    empty?:boolean<span class="Special"> &lt;- </span>channel-empty? chan
    <span class="muControl">break-if</span> empty?
    _, in<span class="Special"> &lt;- </span>read in
  <span class="Delimiter">}</span>
]

<span class="muScenario">scenario</span> channel-initialization [
  run [
    <span class="Constant">1</span>:address:source:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">3/capacity</span>
    <span class="Constant">2</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:channel:number, <span class="Constant">first-full:offset</span>
    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:channel:number, <span class="Constant">first-free:offset</span>
  ]
  memory-should-contain [
    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># first-full</span>
    <span class="Constant">4</span><span class="Special"> &lt;- </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">1</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">3/capacity</span>
    <span class="Constant">1</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">1</span>:address:sink:number, <span class="Constant">34</span>
    <span class="Constant">2</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:sink:number, <span class="Constant">chan:offset</span>
    <span class="Constant">3</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:channel:character, <span class="Constant">first-full:offset</span>
    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">2</span>:address:channel:character, <span class="Constant">first-free:offset</span>
  ]
  memory-should-contain [
    <span class="Constant">3</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># first-full</span>
    <span class="Constant">4</span><span class="Special"> &lt;- </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">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">3/capacity</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    _, <span class="Constant">1</span>:address:source:number<span class="Special"> &lt;- </span>read <span class="Constant">1</span>:address:source:number
    <span class="Constant">3</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:channel:number, <span class="Constant">first-full:offset</span>
    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:channel:number, <span class="Constant">first-free:offset</span>
  ]
  memory-should-contain [
    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># first-full</span>
    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># first-free</span>
  ]
]

<span class="muScenario">scenario</span> channel-wrap [
  run [
    <span class="Comment"># channel with just 1 slot</span>
    <span class="Constant">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">1/capacity</span>
    <span class="Constant">3</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Comment"># write and read a value</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    _, <span class="Constant">1</span>:address:source:number<span class="Special"> &lt;- </span>read <span class="Constant">1</span>:address:source:number
    <span class="Comment"># first-free will now be 1</span>
    <span class="Constant">4</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:channel:number, <span class="Constant">first-free:offset</span>
    <span class="Constant">5</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:channel:number, <span class="Constant">first-free:offset</span>
    <span class="Comment"># write second value, verify that first-free wraps</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    <span class="Constant">6</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:channel:number, <span class="Constant">first-free:offset</span>
    <span class="Comment"># read second value, verify that first-full wraps</span>
    _, <span class="Constant">1</span>:address:source:number<span class="Special"> &lt;- </span>read <span class="Constant">1</span>:address:source:number
    <span class="Constant">7</span>:number<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:channel:number, <span class="Constant">first-full:offset</span>
  ]
  memory-should-contain [
    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># first-free after first write</span>
    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># first-full after first read</span>
    <span class="Constant">6</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># first-free after second write, wrapped</span>
    <span class="Constant">7</span><span class="Special"> &lt;- </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">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">3/capacity</span>
    <span class="Constant">3</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">4</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">3</span>:address:channel:number
    <span class="Constant">5</span>:boolean<span class="Special"> &lt;- </span>channel-full? <span class="Constant">3</span>:address:channel:number
  ]
  memory-should-contain [
    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># empty?</span>
    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># full?</span>
  ]
]

<span class="muScenario">scenario</span> channel-write-not-empty [
  run [
    <span class="Constant">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">3/capacity</span>
    <span class="Constant">3</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    <span class="Constant">4</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">3</span>:address:channel:number
    <span class="Constant">5</span>:boolean<span class="Special"> &lt;- </span>channel-full? <span class="Constant">3</span>:address:channel:number
  ]
  memory-should-contain [
    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># empty?</span>
    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># full?</span>
  ]
]

<span class="muScenario">scenario</span> channel-write-full [
  run [
    <span class="Constant">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">1/capacity</span>
    <span class="Constant">3</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    <span class="Constant">4</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">3</span>:address:channel:number
    <span class="Constant">5</span>:boolean<span class="Special"> &lt;- </span>channel-full? <span class="Constant">3</span>:address:channel:number
  ]
  memory-should-contain [
    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">0</span>  <span class="Comment"># empty?</span>
    <span class="Constant">5</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># full?</span>
  ]
]

<span class="muScenario">scenario</span> channel-read-not-full [
  run [
    <span class="Constant">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">1/capacity</span>
    <span class="Constant">3</span>:address:channel:number<span class="Special"> &lt;- </span>get *<span class="Constant">1</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:number, <span class="Constant">34</span>
    _, <span class="Constant">1</span>:address:source:number<span class="Special"> &lt;- </span>read <span class="Constant">1</span>:address:source:number
    <span class="Constant">4</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">3</span>:address:channel:number
    <span class="Constant">5</span>:boolean<span class="Special"> &lt;- </span>channel-full? <span class="Constant">3</span>:address:channel:number
  ]
  memory-should-contain [
    <span class="Constant">4</span><span class="Special"> &lt;- </span><span class="Constant">1</span>  <span class="Comment"># empty?</span>
    <span class="Constant">5</span><span class="Special"> &lt;- </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="Comment"># todo: can't yet include type ingredients when extending containers</span>
<span class="muData">container</span> channel [
  closed?:boolean
]

<span class="Comment"># a channel can be closed from either the source or the sink</span>
<span class="Comment"># both threads can modify it, but they can only set it, so this is a benign race</span>
<span class="muRecipe">def</span> close x:address:source:_elem<span class="muRecipe"> -&gt; </span>x:address:source:_elem [
  <span class="Constant">local-scope</span>
  <span class="Constant">load-ingredients</span>
  chan:address:channel:_elem<span class="Special"> &lt;- </span>get *x, <span class="Constant">chan:offset</span>
  *chan<span class="Special"> &lt;- </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"> -&gt; </span>x:address:sink:_elem [
  <span class="Constant">local-scope</span>
  <span class="Constant">load-ingredients</span>
  chan:address:channel:_elem<span class="Special"> &lt;- </span>get *x, <span class="Constant">chan:offset</span>
  *chan<span class="Special"> &lt;- </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">&lt;channel-write-initial&gt;</span> [
  closed?:boolean<span class="Special"> &lt;- </span>get *chan, <span class="Constant">closed?:offset</span>
  <span class="muControl">return-if</span> closed?
]

<span class="muRecipe">after</span> <span class="Constant">&lt;channel-read-empty&gt;</span> [
  closed?:boolean<span class="Special"> &lt;- </span>get *chan, <span class="Constant">closed?:offset</span>
  <span class="muControl">return-if</span> closed?, <span class="Constant">0/hack</span>  <span class="Comment"># only scalar _elems supported in channels; need to support construction of arbitrary empty containers</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"> -&gt; </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"> &lt;- </span>get *chan, <span class="Constant">first-full:offset</span>
  free:number<span class="Special"> &lt;- </span>get *chan, <span class="Constant">first-free:offset</span>
  result<span class="Special"> &lt;- </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"> -&gt; </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"> &lt;- </span>get *chan, <span class="Constant">first-free:offset</span>
  tmp<span class="Special"> &lt;- </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"> &lt;- </span>capacity chan
    at-end?:boolean<span class="Special"> &lt;- </span>greater-or-equal tmp, len
    <span class="muControl">break-unless</span> at-end?
    tmp<span class="Special"> &lt;- </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"> &lt;- </span>get *chan, <span class="Constant">first-full:offset</span>
  result<span class="Special"> &lt;- </span>equal full, tmp
]

<span class="muRecipe">def</span> capacity chan:address:channel:_elem<span class="muRecipe"> -&gt; </span>result:number [
  <span class="Constant">local-scope</span>
  <span class="Constant">load-ingredients</span>
  q:address:array:_elem<span class="Special"> &lt;- </span>get *chan, <span class="Constant">data:offset</span>
  result<span class="Special"> &lt;- </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"> -&gt; </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>
  <span class="Delimiter">{</span>
    line:address:buffer<span class="Special"> &lt;- </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, in<span class="Special"> &lt;- </span>read in
      <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"> &lt;- </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"> &lt;- </span>get *line, <span class="Constant">length:offset</span>
          buffer-empty?:boolean<span class="Special"> &lt;- </span>equal buffer-length, <span class="Constant">0</span>
          <span class="muControl">break-if</span> buffer-empty?
          buffer-length<span class="Special"> &lt;- </span>subtract buffer-length, <span class="Constant">1</span>
          *line<span class="Special"> &lt;- </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"> &lt;- </span>append line, c
      line-done?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">10/newline</span>
      <span class="muControl">break-if</span> line-done?
      <span class="Comment"># stop buffering on eof (currently only generated by fake console)</span>
      eof?:boolean<span class="Special"> &lt;- </span>equal c, <span class="Constant">0/eof</span>
      <span class="muControl">break-if</span> eof?
      <span class="muControl">loop</span>
    <span class="Delimiter">}</span>
    <span class="Comment"># copy line into 'buffered-out'</span>
    i:number<span class="Special"> &lt;- </span>copy <span class="Constant">0</span>
    line-contents:address:array:character<span class="Special"> &lt;- </span>get *line, <span class="Constant">data:offset</span>
    max:number<span class="Special"> &lt;- </span>get *line, <span class="Constant">length:offset</span>
    <span class="Delimiter">{</span>
      done?:boolean<span class="Special"> &lt;- </span>greater-or-equal i, max
      <span class="muControl">break-if</span> done?
      c:character<span class="Special"> &lt;- </span>index *line-contents, i
      buffered-out<span class="Special"> &lt;- </span>write buffered-out, c
      i<span class="Special"> &lt;- </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"> &lt;- </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">1</span>:address:source:number, <span class="Constant">2</span>:address:sink:number<span class="Special"> &lt;- </span>new-channel <span class="Constant">10/capacity</span>
    _, <span class="Constant">3</span>:address:sink:number/buffered-stdin<span class="Special"> &lt;- </span>new-channel <span class="Constant">10/capacity</span>
    <span class="Constant">4</span>:address:channel:number/buffered-stdin<span class="Special"> &lt;- </span>get *<span class="Constant">3</span>:address:source:number, <span class="Constant">chan:offset</span>
    <span class="Constant">5</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">4</span>:address:channel:character/buffered-stdin
    assert <span class="Constant">5</span>:boolean, <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>
    <span class="Constant">6</span>:number/buffer-routine<span class="Special"> &lt;- </span>start-running buffer-lines, <span class="Constant">1</span>:address:source:character/stdin, <span class="Constant">3</span>:address:sink:character/buffered-stdin
    wait-for-routine <span class="Constant">6</span>:number/buffer-routine
    <span class="Constant">7</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">4</span>:address:channel:character/buffered-stdin
    assert <span class="Constant">7</span>: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>
    <span class="Constant">2</span>:address:sink:character<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:character, <span class="Constant">97/a</span>
    restart <span class="Constant">6</span>:number/buffer-routine
    wait-for-routine <span class="Constant">6</span>:number/buffer-routine
    <span class="Constant">8</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">4</span>:address:channel:character/buffered-stdin
    assert <span class="Constant">8</span>: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>
    <span class="Constant">2</span>:address:sink:character<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:character, <span class="Constant">98/b</span>
    restart <span class="Constant">6</span>:number/buffer-routine
    wait-for-routine <span class="Constant">6</span>:number/buffer-routine
    <span class="Constant">9</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">4</span>:address:channel:character/buffered-stdin
    assert <span class="Constant">9</span>: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>
    <span class="Constant">2</span>:address:sink:character<span class="Special"> &lt;- </span>write <span class="Constant">2</span>:address:sink:character, <span class="Constant">10/newline</span>
    restart <span class="Constant">6</span>:number/buffer-routine
    wait-for-routine <span class="Constant">6</span>:number/buffer-routine
    <span class="Constant">10</span>:boolean<span class="Special"> &lt;- </span>channel-empty? <span class="Constant">4</span>:address:channel:character/buffered-stdin
    <span class="Constant">11</span>:boolean/completed?<span class="Special"> &lt;- </span>not <span class="Constant">10</span>:boolean
    assert <span class="Constant">11</span>:boolean/completed?, <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 : -->