# Some useful helpers for dealing with text (arrays of characters) def equal a:text, b:text -> result:bool [ local-scope load-ingredients an:num, bn:num <- copy a, b address-equal?:boolean <- equal an, bn return-if address-equal?, 1/true return-unless a, 0/false return-unless b, 0/false a-len:num <- length *a b-len:num <- length *b # compare lengths { trace 99, [text-equal], [comparing lengths] length-equal?:bool <- equal a-len, b-len break-if length-equal? return 0 } # compare each corresponding character trace 99, [text-equal], [comparing characters] i:num <- copy 0 { done?:bool <- greater-or-equal i, a-len break-if done? a2:char <- index *a, i b2:char <- index *b, i { chars-match?:bool <- equal a2, b2 break-if chars-match? return 0 } i <- add i, 1 loop } return 1 ] scenario text-equal-reflexive [ local-scope x:text <- new [abc] run [ 10:bool/raw <- equal x, x ] memory-should-contain [ 10 <- 1 # x == x for all x ] ] scenario text-equal-identical [ local-scope x:text <- new [abc] y:text <- new [abc] run [ 10:bool/raw <- equal x, y ] memory-should-contain [ 10 <- 1 # abc == abc ] ] scenario text-equal-distinct-lengths [ local-scope x:text <- new [abc] y:text <- new [abcd] run [ 10:bool/raw <- equal x, y ] memory-should-contain [ 10 <- 0 # abc != abcd ] trace-should-contain [ text-equal: comparing lengths ] trace-should-not-contain [ text-equal: comparing characters ] ] scenario text-equal-with-empty [ local-scope x:text <- new [] y:text <- new [abcd] run [ 10:bool/raw <- equal x, y ] memory-should-contain [ 10 <- 0 # "" != abcd ] ] scenario text-equal-with-null [ local-scope x:text <- new [abcd] y:text <- copy 0 run [ 10:bool/raw <- equal x, 0 11:bool/raw <- equal 0, x 12:bool/raw <- equal x, y 13:bool/raw <- equal y, x 14:bool/raw <- equal y, y ] memory-should-contain [ 10 <- 0 11 <- 0 12 <- 0 13 <- 0 14 <- 1 ] check-trace-count-for-label 0, [error] ] scenario text-equal-common-lengths-but-distinct [ local-scope x:text <- new [abc] y:text <- new [abd] run [ 10:bool/raw <- equal x, y ] memory-should-contain [ 10 <- 0 # abc != abd ] ] # A new type to help incrementally construct texts. # todo: make this shape-shifting. container buffer [ length:num data:text ] def new-buffer capacity:num -> result:&:buffer [ local-scope load-ingredients result <- new buffer:type *result <- put *result, length:offset, 0 { break-if capacity # capacity not provided capacity <- copy 10 } data:text <- new character:type, capacity *result <- put *result, data:offset, data return result ] def grow-buffer buf:&:buffer -> buf:&:buffer [ local-scope load-ingredients # double buffer size olddata:text <- get *buf, data:offset oldlen:num <- length *olddata newlen:num <- multiply oldlen, 2 newdata:text <- new character:type, newlen *buf <- put *buf, data:offset, newdata # copy old contents i:num <- copy 0 { done?:bool <- greater-or-equal i, oldlen break-if done? src:char <- index *olddata, i *newdata <- put-index *newdata, i, src i <- add i, 1 loop } ] def buffer-full? in:&:buffer -> result:bool [ local-scope load-ingredients len:num <- get *in, length:offset s:text <- get *in, data:offset capacity:num <- length *s result <- greater-or-equal len, capacity ] # most broadly applicable definition of append to a buffer: just call to-text def append buf:&:buffer, x:_elem -> buf:&:buffer [ local-scope load-ingredients text:text <- to-text x len:num <- length *text i:num <- copy 0 { done?:bool <- greater-or-equal i, len break-if done? c:char <- index *text, i buf <- append buf, c i <- add i, 1 loop } ] def append buf:&:buffer, c:char -> buf:&:buffer [ local-scope load-ingredients len:num <- get *buf, length:offset { # backspace? just drop last character if it exists and return backspace?:bool <- equal c, 8/backspace break-unless backspace? empty?:bool <- lesser-or-equal len, 0 return-if empty? len <- subtract len, 1 *buf <- put *buf, length:offset, len return } { # grow buffer if necessary full?:bool <- buffer-full? buf break-unless full? buf <- grow-buffer buf } s:text <- get *buf, data:offset *s <- put-index *s, len, c len <- add len, 1 *buf <- put *buf, length:offset, len ] def append buf:&:buffer, t:text -> buf:&:buffer [ local-scope load-ingredients len:num <- length *t i:num <- copy 0 { done?:bool <- greater-or-equal i, len break-if done? c:char <- index *t, i buf <- append buf, c i <- add i, 1 loop } ] scenario append-to-empty-buffer [ local-scope x:&:buffer <- new-buffer run [
---
layout: layout
---

<h1>Anastasie's website!</h1>

<p>
  Hi, my name is Anastasie. I'm a software engineer focused on offline-first technologies
  and sustainable, uncomplicated architecture.
</p>

<p>
  Some of the things I'm working on/have worked on:

  <ul>
    <li><a href="https://rust-lang.org">The Rust Programming Language</a> (community + organizing/moderation)</li>
    <li><a href="https://github.com/rust-lang/rustlings">Rustlings</a> (maintainership)</li>
    <li><a href="https://jekyllrb.com">Jekyll</a> (maintainership)</li>
    <li><a href="https://nodejs.org">Node.js</a> (leadership)</li>
    <li><a href="https://impfzentren.bayern">Bavarian Vaccination Centres</a> (development)</li>
    <li><a href="https://jsconf.eu">JSConf EU</a> and <a href="https://cssconf.eu">CSSconf EU</a> (organizing)</li>
    <li><a href="https://berline.rs">Rust Berlin</a> (organizing)</li>
  </ul>
</p>

<p>
  You can contact me via email: <code>ana at this website</code>
</p>
start start:num <- copy 0 { { at-end?:bool <- greater-or-equal start, len break-unless at-end? result <- new character:type, 0 return } curr:char <- index *s, start whitespace?:bool <- space? curr break-unless whitespace? start <- add start, 1 loop } # right trim: compute end end:num <- subtract len, 1 { not-at-start?:bool <- greater-than end, start assert not-at-start?, [end ran up against start] curr:char <- index *s, end whitespace?:bool <- space? curr break-unless whitespace? end <- subtract end, 1 loop } # result = new character[end+1 - start] new-len:num <- subtract end, start, -1 result:text <- new character:type, new-len # copy the untrimmed parts between start and end i:num <- copy start j:num <- copy 0 { # while i <= end done?:bool <- greater-than i, end break-if done? # result[j] = s[i] src:char <- index *s, i *result <- put-index *result, j, src i <- add i, 1 j <- add j, 1 loop } ] scenario trim-unmodified [ local-scope x:text <- new [abc] run [ y:text <- trim x 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-left [ local-scope x:text <- new [ abc] run [ y:text <- trim x 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-right [ local-scope x:text <- new [abc ] run [ y:text <- trim x 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-left-right [ local-scope x:text <- new [ abc ] run [ y:text <- trim x 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] scenario trim-newline-tab [ local-scope x:text <- new [ abc ] run [ y:text <- trim x 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [abc] ] ] def find-next text:text, pattern:char, idx:num -> next-index:num [ local-scope load-ingredients len:num <- length *text { eof?:bool <- greater-or-equal idx, len break-if eof? curr:char <- index *text, idx found?:bool <- equal curr, pattern break-if found? idx <- add idx, 1 loop } return idx ] scenario text-find-next [ local-scope x:text <- new [a/b] run [ 10:num/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 1 ] ] scenario text-find-next-empty [ local-scope x:text <- new [] run [ 10:num/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 0 ] ] scenario text-find-next-initial [ local-scope x:text <- new [/abc] run [ 10:num/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 0 # prefix match ] ] scenario text-find-next-final [ local-scope x:text <- new [abc/] run [ 10:num/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 3 # suffix match ] ] scenario text-find-next-missing [ local-scope x:text <- new [abcd] run [ 10:num/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 4 # no match ] ] scenario text-find-next-invalid-index [ local-scope x:text <- new [abc] run [ 10:num/raw <- find-next x, 47/slash, 4/start-index ] memory-should-contain [ 10 <- 4 # no change ] ] scenario text-find-next-first [ local-scope x:text <- new [ab/c/] run [ 10:num/raw <- find-next x, 47/slash, 0/start-index ] memory-should-contain [ 10 <- 2 # first '/' of multiple ] ] scenario text-find-next-second [ local-scope x:text <- new [ab/c/] run [ 10:num/raw <- find-next x, 47/slash, 3/start-index ] memory-should-contain [ 10 <- 4 # second '/' of multiple ] ] # search for a pattern of multiple characters # fairly dumb algorithm def find-next text:text, pattern:text, idx:num -> next-index:num [ local-scope load-ingredients first:char <- index *pattern, 0 # repeatedly check for match at current idx len:num <- length *text { # does some unnecessary work checking even when there isn't enough of text left done?:bool <- greater-or-equal idx, len break-if done? found?:bool <- match-at text, pattern, idx break-if found? idx <- add idx, 1 # optimization: skip past indices that definitely won't match idx <- find-next text, first, idx loop } return idx ] scenario find-next-text-1 [ local-scope x:text <- new [abc] y:text <- new [bc] run [ 10:num/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 1 ] ] scenario find-next-text-2 [ local-scope x:text <- new [abcd] y:text <- new [bc] run [ 10:num/raw <- find-next x, y, 1 ] memory-should-contain [ 10 <- 1 ] ] scenario find-next-no-match [ local-scope x:text <- new [abc] y:text <- new [bd] run [ 10:num/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 3 # not found ] ] scenario find-next-suffix-match [ local-scope x:text <- new [abcd] y:text <- new [cd] run [ 10:num/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 2 ] ] scenario find-next-suffix-match-2 [ local-scope x:text <- new [abcd] y:text <- new [cde] run [ 10:num/raw <- find-next x, y, 0 ] memory-should-contain [ 10 <- 4 # not found ] ] # checks if pattern matches at index 'idx' def match-at text:text, pattern:text, idx:num -> result:bool [ local-scope load-ingredients pattern-len:num <- length *pattern # check that there's space left for the pattern { x:num <- length *text x <- subtract x, pattern-len enough-room?:bool <- lesser-or-equal idx, x break-if enough-room? return 0/not-found } # check each character of pattern pattern-idx:num <- copy 0 { done?:bool <- greater-or-equal pattern-idx, pattern-len break-if done? c:char <- index *text, idx exp:char <- index *pattern, pattern-idx { match?:bool <- equal c, exp break-if match? return 0/not-found } idx <- add idx, 1 pattern-idx <- add pattern-idx, 1 loop } return 1/found ] scenario match-at-checks-pattern-at-index [ local-scope x:text <- new [abc] y:text <- new [ab] run [ 10:bool/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 1 # match found ] ] scenario match-at-reflexive [ local-scope x:text <- new [abc] run [ 10:bool/raw <- match-at x, x, 0 ] memory-should-contain [ 10 <- 1 # match found ] ] scenario match-at-outside-bounds [ local-scope x:text <- new [abc] y:text <- new [a] run [ 10:bool/raw <- match-at x, y, 4 ] memory-should-contain [ 10 <- 0 # never matches ] ] scenario match-at-empty-pattern [ local-scope x:text <- new [abc] y:text <- new [] run [ 10:bool/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 1 # always matches empty pattern given a valid index ] ] scenario match-at-empty-pattern-outside-bound [ local-scope x:text <- new [abc] y:text <- new [] run [ 10:bool/raw <- match-at x, y, 4 ] memory-should-contain [ 10 <- 0 # no match ] ] scenario match-at-empty-text [ local-scope x:text <- new [] y:text <- new [abc] run [ 10:bool/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 0 # no match ] ] scenario match-at-empty-against-empty [ local-scope x:text <- new [] run [ 10:bool/raw <- match-at x, x, 0 ] memory-should-contain [ 10 <- 1 # matches because pattern is also empty ] ] scenario match-at-inside-bounds [ local-scope x:text <- new [abc] y:text <- new [bc] run [ 10:bool/raw <- match-at x, y, 1 ] memory-should-contain [ 10 <- 1 # match ] ] scenario match-at-inside-bounds-2 [ local-scope x:text <- new [abc] y:text <- new [bc] run [ 10:bool/raw <- match-at x, y, 0 ] memory-should-contain [ 10 <- 0 # no match ] ] def split s:text, delim:char -> result:&:@:text [ local-scope load-ingredients # empty text? return empty array len:num <- length *s { empty?:bool <- equal len, 0 break-unless empty? result <- new {(address array character): type}, 0 return } # count #pieces we need room for count:num <- copy 1 # n delimiters = n+1 pieces idx:num <- copy 0 { idx <- find-next s, delim, idx done?:bool <- greater-or-equal idx, len break-if done? idx <- add idx, 1 count <- add count, 1 loop } # allocate space result <- new {(address array character): type}, count # repeatedly copy slices start..end until delimiter into result[curr-result] curr-result:num <- copy 0 start:num <- copy 0 { # while next delim exists done?:bool <- greater-or-equal start, len break-if done? end:num <- find-next s, delim, start # copy start..end into result[curr-result] dest:text <- copy-range s, start, end *result <- put-index *result, curr-result, dest # slide over to next slice start <- add end, 1 curr-result <- add curr-result, 1 loop } ] scenario text-split-1 [ local-scope x:text <- new [a/b] run [ y:&:@:text <- split x, 47/slash 10:num/raw <- length *y a:text <- index *y, 0 b:text <- index *y, 1 20:@:char/raw <- copy *a 30:@:char/raw <- copy *b ] memory-should-contain [ 10 <- 2 # length of result 20:array:character <- [a] 30:array:character <- [b] ] ] scenario text-split-2 [ local-scope x:text <- new [a/b/c] run [ y:&:@:text <- split x, 47/slash 10:num/raw <- length *y a:text <- index *y, 0 b:text <- index *y, 1 c:text <- index *y, 2 20:@:char/raw <- copy *a 30:@:char/raw <- copy *b 40:@:char/raw <- copy *c ] memory-should-contain [ 10 <- 3 # length of result 20:array:character <- [a] 30:array:character <- [b] 40:array:character <- [c] ] ] scenario text-split-missing [ local-scope x:text <- new [abc] run [ y:&:@:text <- split x, 47/slash 10:num/raw <- length *y a:text <- index *y, 0 20:@:char/raw <- copy *a ] memory-should-contain [ 10 <- 1 # length of result 20:array:character <- [abc] ] ] scenario text-split-empty [ local-scope x:text <- new [] run [ y:&:@:text <- split x, 47/slash 10:num/raw <- length *y ] memory-should-contain [ 10 <- 0 # empty result ] ] scenario text-split-empty-piece [ local-scope x:text <- new [a/b//c] run [ y:&:@:text <- split x:text, 47/slash 10:num/raw <- length *y a:text <- index *y, 0 b:text <- index *y, 1 c:text <- index *y, 2 d:text <- index *y, 3 20:@:char/raw <- copy *a 30:@:char/raw <- copy *b 40:@:char/raw <- copy *c 50:@:char/raw <- copy *d ] memory-should-contain [ 10 <- 4 # length of result 20:array:character <- [a] 30:array:character <- [b] 40:array:character <- [] 50:array:character <- [c] ] ] def split-first text:text, delim:char -> x:text, y:text [ local-scope load-ingredients # empty text? return empty texts len:num <- length *text { empty?:bool <- equal len, 0 break-unless empty? x:text <- new [] y:text <- new [] return } idx:num <- find-next text, delim, 0 x:text <- copy-range text, 0, idx idx <- add idx, 1 y:text <- copy-range text, idx, len ] scenario text-split-first [ local-scope x:text <- new [a/b] run [ y:text, z:text <- split-first x, 47/slash 10:@:char/raw <- copy *y 20:@:char/raw <- copy *z ] memory-should-contain [ 10:array:character <- [a] 20:array:character <- [b] ] ] def copy-range buf:text, start:num, end:num -> result:text [ local-scope load-ingredients # if end is out of bounds, trim it len:num <- length *buf end:num <- min len, end # allocate space for result len <- subtract end, start result:text <- new character:type, len # copy start..end into result[curr-result] src-idx:num <- copy start dest-idx:num <- copy 0 { done?:bool <- greater-or-equal src-idx, end break-if done? src:char <- index *buf, src-idx *result <- put-index *result, dest-idx, src src-idx <- add src-idx, 1 dest-idx <- add dest-idx, 1 loop } ] scenario copy-range-works [ local-scope x:text <- new [abc] run [ y:text <- copy-range x, 1, 3 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [bc] ] ] scenario copy-range-out-of-bounds [ local-scope x:text <- new [abc] run [ y:text <- copy-range x, 2, 4 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [c] ] ] scenario copy-range-out-of-bounds-2 [ local-scope x:text <- new [abc] run [ y:text <- copy-range x, 3, 3 1:@:char/raw <- copy *y ] memory-should-contain [ 1:array:character <- [] ] ] def parse-whole-number in:text -> out:num, error?:bool [ local-scope load-ingredients out <- copy 0 result:num <- copy 0 # temporary location i:num <- copy 0 len:num <- length *in { done?:bool <- greater-or-equal i, len break-if done? c:char <- index *in, i x:num <- character-to-code c digit:num, error?:bool <- character-code-to-digit x return-if error? result <- multiply result, 10 result <- add result, digit i <- add i, 1 loop } # no error; all digits were valid out <- copy result ] # (contributed by Ella Couch) recipe character-code-to-digit character-code:number -> result:number, error?:boolean [ local-scope load-ingredients result <- copy 0 error? <- lesser-than character-code, 48 # '0' return-if error? error? <- greater-than character-code, 57 # '9' return-if error? result <- subtract character-code, 48 ] scenario character-code-to-digit-contain-only-digit [ local-scope a:number <- copy 48 # character code for '0' run [ 10:number/raw, 11:boolean/raw <- character-code-to-digit a ] memory-should-contain [ 10 <- 0 11 <- 0 # no error ] ] scenario character-code-to-digit-contain-only-digit-2 [ local-scope a:number <- copy 57 # character code for '9' run [ 1:number/raw, 2:boolean/raw <- character-code-to-digit a ] memory-should-contain [ 1 <- 9 2 <- 0 # no error ] ] scenario character-code-to-digit-handles-codes-lower-than-zero [ local-scope a:number <- copy 47 run [ 10:number/raw, 11:boolean/raw <- character-code-to-digit a ] memory-should-contain [ 10 <- 0 11 <- 1 # error ] ] scenario character-code-to-digit-handles-codes-larger-than-nine [ local-scope a:number <- copy 58 run [ 10:number/raw, 11:boolean/raw <- character-code-to-digit a ] memory-should-contain [ 10 <- 0 11 <- 1 # error ] ]