diff options
Diffstat (limited to 'archive/2.vm/061text.mu')
-rw-r--r-- | archive/2.vm/061text.mu | 1427 |
1 files changed, 1427 insertions, 0 deletions
diff --git a/archive/2.vm/061text.mu b/archive/2.vm/061text.mu new file mode 100644 index 00000000..4d46319b --- /dev/null +++ b/archive/2.vm/061text.mu @@ -0,0 +1,1427 @@ +# Some useful helpers for dealing with text (arrays of characters) + +def equal a:text, b:text -> result:bool [ + local-scope + load-inputs + an:num, bn:num <- deaddress a, b + address-equal?:boolean <- equal an, bn + return-if address-equal?, true + return-unless a, false + return-unless b, 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 + return-unless length-equal?, false + # 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 + return-unless chars-match?, false + i <- add i, 1 + loop + } + return true +] + +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 null + run [ + 10:bool/raw <- equal x, null + 11:bool/raw <- equal null, 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. +container buffer:_elem [ + length:num + data:&:@:_elem +] + +def new-buffer capacity:num -> result:&:buffer:_elem [ + local-scope + load-inputs + result <- new {(buffer _elem): type} + *result <- put *result, length:offset, 0 + { + break-if capacity + # capacity not provided + capacity <- copy 10 + } + data:&:@:_elem <- new _elem:type, capacity + *result <- put *result, data:offset, data + return result +] + +def grow-buffer buf:&:buffer:_elem -> buf:&:buffer:_elem [ + local-scope + load-inputs + # double buffer size + olddata:&:@:_elem <- get *buf, data:offset + oldlen:num <- length *olddata + newlen:num <- multiply oldlen, 2 + newdata:&:@:_elem <- new _elem: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:_elem <- index *olddata, i + *newdata <- put-index *newdata, i, src + i <- add i, 1 + loop + } +] + +def buffer-full? in:&:buffer:_elem -> result:bool [ + local-scope + load-inputs + len:num <- get *in, length:offset + s:&:@:_elem <- get *in, data:offset + capacity:num <- length *s + result <- greater-or-equal len, capacity +] + +# most broadly applicable definition of append to a buffer +def append buf:&:buffer:_elem, x:_elem -> buf:&:buffer:_elem [ + local-scope + load-inputs + len:num <- get *buf, length:offset + { + # grow buffer if necessary + full?:bool <- buffer-full? buf + break-unless full? + buf <- grow-buffer buf + } + s:&:@:_elem <- get *buf, data:offset + *s <- put-index *s, len, x + len <- add len, 1 + *buf <- put *buf, length:offset, len +] + +# most broadly applicable definition of append to a buffer of characters: just +# call to-text +def append buf:&:buffer:char, x:_elem -> buf:&:buffer:char [ + local-scope + load-inputs + text:text <- to-text x + buf <- append buf, text +] + +# specialization for characters that is backspace-aware +def append buf:&:buffer:char, c:char -> buf:&:buffer:char [ + local-scope + load-inputs + 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:_elem, t:&:@:_elem -> buf:&:buffer:_elem [ + local-scope + load-inputs + len:num <- length *t + i:num <- copy 0 + { + done?:bool <- greater-or-equal i, len + break-if done? + x:_elem <- index *t, i + buf <- append buf, x + i <- add i, 1 + loop + } +] + +scenario append-to-empty-buffer [ + local-scope + x:&:buffer:char <- new-buffer + run [ + c:char <- copy 97/a + x <- append x, c + 10:num/raw <- get *x, length:offset + s:text <- get *x, data:offset + 11:char/raw <- index *s, 0 + 12:char/raw <- index *s, 1 + ] + memory-should-contain [ + 10 <- 1 # buffer length + 11 <- 97 # a + 12 <- 0 # rest of buffer is empty + ] +] + +scenario append-to-buffer [ + local-scope + x:&:buffer:char <- new-buffer + c:char <- copy 97/a + x <- append x, c + run [ + c <- copy 98/b + x <- append x, c + 10:num/raw <- get *x, length:offset + s:text <- get *x, data:offset + 11:char/raw <- index *s, 0 + 12:char/raw <- index *s, 1 + 13:char/raw <- index *s, 2 + ] + memory-should-contain [ + 10 <- 2 # buffer length + 11 <- 97 # a + 12 <- 98 # b + 13 <- 0 # rest of buffer is empty + ] +] + +scenario append-grows-buffer [ + local-scope + x:&:buffer:char <- new-buffer 3 + s1:text <- get *x, data:offset + x <- append x, [abc] # buffer is now full + s2:text <- get *x, data:offset + run [ + 10:bool/raw <- equal s1, s2 + 11:@:char/raw <- copy *s2 + +buffer-filled + c:char <- copy 100/d + x <- append x, c + s3:text <- get *x, data:offset + 20:bool/raw <- equal s1, s3 + 21:num/raw <- get *x, length:offset + 30:@:char/raw <- copy *s3 + ] + memory-should-contain [ + # before +buffer-filled + 10 <- 1 # no change in data pointer after original append + 11 <- 3 # size of data + 12 <- 97 # data + 13 <- 98 + 14 <- 99 + # in the end + 20 <- 0 # data pointer has grown after second append + 21 <- 4 # final length + 30 <- 6 # but data's capacity has doubled + 31 <- 97 # data + 32 <- 98 + 33 <- 99 + 34 <- 100 + 35 <- 0 + 36 <- 0 + ] +] + +scenario buffer-append-handles-backspace [ + local-scope + x:&:buffer:char <- new-buffer + x <- append x, [ab] + run [ + c:char <- copy 8/backspace + x <- append x, c + s:text <- buffer-to-array x + 10:@:char/raw <- copy *s + ] + memory-should-contain [ + 10 <- 1 # length + 11 <- 97 # contents + 12 <- 0 + ] +] + +scenario append-to-buffer-of-non-characters [ + local-scope + x:&:buffer:text <- new-buffer 1/capacity + # no errors +] + +def buffer-to-array in:&:buffer:_elem -> result:&:@:_elem [ + local-scope + load-inputs + # propagate null buffer + return-unless in, null + len:num <- get *in, length:offset + s:&:@:_elem <- get *in, data:offset + # we can't just return s because it is usually the wrong length + result <- new _elem:type, len + i:num <- copy 0 + { + done?:bool <- greater-or-equal i, len + break-if done? + src:_elem <- index *s, i + *result <- put-index *result, i, src + i <- add i, 1 + loop + } +] + +def blank? x:&:@:_elem -> result:bool [ + local-scope + load-inputs + return-unless x, true + len:num <- length *x + result <- equal len, 0 +] + +# Append any number of texts together. +# A later layer also translates calls to this to implicitly call to-text, so +# append to string becomes effectively dynamically typed. +# +# Beware though: this hack restricts how much 'append' can be overridden. Any +# new variants that match: +# append _:text, ___ +# will never ever get used. +def append first:text -> result:text [ + local-scope + load-inputs + buf:&:buffer:char <- new-buffer 30 + # append first input + { + break-unless first + buf <- append buf, first + } + # append remaining inputs + { + arg:text, arg-found?:bool <- next-input + break-unless arg-found? + loop-unless arg + buf <- append buf, arg + loop + } + result <- buffer-to-array buf +] + +scenario text-append-1 [ + local-scope + x:text <- new [hello,] + y:text <- new [ world!] + run [ + z:text <- append x, y + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [hello, world!] + ] +] + +scenario text-append-null [ + local-scope + x:text <- copy null + y:text <- new [ world!] + run [ + z:text <- append x, y + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [ world!] + ] +] + +scenario text-append-null-2 [ + local-scope + x:text <- new [hello,] + y:text <- copy null + run [ + z:text <- append x, y + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [hello,] + ] +] + +scenario text-append-multiary [ + local-scope + x:text <- new [hello, ] + y:text <- new [world] + z:text <- new [!] + run [ + z:text <- append x, y, z + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [hello, world!] + ] +] + +scenario replace-character-in-text [ + local-scope + x:text <- new [abc] + run [ + x <- replace x, 98/b, 122/z + 10:@:char/raw <- copy *x + ] + memory-should-contain [ + 10:array:character <- [azc] + ] +] + +def replace s:text, oldc:char, newc:char, from:num/optional -> s:text [ + local-scope + load-inputs + len:num <- length *s + i:num <- find-next s, oldc, from + done?:bool <- greater-or-equal i, len + return-if done? + *s <- put-index *s, i, newc + i <- add i, 1 + s <- replace s, oldc, newc, i +] + +scenario replace-character-at-start [ + local-scope + x:text <- new [abc] + run [ + x <- replace x, 97/a, 122/z + 10:@:char/raw <- copy *x + ] + memory-should-contain [ + 10:array:character <- [zbc] + ] +] + +scenario replace-character-at-end [ + local-scope + x:text <- new [abc] + run [ + x <- replace x, 99/c, 122/z + 10:@:char/raw <- copy *x + ] + memory-should-contain [ + 10:array:character <- [abz] + ] +] + +scenario replace-character-missing [ + local-scope + x:text <- new [abc] + run [ + x <- replace x, 100/d, 122/z + 10:@:char/raw <- copy *x + ] + memory-should-contain [ + 10:array:character <- [abc] + ] +] + +scenario replace-all-characters [ + local-scope + x:text <- new [banana] + run [ + x <- replace x, 97/a, 122/z + 10:@:char/raw <- copy *x + ] + memory-should-contain [ + 10:array:character <- [bznznz] + ] +] + +# replace underscores in first with remaining args +def interpolate template:text -> result:text [ + local-scope + load-inputs # consume just the template + # compute result-len, space to allocate for result + tem-len:num <- length *template + result-len:num <- copy tem-len + { + # while inputs remain + a:text, arg-received?:bool <- next-input + break-unless arg-received? + # result-len = result-len + arg.length - 1 (for the 'underscore' being replaced) + a-len:num <- length *a + result-len <- add result-len, a-len + result-len <- subtract result-len, 1 + loop + } + rewind-inputs + _ <- next-input # skip template + result <- new character:type, result-len + # repeatedly copy sections of template and 'holes' into result + result-idx:num <- copy 0 + i:num <- copy 0 + { + # while arg received + a:text, arg-received?:bool <- next-input + break-unless arg-received? + # copy template into result until '_' + { + # while i < template.length + tem-done?:bool <- greater-or-equal i, tem-len + break-if tem-done?, +done + # while template[i] != '_' + in:char <- index *template, i + underscore?:bool <- equal in, 95/_ + break-if underscore? + # result[result-idx] = template[i] + *result <- put-index *result, result-idx, in + i <- add i, 1 + result-idx <- add result-idx, 1 + loop + } + # copy 'a' into result + j:num <- copy 0 + { + # while j < a.length + arg-done?:bool <- greater-or-equal j, a-len + break-if arg-done? + # result[result-idx] = a[j] + in:char <- index *a, j + *result <- put-index *result, result-idx, in + j <- add j, 1 + result-idx <- add result-idx, 1 + loop + } + # skip '_' in template + i <- add i, 1 + loop # interpolate next arg + } + +done + # done with holes; copy rest of template directly into result + { + # while i < template.length + tem-done?:bool <- greater-or-equal i, tem-len + break-if tem-done? + # result[result-idx] = template[i] + in:char <- index *template, i + *result <- put-index *result, result-idx, in + i <- add i, 1 + result-idx <- add result-idx, 1 + loop + } +] + +scenario interpolate-works [ + local-scope + x:text <- new [abc_ghi] + y:text <- new [def] + run [ + z:text <- interpolate x, y + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [abcdefghi] + ] +] + +scenario interpolate-at-start [ + local-scope + x:text <- new [_, hello!] + y:text <- new [abc] + run [ + z:text <- interpolate x, y + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [abc, hello!] + 22 <- 0 # out of bounds + ] +] + +scenario interpolate-at-end [ + local-scope + x:text <- new [hello, _] + y:text <- new [abc] + run [ + z:text <- interpolate x, y + 10:@:char/raw <- copy *z + ] + memory-should-contain [ + 10:array:character <- [hello, abc] + ] +] + +# result:bool <- space? c:char +def space? c:char -> result:bool [ + local-scope + load-inputs + # most common case first + result <- equal c, 32/space + return-if result + result <- equal c, 10/newline + return-if result + result <- equal c, 9/tab + return-if result + result <- equal c, 13/carriage-return + return-if result + # remaining uncommon cases in sorted order + # http://unicode.org code-points in unicode-set Z and Pattern_White_Space + result <- equal c, 11/ctrl-k + return-if result + result <- equal c, 12/ctrl-l + return-if result + result <- equal c, 133/ctrl-0085 + return-if result + result <- equal c, 160/no-break-space + return-if result + result <- equal c, 5760/ogham-space-mark + return-if result + result <- equal c, 8192/en-quad + return-if result + result <- equal c, 8193/em-quad + return-if result + result <- equal c, 8194/en-space + return-if result + result <- equal c, 8195/em-space + return-if result + result <- equal c, 8196/three-per-em-space + return-if result + result <- equal c, 8197/four-per-em-space + return-if result + result <- equal c, 8198/six-per-em-space + return-if result + result <- equal c, 8199/figure-space + return-if result + result <- equal c, 8200/punctuation-space + return-if result + result <- equal c, 8201/thin-space + return-if result + result <- equal c, 8202/hair-space + return-if result + result <- equal c, 8206/left-to-right + return-if result + result <- equal c, 8207/right-to-left + return-if result + result <- equal c, 8232/line-separator + return-if result + result <- equal c, 8233/paragraph-separator + return-if result + result <- equal c, 8239/narrow-no-break-space + return-if result + result <- equal c, 8287/medium-mathematical-space + return-if result + result <- equal c, 12288/ideographic-space +] + +def trim s:text -> result:text [ + local-scope + load-inputs + len:num <- length *s + # left trim: compute 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-inputs + 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-inputs + 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-inputs + 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 + return-unless enough-room?, false/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 + return-unless match?, false/not-found + idx <- add idx, 1 + pattern-idx <- add pattern-idx, 1 + loop + } + return true/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-inputs + # 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-inputs + # 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-inputs + # 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-inputs + 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-inputs + 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 + ] +] |