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   ">like an array, except that you can index it with arbitrary types
# and not just non-negative whole numbers.

# incomplete; doesn't handle hash conflicts

scenario table-read-write [
  local-scope
  tab:&:table:num:num <- new-table 30
  run [
    put-index tab, 12, 34
    60:num/raw, 61:bool/raw <- index tab, 12
  ]
  memory-should-contain [
    60 <- 34
    61 <- 1  # found
  ]
]

scenario table-read-write-non-integer [
  local-scope
  tab:&:table:text:num <- new-table 30
  run [
    put-index tab, [abc def], 34
    1:num/raw, 2:bool/raw <- index tab, [abc def]
  ]
  memory-should-contain [
    1 <- 34
    2 <- 1  # found
  ]
]

scenario table-read-not-found [
  local-scope
  tab:&:table:text:num <- new-table 30
  run [
    1:num/raw, 2:bool/raw <- index tab, [abc def]
  ]
  memory-should-contain [
    1 <- 0
    2 <- 0  # not found
  ]
]

container table:_key:_value [
  length:num
  capacity:num
  data:&:@:table-row:_key:_value
]

container table-row:_key:_value [
  occupied?:bool
  key:_key
  value:_value
]

def new-table capacity:num -> result:&:table:_key:_value [
  local-scope
  load-ingredients
  result <- new {(table _key _value): type}
  data:&:@:table-row:_key:_value <- new {(table-row _key _value): type}, capacity
  *result <- merge 0/length, capacity, data
]

# todo: tag results as /required so that call-sites are forbidden from ignoring them
# then we could handle conflicts simply by resizing the table
def put-index table:&:table:_key:_value, key:_key, value:_value -> table:&:table:_key:_value [
  local-scope
  load-ingredients
  hash:num <- hash key
  hash