1 # Wrappers around file system primitives that take a 'resources' object and
  2 # are thus easier to test.
  3 #
  4 # - start-reading - asynchronously open a file, returning a channel source for
  5 #   receiving the results
  6 # - start-writing - asynchronously open a file, returning a channel sink for
  7 #   the data to write
  8 # - slurp - synchronously read from a file
  9 # - dump - synchronously write to a file
 10 
 11 container resources [
 12   lock:bool
 13   data:&:@:resource
 14 ]
 15 
 16 container resource [
 17   name:text
 18   contents:text
 19 ]
 20 
 21 def start-reading resources:&:resources, filename:text -> contents:&:source:char, error?:bool [
 22   local-scope
 23   load-ingredients
 24   error? <- copy 0/false
 25   {
 26   ¦ break-unless resources
 27   ¦ # fake file system
 28   ¦ contents, error? <- start-reading-from-fake-resource resources, filename
 29   ¦ return
 30   }
 31   # real file system
 32   file:num <- $open-file-for-reading filename
 33   return-unless file, 0/contents, 1/error?
 34   contents:&:source:char, sink:&:sink:char <- new-channel 30
 35   start-running receive-from-file file, sink
 36 ]
 37 
 38 def slurp resources:&:resources, filename:text -> contents:text, error?:bool [
 39   local-scope
 40   load-ingredients
 41   source:&:source:char, error?:bool <- start-reading resources, filename
 42   return-if error?, 0/contents
 43   buf:&:buffer:char <- new-buffer 30/capacity
 44   {
 45   ¦ c:char, done?:bool, source <- read source
 46   ¦ break-if done?
 47   ¦ buf <- append buf, c
 48   ¦ loop
 49   }
 50   contents <- buffer-to-array buf
 51 ]
 52 
 53 def start-reading-from-fake-resource resources:&:resources, resource:text -> contents:&:source:char, error?:bool [
 54   local-scope
 55   load-ingredients
 56   error? <- copy 0/no-error
 57   i:num <- copy 0
 58   data:&:@:resource <- get *resources, data:offset
 59   len:num <- length *data
 60   {
 61   ¦ done?:bool <- greater-or-equal i, len
 62   ¦ break-if done?
 63   ¦ tmp:resource <- index *data, i
 64   ¦ i <- add i, 1
 65   ¦ curr-resource:text <- get tmp, name:offset
 66   ¦ found?:bool <- equal resource, curr-resource
 67   ¦ loop-unless found?
 68   ¦ contents:&:source:char, sink:&:sink:char <- new-channel 30
 69   ¦ curr-contents:text <- get tmp, contents:offset
 70   ¦ start-running receive-from-text curr-contents, sink
 71   ¦ return
 72   }
 73   return 0/not-found, 1/error
 74 ]
 75 
 76 def receive-from-file file:num, sink:&:sink:char -> sink:&:sink:char [
 77   local-scope
 78   load-ingredients
 79   {
 80   ¦ c:char, eof?:bool <- $read-from-file file
 81   ¦ break-if eof?
 82   ¦ sink <- write sink, c
 83   ¦ loop
 84   }
 85   sink <- close sink
 86   file <- $close-file file
 87 ]
 88 
 89 def receive-from-text contents:text, sink:&:sink:char -> sink:&:sink:char [
 90   local-scope
 91   load-ingredients
 92   i:num <- copy 0
 93   len:num <- length *contents
 94   {
 95   ¦ done?:bool <- greater-or-equal i, len
 96   ¦ break-if done?
 97   ¦ c:char <- index *contents, i
 98   ¦ sink <- write sink, c
 99   ¦ i <- add i, 1
100   ¦ loop
101   }
102   sink <- close sink
103 ]
104 
105 def start-writing resources:&:resources, filename:text -> sink:&:sink:char, routine-id:num, error?:bool [
106   local-scope
107   load-ingredients
108   error? <- copy 0/false
109   source:&:source:char, sink:&:sink:char <- new-channel 30
110   {
111   ¦ break-unless resources
112   ¦ # fake file system
113   ¦ routine-id <- start-running transmit-to-fake-resource resources, filename, source
114   ¦ return
115   }
116   # real file system
117   file:num <- $open-file-for-writing filename
118   return-unless file, 0/sink, 0/routine-id, 1/error?
119   {
120   ¦ break-if file
121   ¦ msg:text <- append [no such file: ] filename
122   ¦ assert file, msg
123   }
124   routine-id <- start-running transmit-to-file file, source
125 ]
126 
127 def dump resources:&:resources, filename:text, contents:text -> resources:&:resources, error?:bool [
128   local-scope
129   load-ingredients
130   # todo: really create an empty file
131   return-unless contents, resources, 0/no-error
132   sink-file:&:sink:char, write-routine:num, error?:bool <- start-writing resources, filename
133   return-if error?
134   i:num <- copy 0
135   len:num <- length *contents
136   {
137   ¦ done?:bool <- greater-or-equal i, len
138   ¦ break-if done?
139   ¦ c:char <- index *contents, i
140   ¦ sink-file <- write sink-file, c
141   ¦ i <- add i, 1
142   ¦ loop
143   }
144   close sink-file
145   # make sure to wait for the file to be actually written to disk
146   # (Mu practices structured concurrency: http://250bpm.com/blog:71)
147   wait-for-routine write-routine
148 ]
149 
150 def transmit-to-file file:num, source:&:source:char -> source:&:source:char [
151   local-scope
152   load-ingredients
153   {
154   ¦ c:char, done?:bool, source <- read source
155   ¦ break-if done?
156   ¦ $write-to-file file, c
157   ¦ loop
158   }
159   file <- $close-file file
160 ]
161 
162 def transmit-to-fake-resource resources:&:resources, filename:text, source:&:source:char -> resources:&:resources, source:&:source:char [
163   local-scope
164   load-ingredients
165   lock:location <- get-location *resources, lock:offset
166   wait-for-reset-then-set lock
167   # compute new file contents
168   buf:&:buffer:char <- new-buffer 30
169   {
170   ¦ c:char, done?:bool, source <- read source
171   ¦ break-if done?
172   ¦ buf <- append buf, c
173   ¦ loop
174   }
175   contents:text <- buffer-to-array buf
176   new-resource:resource <- merge filename, contents
177   # write to resources
178   curr-filename:text <- copy 0
179   data:&:@:resource <- get *resources, data:offset
180   # replace file contents if it already exists
181   i:num <- copy 0
182   len:num <- length *data
183   {
184   ¦ done?:bool <- greater-or-equal i, len
185   ¦ break-if done?
186   ¦ tmp:resource <- index *data, i
187   ¦ curr-filename <- get tmp, name:offset
188   ¦ found?:bool <- equal filename, curr-filename
189   ¦ {
190   ¦ ¦ break-unless found?
191   ¦ ¦ put-index *data, i, new-resource
192   ¦ ¦ jump +unlock-and-exit
193   ¦ }
194   ¦ i <- add i, 1
195   ¦ loop
196   }
197   # if file didn't already exist, make room for it
198   new-len:num <- add len, 1
199   new-data:&:@:resource <- new resource:type, new-len
200   put *resources, data:offset, new-data
201   # copy over old files
202   i:num <- copy 0
203   {
204   ¦ done?:bool <- greater-or-equal i, len
205   ¦ break-if done?
206   ¦ tmp:resource <- index *data, i
207   ¦ put-index *new-data, i, tmp
208   }
209   # write new file
210   put-index *new-data, len, new-resource
211   +unlock-and-exit
212   reset lock
213 ]