diff --git a/_01.Language-basics.sz b/_01.Language-basics.sz index 1947a13..7b5f52c 100644 --- a/_01.Language-basics.sz +++ b/_01.Language-basics.sz @@ -1734,6 +1734,8 @@ Characters are written @code{#\a}, @code{#\space}, @code{#\newline}. They can be (str "a" "b" "c") ;; "abc": any number of strings (string-upcase "björn") ;; "BJÖRN" (string-trim " hi ") ;; "hi" +(string-trim-left " hi ") ;; "hi " +(string-trim-right " hi ") ;; " hi" (string-pad-left "7" 3 #:with #\0) ;; "007" (string-split "a,b,,c" ",") ;; ["a" "b" "" "c"]: a Vec (string-join ["a" "b" "c"] ", ") ;; "a, b, c" @@ -1747,7 +1749,7 @@ All of these compare strings ordinally, byte by byte, which for UTF-8 is the ord @subsubsection{Cursors} -A cursor is a position in a string. It is as cheap as an int, but it always sits on a character boundary, and it can only be moved one character at a time, which is what makes it safe. Every cursor function takes the string as well as the cursor. +A cursor is a position in a string. It is as cheap as an int, but it always sits on a character boundary, and it can only be moved one character at a time, which is what makes it safe. Every cursor function takes the string as well as the cursor. Positions are always cursors: nothing takes a byte index, and the searches answer cursors too. @codeblock[#:lang "bjolang"]|{ (: capitalise (-> string string)) @@ -1769,6 +1771,15 @@ A cursor is a position in a string. It is as cheap as an int, but it always sits (None s))) (first-word "hello there world") ;; "hello" + +;; string-search finds where a string starts; string-search-backward, the last one. +(: extension (-> string string)) +(defun (extension path) + (match (string-search-backward path ".") + ((Some dot) (substring path (string-cursor-next path dot) (string-cursor-end path))) + (None ""))) + +(extension "notes.backup.txt") ;; "txt" }| The folds walk a string for you, and are usually simpler than a cursor: @@ -1785,6 +1796,42 @@ The folds walk a string for you, and are usually simpler than a cursor: (count-vowels "Hello World") ;; 3 }| +@subsubsection{Slices} + +@code{substring}, @code{string-trim} and @code{string-split} copy the text they answer. A slice is the same text without the copy: a string that shares the bytes of the string it was taken from. Only the forms ending in @code{/slice} make one: + +@codeblock[#:lang "bjolang"]|{ +(substring/slice s start end) +(string-trim/slice s) +(string-trim-left/slice s) +(string-trim-right/slice s) +(string-split/slice s sep) +}| + +A slice is a @code{string} like any other. It equals, hashes and orders by its text, so it finds a map key with that text, and it can be passed anywhere a string can. That makes slices the thing to reach for when a scanner cuts many short pieces out of one text and only looks at them: + +@codeblock[#:lang "bjolang"]|{ +;; "name = Björn; age = 42" into a map, without copying a key or a value +;; before it is needed. +(: settings (-> string (Map string string))) +(defun (settings line) + (loop (:for field (string-split/slice line ";")) + (:let parts (string-split/slice field "=")) + (:when (= (vec-length parts) 2)) + (:acc m (mapping ((string-copy (string-trim/slice (vec-ref parts 0))) + . (string-copy (string-trim/slice (vec-ref parts 1)))))) + => m)) + +(map-ref (settings "name = Björn; age = 42") "age") ;; "42" +}| + +Two things are different about a slice: + +@itemlist{ + @item{@strong{Its cursors are its source's.} A cursor found in a slice, by @code{string-search}, @code{string-index} or a regex, is a position in the string the slice was taken from, and @code{(string-cursor-start sl)} is where the slice begins in it.} + @item{@strong{It keeps all of its source alive.} A slice of ten bytes out of a file of a megabyte holds on to the megabyte. That is why the plain forms copy, and why the example copies what it keeps: @code{(string-copy s)} gives a string bytes of its own, and is @code{s} itself when it already has them.} +} + @subsubsection{Building strings} Appending strings in a loop copies the whole string every time. Use a string builder instead, or the @code{stringing} collector, which uses one for you: