https://github.com/akkartik/mu/blob/master/088file.mu

      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-inputs
     24   error? <- copy 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, null/no-contents, true/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-inputs
     41   source:&:source:char, error?:bool <- start-reading resources, filename
     42   return-if error?, null/no-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-inputs
     56   error? <- copy false
     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 null/no-such-resource, true/error-found
     74 ]
     75
     76 def receive-from-file file:num, sink:&:sink:char -> sink:&:sink:char [
     77   local-scope
     78   load-inputs
     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-inputs
     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-inputs
    108   error? <- copy 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, null/sink, 0/routine-id, true/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-inputs
    130   # todo: really create an empty file
    131   return-unless contents, resources, false/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-inputs
    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-inputs
    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 null
    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 ]