summary refs log blame commit diff stats
path: root/svc/init.go
blob: 4408ecf19bdf3c933585f8ab7260da1f5a885141 (plain) (tree)
="nv">num <- new-channel 3/capacity chan:&:channel:num <- get *source, chan:offset 10:num/raw <- get *chan, first-full:offset 11:num/raw <- get *chan, first-free:offset ] memory-should-contain [ 10 <- 0 # first-full 11 <- 0 # first-free ] ] scenario channel-write-increments-free [ local-scope _, sink:&:sink:num <- new-channel 3/capacity run [ sink <- write sink, 34 chan:&:channel:num <- get *sink, chan:offset 10:num/raw <- get *chan, first-full:offset 11:num/raw <- get *chan, first-free:offset ] memory-should-contain [ 10 <- 0 # first-full 11 <- 1 # first-free ] ] scenario channel-read-increments-full [ local-scope source:&:source:num, sink:&:sink:num <- new-channel 3/capacity sink <- write sink, 34 run [ _, _, source <- read source chan:&:channel:num <- get *source, chan:offset 10:num/raw <- get *chan, first-full:offset 11:num/raw <- get *chan, first-free:offset ] memory-should-contain [ 10 <- 1 # first-full 11 <- 1 # first-free ] ] scenario channel-wrap [ local-scope # channel with just 1 slot source:&:source:num, sink:&:sink:num <- new-channel 1/capacity chan:&:channel:num <- get *source, chan:offset # write and read a value sink <- write sink, 34 _, _, source <- read source run [ # first-free will now be 1 10:num/raw <- get *chan, first-free:offset 11:num/raw <- get *chan, first-free:offset # write second value, verify that first-free wraps sink <- write sink, 34 20:num/raw <- get *chan, first-free:offset # read second value, verify that first-full wraps _, _, source <- read source 30:num/raw <- get *chan, first-full:offset ] memory-should-contain [ 10 <- 1 # first-free after first write 11 <- 1 # first-full after first read 20 <- 0 # first-free after second write, wrapped 30 <- 0 # first-full after second read, wrapped ] ] scenario channel-new-empty-not-full [ run [ local-scope source:&:source:num <- new-channel 3/capacity chan:&:channel:num <- get *source, chan:offset 10:bool/raw <- channel-empty? chan 11:bool/raw <- channel-full? chan ] memory-should-contain [ 10 <- 1 # empty? 11 <- 0 # full? ] ] scenario channel-write-not-empty [ local-scope source:&:source:num, sink:&:sink:num <- new-channel 3/capacity chan:&:channel:num <- get *source, chan:offset run [ sink <- write sink, 34 10:bool/raw <- channel-empty? chan 11:bool/raw <- channel-full? chan ] memory-should-contain [ 10 <- 0 # empty? 11 <- 0 # full? ] ] scenario channel-write-full [ local-scope source:&:source:num, sink:&:sink:num <- new-channel 1/capacity chan:&:channel:num <- get *source, chan:offset run [ sink <- write sink, 34 10:bool/raw <- channel-empty? chan 11:bool/raw <- channel-full? chan ] memory-should-contain [ 10 <- 0 # empty? 11 <- 1 # full? ] ] scenario channel-read-not-full [ local-scope source:&:source:num, sink:&:sink:num <- new-channel 1/capacity chan:&:channel:num <- get *source, chan:offset sink <- write sink, 34 run [ _, _, source <- read source 10:bool/raw <- channel-empty? chan 11:bool/raw <- channel-full? chan ] memory-should-contain [ 10 <- 1 # empty? 11 <- 0 # full? ] ] scenario channel-clear [ local-scope # create a channel with a few items source:&:source:num, sink:&:sink:num <- new-channel 3/capacity chan:&:channel:num <- get *sink, chan:offset write sink, 30 write sink, 31 write sink, 32 run [ clear source 10:bool/raw <- channel-empty? chan ] memory-should-contain [ 10 <- 1 # after the call to 'clear', the channel should be empty ] ] def clear in:&:source:_elem -> in:&:source:_elem [ local-scope load-inputs chan:&:channel:_elem <- get *in, chan:offset { empty?:bool <- channel-empty? chan break-if empty? _, _, in <- read in loop } ] ## cancelling channels # every channel comes with a boolean signifying if it's been closed # initially this boolean is false container channel:_elem [ closed?:bool ] # a channel can be closed from either the source or the sink # both routines can modify the 'closed?' bit, but they can only ever set it, so this is a benign race def close x:&:source:_elem -> x:&:source:_elem [ local-scope load-inputs chan:&:channel:_elem <- get *x, chan:offset *chan <- put *chan, closed?:offset, true ] def close x:&:sink:_elem -> x:&:sink:_elem [ local-scope load-inputs chan:&:channel:_elem <- get *x, chan:offset *chan <- put *chan, closed?:offset, true ] # once a channel is closed from one side, no further operations are expected from that side # if a channel is closed for reading, # no further writes will be let through # if a channel is closed for writing, # future reads continue until the channel empties, # then the channel is also closed for reading after <channel-write-initial> [ closed?:bool <- get *chan, closed?:offset return-if closed? ] after <channel-read-empty> [ closed?:bool <- get *chan, closed?:offset { break-unless closed? empty-result:&:_elem <- new _elem:type current-routine-is-unblocked return *empty-result, true } ] ## helpers # An empty channel has first-free and first-full both at the same value. def channel-empty? chan:&:channel:_elem -> result:bool [ local-scope load-inputs # return chan.first-full == chan.first-free full:num <- get *chan, first-full:offset free:num <- get *chan, first-free:offset result <- equal full, free ] # A full channel has first-free just before first-full, wasting one slot. # (Other alternatives: https://www.snellman.net/blog/archive/2016-12-13-ring-buffers) def channel-full? chan:&:channel:_elem -> result:bool [ local-scope load-inputs # tmp = chan.first-free + 1 tmp:num <- get *chan, first-free:offset tmp <- add tmp, 1 { # if tmp == chan.capacity, tmp = 0 len:num <- capacity chan at-end?:bool <- greater-or-equal tmp, len break-unless at-end? tmp <- copy 0 } # return chan.first-full == tmp full:num <- get *chan, first-full:offset result <- equal full, tmp ] def capacity chan:&:channel:_elem -> result:num [ local-scope load-inputs q:&:@:_elem <- get *chan, data:offset result <- length *q ] ## helpers for channels of characters in particular def buffer-lines in:&:source:char, buffered-out:&:sink:char -> buffered-out:&:sink:char, in:&:source:char [ local-scope load-inputs # repeat forever eof?:bool <- copy false { line:&:buffer:char <- new-buffer 30 # read characters from 'in' until newline, copy into line { +next-character c:char, eof?:bool, in <- read in break-if eof? # drop a character on backspace { # special-case: if it's a backspace backspace?:bool <- equal c, 8 break-unless backspace? # drop previous character { buffer-length:num <- get *line, length:offset buffer-empty?:bool <- equal buffer-length, 0 break-if buffer-empty? buffer-length <- subtract buffer-length, 1 *line <- put *line, length:offset, buffer-length } # and don't append this one loop +next-character } # append anything else line <- append line, c line-done?:bool <- equal c, 10/newline break-if line-done? loop } # copy line into 'buffered-out' i:num <- copy 0 line-contents:text <- get *line, data:offset max:num <- get *line, length:offset { done?:bool <- greater-or-equal i, max break-if done? c:char <- index *line-contents, i buffered-out <- write buffered-out, c i <- add i, 1 loop } { break-unless eof? buffered-out <- close buffered-out return } loop } ] scenario buffer-lines-blocks-until-newline [ run [ local-scope source:&:source:char, sink:&:sink:char <- new-channel 10/capacity _, buffered-stdin:&:sink:char/buffered-stdin <- new-channel 10/capacity buffered-chan:&:channel:char <- get *buffered-stdin, chan:offset empty?:bool <- channel-empty? buffered-chan assert empty?, [ F buffer-lines-blocks-until-newline: channel should be empty after init] # buffer stdin into buffered-stdin, try to read from buffered-stdin buffer-routine:num <- start-running buffer-lines, source, buffered-stdin wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan assert empty?:bool, [ F buffer-lines-blocks-until-newline: channel should be empty after buffer-lines bring-up] # write 'a' sink <- write sink, 97/a restart buffer-routine wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan assert empty?:bool, [ F buffer-lines-blocks-until-newline: channel should be empty after writing 'a'] # write 'b' sink <- write sink, 98/b restart buffer-routine wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan assert empty?:bool, [ F buffer-lines-blocks-until-newline: channel should be empty after writing 'b'] # write newline sink <- write sink, 10/newline restart buffer-routine wait-for-routine-to-block buffer-routine empty? <- channel-empty? buffered-chan data-emitted?:bool <- not empty? assert data-emitted?, [ F buffer-lines-blocks-until-newline: channel should contain data after writing newline] trace 1, [test], [reached end] ] trace-should-contain [ test: reached end ] ] def drain source:&:source:char -> result:text, source:&:source:char [ local-scope load-inputs buf:&:buffer:char <- new-buffer 30 { c:char, done?:bool <- read source break-if done? buf <- append buf, c loop } result <- buffer-to-array buf ]