1 # Some useful helpers for dealing with text (arrays of characters)
3 def equal a:text, b:text -> result:bool [
6 an:num, bn:num <- deaddress a, b
7 address-equal?:boolean <- equal an, bn
8 return-if address-equal?, true
10 return-unless b, false
11 a-len:num <- length *a
12 b-len:num <- length *b
14 trace 99, [text-equal], [comparing lengths]
15 length-equal?:bool <- equal a-len, b-len
16 return-unless length-equal?, false
17 # compare each corresponding character
18 trace 99, [text-equal], [comparing characters]
21 done?:bool <- greater-or-equal i, a-len
23 a2:char <- index *a, i
24 b2:char <- index *b, i
25 chars-match?:bool <- equal a2, b2
26 return-unless chars-match?, false
33 scenario text-equal-reflexive [
37 10:bool/raw <- equal x, x
39 memory-should-contain [
40 10 <- 1 # x == x for all x
44 scenario text-equal-identical [
49 10:bool/raw <- equal x, y
51 memory-should-contain [
56 scenario text-equal-distinct-lengths [
61 10:bool/raw <- equal x, y
63 memory-should-contain [
66 trace-should-contain [
67 text-equal: comparing lengths
69 trace-should-not-contain [
70 text-equal: comparing characters
74 scenario text-equal-with-empty [
79 10:bool/raw <- equal x, y
81 memory-should-contain [
86 scenario text-equal-with-null [
91 10:bool/raw <- equal x, null
92 11:bool/raw <- equal null, x
93 12:bool/raw <- equal x, y
94 13:bool/raw <- equal y, x
95 14:bool/raw <- equal y, y
97 memory-should-contain [
104 check-trace-count-for-label 0, [error]
107 scenario text-equal-common-lengths-but-distinct [
112 10:bool/raw <- equal x, y
114 memory-should-contain [
119 # A new type to help incrementally construct texts.
120 container buffer:_elem [
125 def new-buffer capacity:num -> result:&:buffer:_elem [
128 result <- new {(buffer _elem): type}
129 *result <- put *result, length:offset, 0
132 # capacity not provided
135 data:&:@:_elem <- new _elem:type, capacity
136 *result <- put *result, data:offset, data
140 def grow-buffer buf:&:buffer:_elem -> buf:&:buffer:_elem [
144 olddata:&:@:_elem <- get *buf, data:offset
145 oldlen:num <- length *olddata
146 newlen:num <- multiply oldlen, 2
147 newdata:&:@:_elem <- new _elem:type, newlen
148 *buf <- put *buf, data:offset, newdata
152 done?:bool <- greater-or-equal i, oldlen
154 src:_elem <- index *olddata, i
155 *newdata <- put-index *newdata, i, src
161 def buffer-full? in:&:buffer:_elem -> result:bool [
164 len:num <- get *in, length:offset
165 s:&:@:_elem <- get *in, data:offset
166 capacity:num <- length *s
167 result <- greater-or-equal len, capacity
170 # most broadly applicable definition of append to a buffer
171 def append buf:&:buffer:_elem, x:_elem -> buf:&:buffer:_elem [
174 len:num <- get *buf, length:offset
176 # grow buffer if necessary
177 full?:bool <- buffer-full? buf
179 buf <- grow-buffer buf
181 s:&:@:_elem <- get *buf, data:offset
182 *s <- put-index *s, len, x
184 *buf <- put *buf, length:offset, len
187 # most broadly applicable definition of append to a buffer of characters: just
189 def append buf:&:buffer:char, x:_elem -> buf:&:buffer:char [
192 text:text <- to-text x
193 buf <- append buf, text
196 # specialization for characters that is backspace-aware
197 def append buf:&:buffer:char, c:char -> buf:&:buffer:char [
200 len:num <- get *buf, length:offset
202 # backspace? just drop last character if it exists and return
203 backspace?:bool <- equal c, 8/backspace
204 break-unless backspace?
205 empty?:bool <- lesser-or-equal len, 0
207 len <- subtract len, 1
208 *buf <- put *buf, length:offset, len
212 # grow buffer if necessary
213 full?:bool <- buffer-full? buf
215 buf <- grow-buffer buf
217 s:text <- get *buf, data:offset
218 *s <- put-index *s, len, c
220 *buf <- put *buf, length:offset, len
223 def append buf:&:buffer:_elem, t:&:@:_elem -> buf:&:buffer:_elem [
229 done?:bool <- greater-or-equal i, len
231 x:_elem <- index *t, i
238 scenario append-to-empty-buffer [
240 x:&:buffer:char <- new-buffer
244 10:num/raw <- get *x, length:offset
245 s:text <- get *x, data:offset
246 11:char/raw <- index *s, 0
247 12:char/raw <- index *s, 1
249 memory-should-contain [
250 10 <- 1 # buffer length
252 12 <- 0 # rest of buffer is empty
256 scenario append-to-buffer [
258 x:&:buffer:char <- new-buffer
264 10:num/raw <- get *x, length:offset
265 s:text <- get *x, data:offset
266 11:char/raw <- index *s, 0
267 12:char/raw <- index *s, 1
268 13:char/raw <- index *s, 2
270 memory-should-contain [
271 10 <- 2 # buffer length
274 13 <- 0 # rest of buffer is empty
278 scenario append-grows-buffer [
280 x:&:buffer:char <- new-buffer 3
281 s1:text <- get *x, data:offset
282 x <- append x, [abc] # buffer is now full
283 s2:text <- get *x, data:offset
285 10:bool/raw <- equal s1, s2
286 11:@:char/raw <- copy *s2
290 s3:text <- get *x, data:offset
291 20:bool/raw <- equal s1, s3
292 21:num/raw <- get *x, length:offset
293 30:@:char/raw <- copy *s3
295 memory-should-contain [
296 # before +buffer-filled
297 10 <- 1 # no change in data pointer after original append
298 11 <- 3 # size of data
303 20 <- 0 # data pointer has grown after second append
304 21 <- 4 # final length
305 30 <- 6 # but data's capacity has doubled
315 scenario buffer-append-handles-backspace [
317 x:&:buffer:char <- new-buffer
320 c:char <- copy 8/backspace
322 s:text <- buffer-to-array x
323 10:@:char/raw <- copy *s
325 memory-should-contain [
332 scenario append-to-buffer-of-non-characters [
334 x:&:buffer:text <- new-buffer 1/capacity
338 def buffer-to-array in:&:buffer:_elem -> result:&:@:_elem [
341 # propagate null buffer
342 return-unless in, null
343 len:num <- get *in, length:offset
344 s:&:@:_elem <- get *in, data:offset
345 # we can't just return s because it is usually the wrong length
346 result <- new _elem:type, len
349 done?:bool <- greater-or-equal i, len
351 src:_elem <- index *s, i
352 *result <- put-index *result, i, src
358 def blank? x:&:@:_elem -> result:bool [
361 return-unless x, true
363 result <- equal len, 0
366 # Append any number of texts together.
367 # A later layer also translates calls to this to implicitly call to-text, so
368 # append to string becomes effectively dynamically typed.
370 # Beware though: this hack restricts how much 'append' can be overridden. Any
371 # new variants that match:
373 # will never ever get used.
374 def append first:text -> result:text [
377 buf:&:buffer:char <- new-buffer 30
381 buf <- append buf, first
383 # append remaining inputs
385 arg:text, arg-found?:bool <- next-input
386 break-unless arg-found?
388 buf <- append buf, arg
391 result <- buffer-to-array buf
394 scenario text-append-1 [
396 x:text <- new [hello,]
397 y:text <- new [ world!]
399 z:text <- append x, y
400 10:@:char/raw <- copy *z
402 memory-should-contain [
403 10:array:character <- [hello, world!]
407 scenario text-append-null [
410 y:text <- new [ world!]
412 z:text <- append x, y
413 10:@:char/raw <- copy *z
415 memory-should-contain [
416 10:array:character <- [ world!]
420 scenario text-append-null-2 [
422 x:text <- new [hello,]
425 z:text <- append x, y
426 10:@:char/raw <- copy *z
428 memory-should-contain [
429 10:array:character <- [hello,]
433 scenario text-append-multiary [
435 x:text <- new [hello, ]
436 y:text <- new [world]
439 z:text <- append x, y, z
440 10:@:char/raw <- copy *z
442 memory-should-contain [
443 10:array:character <- [hello, world!]
447 scenario replace-character-in-text [
451 x <- replace x, 98/b, 122/z
452 10:@:char/raw <- copy *x
454 memory-should-contain [
455 10:array:character <- [azc]
459 def replace s:text, oldc:char, newc:char, from:num/optional -> s:text [
463 i:num <- find-next s, oldc, from
464 done?:bool <- greater-or-equal i, len
466 *s <- put-index *s, i, newc
468 s <- replace s, oldc, newc, i
471 scenario replace-character-at-start [
475 x <- replace x, 97/a, 122/z
476 10:@:char/raw <- copy *x
478 memory-should-contain [
479 10:array:character <- [zbc]
483 scenario replace-character-at-end [
487 x <- replace x, 99/c, 122/z
488 10:@:char/raw <- copy *x
490 memory-should-contain [
491 10:array:character <- [abz]
495 scenario replace-character-missing [
499 x <- replace x, 100/d, 122/z
500 10:@:char/raw <- copy *x
502 memory-should-contain [
503 10:array:character <- [abc]
507 scenario replace-all-characters [
509 x:text <- new [banana]
511 x <- replace x, 97/a, 122/z
512 10:@:char/raw <- copy *x
514 memory-should-contain [
515 10:array:character <- [bznznz]
519 # replace underscores in first with remaining args
520 def interpolate template:text -> result:text [
522 load-inputs # consume just the template
523 # compute result-len, space to allocate for result
524 tem-len:num <- length *template
525 result-len:num <- copy tem-len
527 # while inputs remain
528 a:text, arg-received?:bool <- next-input
529 break-unless arg-received?
530 # result-len = result-len + arg.length - 1 (for the 'underscore' being replaced)
531 a-len:num <- length *a
532 result-len <- add result-len, a-len
533 result-len <- subtract result-len, 1
537 _ <- next-input # skip template
538 result <- new character:type, result-len
539 # repeatedly copy sections of template and 'holes' into result
540 result-idx:num <- copy 0
544 a:text, arg-received?:bool <- next-input
545 break-unless arg-received?
546 # copy template into result until '_'
548 # while i < template.length
549 tem-done?:bool <- greater-or-equal i, tem-len
550 break-if tem-done?, +done
551 # while template[i] != '_'
552 in:char <- index *template, i
553 underscore?:bool <- equal in, 95/_
555 # result[result-idx] = template[i]
556 *result <- put-index *result, result-idx, in
558 result-idx <- add result-idx, 1
561 # copy 'a' into result
565 arg-done?:bool <- greater-or-equal j, a-len
567 # result[result-idx] = a[j]
568 in:char <- index *a, j
569 *result <- put-index *result, result-idx, in
571 result-idx <- add result-idx, 1
574 # skip '_' in template
576 loop # interpolate next arg
579 # done with holes; copy rest of template directly into result
581 # while i < template.length
582 tem-done?:bool <- greater-or-equal i, tem-len
584 # result[result-idx] = template[i]
585 in:char <- index *template, i
586 *result <- put-index *result, result-idx, in
588 result-idx <- add result-idx, 1
593 scenario interpolate-works [
595 x:text <- new [abc_ghi]
598 z:text <- interpolate x, y
599 10:@:char/raw <- copy *z
601 memory-should-contain [
602 10:array:character <- [abcdefghi]
606 scenario interpolate-at-start [
608 x:text <- new [_, hello!]
611 z:text <- interpolate x, y
612 10:@:char/raw <- copy *z
614 memory-should-contain [
615 10:array:character <- [abc, hello!]
616 22 <- 0 # out of bounds
620 scenario interpolate-at-end [
622 x:text <- new [hello, _]
625 z:text <- interpolate x, y
626 10:@:char/raw <- copy *z
628 memory-should-contain [
629 10:array:character <- [hello, abc]
633 # result:bool <- space? c:char
634 def space? c:char -> result:bool [
637 # most common case first
638 result <- equal c, 32/space
640 result <- equal c, 10/newline
642 result <- equal c, 9/tab
644 result <- equal c, 13/carriage-return
646 # remaining uncommon cases in sorted order
647 # http://unicode.org code-points in unicode-set Z and Pattern_White_Space
648 result <- equal c, 11/ctrl-k
650 result <- equal c, 12/ctrl-l
652 result <- equal c, 133/ctrl-0085
654 result <- equal c, 160/no-break-space
656 result <- equal c, 5760/ogham-space-mark
658 result <- equal c, 8192/en-quad
660 result <- equal c, 8193/em-quad
662 result <- equal c, 8194/en-space
664 result <- equal c, 8195/em-space
666 result <- equal c, 8196/three-per-em-space
668 result <- equal c, 8197/four-per-em-space
670 result <- equal c, 8198/six-per-em-space
672 result <- equal c, 8199/figure-space
674 result <- equal c, 8200/punctuation-space
676 result <- equal c, 8201/thin-space
678 result <- equal c, 8202/hair-space
680 result <- equal c, 8206/left-to-right
682 result <- equal c, 8207/right-to-left
684 result <- equal c, 8232/line-separator
686 result <- equal c, 8233/paragraph-separator
688 result <- equal c, 8239/narrow-no-break-space
690 result <- equal c, 8287/medium-mathematical-space
692 result <- equal c, 12288/ideographic-space
695 def trim s:text -> result:text [
699 # left trim: compute start
703 at-end?:bool <- greater-or-equal start, len
705 result <- new character:type, 0
708 curr:char <- index *s, start
709 whitespace?:bool <- space? curr
710 break-unless whitespace?
711 start <- add start, 1
714 # right trim: compute end
715 end:num <- subtract len, 1
717 not-at-start?:bool <- greater-than end, start
718 assert not-at-start?, [end ran up against start]
719 curr:char <- index *s, end
720 whitespace?:bool <- space? curr
721 break-unless whitespace?
722 end <- subtract end, 1
725 # result = new character[end+1 - start]
726 new-len:num <- subtract end, start, -1
727 result:text <- new character:type, new-len
728 # copy the untrimmed parts between start and end
733 done?:bool <- greater-than i, end
736 src:char <- index *s, i
737 *result <- put-index *result, j, src
744 scenario trim-unmodified [
749 1:@:char/raw <- copy *y
751 memory-should-contain [
752 1:array:character <- [abc]
761 1:@:char/raw <- copy *y
763 memory-should-contain [
764 1:array:character <- [abc]
768 scenario trim-right [
773 1:@:char/raw <- copy *y
775 memory-should-contain [
776 1:array:character <- [abc]
780 scenario trim-left-right [
782 x:text <- new [ abc ]
785 1:@:char/raw <- copy *y
787 memory-should-contain [
788 1:array:character <- [abc]
792 scenario trim-newline-tab [
798 1:@:char/raw <- copy *y
800 memory-should-contain [
801 1:array:character <- [abc]
805 def find-next text:text, pattern:char, idx:num -> next-index:num [
808 len:num <- length *text
810 eof?:bool <- greater-or-equal idx, len
812 curr:char <- index *text, idx
813 found?:bool <- equal curr, pattern
821 scenario text-find-next [
825 10:num/raw <- find-next x, 47/slash, 0/start-index
827 memory-should-contain [
832 scenario text-find-next-empty [
836 10:num/raw <- find-next x, 47/slash, 0/start-index
838 memory-should-contain [
843 scenario text-find-next-initial [
847 10:num/raw <- find-next x, 47/slash, 0/start-index
849 memory-should-contain [
850 10 <- 0 # prefix match
854 scenario text-find-next-final [
858 10:num/raw <- find-next x, 47/slash, 0/start-index
860 memory-should-contain [
861 10 <- 3 # suffix match
865 scenario text-find-next-missing [
869 10:num/raw <- find-next x, 47/slash, 0/start-index
871 memory-should-contain [
876 scenario text-find-next-invalid-index [
880 10:num/raw <- find-next x, 47/slash, 4/start-index
882 memory-should-contain [
887 scenario text-find-next-first [
889 x:text <- new [ab/c/]
891 10:num/raw <- find-next x, 47/slash, 0/start-index
893 memory-should-contain [
894 10 <- 2 # first '/' of multiple
898 scenario text-find-next-second [
900 x:text <- new [ab/c/]
902 10:num/raw <- find-next x, 47/slash, 3/start-index
904 memory-should-contain [
905 10 <- 4 # second '/' of multiple
909 # search for a pattern of multiple characters
910 # fairly dumb algorithm
911 def find-next text:text, pattern:text, idx:num -> next-index:num [
914 first:char <- index *pattern, 0
915 # repeatedly check for match at current idx
916 len:num <- length *text
918 # does some unnecessary work checking even when there isn't enough of text left
919 done?:bool <- greater-or-equal idx, len
921 found?:bool <- match-at text, pattern, idx
924 # optimization: skip past indices that definitely won't match
925 idx <- find-next text, first, idx
931 scenario find-next-text-1 [
936 10:num/raw <- find-next x, y, 0
938 memory-should-contain [
943 scenario find-next-text-2 [
948 10:num/raw <- find-next x, y, 1
950 memory-should-contain [
955 scenario find-next-no-match [
960 10:num/raw <- find-next x, y, 0
962 memory-should-contain [
967 scenario find-next-suffix-match [
972 10:num/raw <- find-next x, y, 0
974 memory-should-contain [
979 scenario find-next-suffix-match-2 [
984 10:num/raw <- find-next x, y, 0
986 memory-should-contain [
991 # checks if pattern matches at index 'idx'
992 def match-at text:text, pattern:text, idx:num -> result:bool [
995 pattern-len:num <- length *pattern
996 # check that there's space left for the pattern
997 x:num <- length *text
998 x <- subtract x, pattern-len
999 enough-room?:bool <- lesser-or-equal idx, x
1000 return-unless enough-room?, false/not-found
1001 # check each character of pattern
1002 pattern-idx:num <- copy 0
1004 done?:bool <- greater-or-equal pattern-idx, pattern-len
1006 c:char <- index *text, idx
1007 exp:char <- index *pattern, pattern-idx
1008 match?:bool <- equal c, exp
1009 return-unless match?, false/not-found
1011 pattern-idx <- add pattern-idx, 1
1017 scenario match-at-checks-pattern-at-index [
1022 10:bool/raw <- match-at x, y, 0
1024 memory-should-contain [
1025 10 <- 1 # match found
1029 scenario match-at-reflexive [
1033 10:bool/raw <- match-at x, x, 0
1035 memory-should-contain [
1036 10 <- 1 # match found
1040 scenario match-at-outside-bounds [
1045 10:bool/raw <- match-at x, y, 4
1047 memory-should-contain [
1048 10 <- 0 # never matches
1052 scenario match-at-empty-pattern [
1057 10:bool/raw <- match-at x, y, 0
1059 memory-should-contain [
1060 10 <- 1 # always matches empty pattern given a valid index
1064 scenario match-at-empty-pattern-outside-bound [
1069 10:bool/raw <- match-at x, y, 4
1071 memory-should-contain [
1076 scenario match-at-empty-text [
1081 10:bool/raw <- match-at x, y, 0
1083 memory-should-contain [
1088 scenario match-at-empty-against-empty [
1092 10:bool/raw <- match-at x, x, 0
1094 memory-should-contain [
1095 10 <- 1 # matches because pattern is also empty
1099 scenario match-at-inside-bounds [
1104 10:bool/raw <- match-at x, y, 1
1106 memory-should-contain [
1111 scenario match-at-inside-bounds-2 [
1116 10:bool/raw <- match-at x, y, 0
1118 memory-should-contain [
1123 def split s:text, delim:char -> result:&:@:text [
1126 # empty text? return empty array
1127 len:num <- length *s
1129 empty?:bool <- equal len, 0
1131 result <- new {(address array character): type}, 0
1134 # count #pieces we need room for
1135 count:num <- copy 1 # n delimiters = n+1 pieces
1138 idx <- find-next s, delim, idx
1139 done?:bool <- greater-or-equal idx, len
1142 count <- add count, 1
1146 result <- new {(address array character): type}, count
1147 # repeatedly copy slices start..end until delimiter into result[curr-result]
1148 curr-result:num <- copy 0
1151 # while next delim exists
1152 done?:bool <- greater-or-equal start, len
1154 end:num <- find-next s, delim, start
1155 # copy start..end into result[curr-result]
1156 dest:text <- copy-range s, start, end
1157 *result <- put-index *result, curr-result, dest
1158 # slide over to next slice
1160 curr-result <- add curr-result, 1
1165 scenario text-split-1 [
1169 y:&:@:text <- split x, 47/slash
1170 10:num/raw <- length *y
1171 a:text <- index *y, 0
1172 b:text <- index *y, 1
1173 20:@:char/raw <- copy *a
1174 30:@:char/raw <- copy *b
1176 memory-should-contain [
1177 10 <- 2 # length of result
1178 20:array:character <- [a]
1179 30:array:character <- [b]
1183 scenario text-split-2 [
1185 x:text <- new [a/b/c]
1187 y:&:@:text <- split x, 47/slash
1188 10:num/raw <- length *y
1189 a:text <- index *y, 0
1190 b:text <- index *y, 1
1191 c:text <- index *y, 2
1192 20:@:char/raw <- copy *a
1193 30:@:char/raw <- copy *b
1194 40:@:char/raw <- copy *c
1196 memory-should-contain [
1197 10 <- 3 # length of result
1198 20:array:character <- [a]
1199 30:array:character <- [b]
1200 40:array:character <- [c]
1204 scenario text-split-missing [
1208 y:&:@:text <- split x, 47/slash
1209 10:num/raw <- length *y
1210 a:text <- index *y, 0
1211 20:@:char/raw <- copy *a
1213 memory-should-contain [
1214 10 <- 1 # length of result
1215 20:array:character <- [abc]
1219 scenario text-split-empty [
1223 y:&:@:text <- split x, 47/slash
1224 10:num/raw <- length *y
1226 memory-should-contain [
1227 10 <- 0 # empty result
1231 scenario text-split-empty-piece [
1233 x:text <- new [a/b//c]
1235 y:&:@:text <- split x:text, 47/slash
1236 10:num/raw <- length *y
1237 a:text <- index *y, 0
1238 b:text <- index *y, 1
1239 c:text <- index *y, 2
1240 d:text <- index *y, 3
1241 20:@:char/raw <- copy *a
1242 30:@:char/raw <- copy *b
1243 40:@:char/raw <- copy *c
1244 50:@:char/raw <- copy *d
1246 memory-should-contain [
1247 10 <- 4 # length of result
1248 20:array:character <- [a]
1249 30:array:character <- [b]
1250 40:array:character <- []
1251 50:array:character <- [c]
1255 def split-first text:text, delim:char -> x:text, y:text [
1258 # empty text? return empty texts
1259 len:num <- length *text
1261 empty?:bool <- equal len, 0
1267 idx:num <- find-next text, delim, 0
1268 x:text <- copy-range text, 0, idx
1270 y:text <- copy-range text, idx, len
1273 scenario text-split-first [
1277 y:text, z:text <- split-first x, 47/slash
1278 10:@:char/raw <- copy *y
1279 20:@:char/raw <- copy *z
1281 memory-should-contain [
1282 10:array:character <- [a]
1283 20:array:character <- [b]
1287 def copy-range buf:text, start:num, end:num -> result:text [
1290 # if end is out of bounds, trim it
1291 len:num <- length *buf
1292 end:num <- min len, end
1293 # allocate space for result
1294 len <- subtract end, start
1295 result:text <- new character:type, len
1296 # copy start..end into result[curr-result]
1297 src-idx:num <- copy start
1298 dest-idx:num <- copy 0
1300 done?:bool <- greater-or-equal src-idx, end
1302 src:char <- index *buf, src-idx
1303 *result <- put-index *result, dest-idx, src
1304 src-idx <- add src-idx, 1
1305 dest-idx <- add dest-idx, 1
1310 scenario copy-range-works [
1314 y:text <- copy-range x, 1, 3
1315 1:@:char/raw <- copy *y
1317 memory-should-contain [
1318 1:array:character <- [bc]
1322 scenario copy-range-out-of-bounds [
1326 y:text <- copy-range x, 2, 4
1327 1:@:char/raw <- copy *y
1329 memory-should-contain [
1330 1:array:character <- [c]
1334 scenario copy-range-out-of-bounds-2 [
1338 y:text <- copy-range x, 3, 3
1339 1:@:char/raw <- copy *y
1341 memory-should-contain [
1342 1:array:character <- []
1346 def parse-whole-number in:text -> out:num, error?:bool [
1350 result:num <- copy 0 # temporary location
1352 len:num <- length *in
1354 done?:bool <- greater-or-equal i, len
1356 c:char <- index *in, i
1357 x:num <- character-to-code c
1358 digit:num, error?:bool <- character-code-to-digit x
1360 result <- multiply result, 10
1361 result <- add result, digit
1365 # no error; all digits were valid
1369 # (contributed by Ella Couch)
1370 recipe character-code-to-digit character-code:number -> result:number, error?:boolean [
1374 error? <- lesser-than character-code, 48 # '0'
1376 error? <- greater-than character-code, 57 # '9'
1378 result <- subtract character-code, 48
1381 scenario character-code-to-digit-contain-only-digit [
1383 a:number <- copy 48 # character code for '0'
1385 10:number/raw, 11:boolean/raw <- character-code-to-digit a
1387 memory-should-contain [
1393 scenario character-code-to-digit-contain-only-digit-2 [
1395 a:number <- copy 57 # character code for '9'
1397 1:number/raw, 2:boolean/raw <- character-code-to-digit a
1399 memory-should-contain [
1405 scenario character-code-to-digit-handles-codes-lower-than-zero [
1409 10:number/raw, 11:boolean/raw <- character-code-to-digit a
1411 memory-should-contain [
1417 scenario character-code-to-digit-handles-codes-larger-than-nine [
1421 10:number/raw, 11:boolean/raw <- character-code-to-digit a
1423 memory-should-contain [