Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
history
You can edit this page using
wiki syntax
for markup.
Article contents:
== nutils [[toc:]] === Description {{nutils}} is an eclectic collection of easy-to-use and versatile utilities. Unquestionably the basic ideas underlying these utilities have been expressed in Scheme many times over the decades, but nonetheless the versions found here offer a new twist or enhancements to old ideas. It's hoped these variations provide convenience and efficiency, thus make Scheme programming just a bit more productive and enjoyable. === Author Jules Altfas === Repository [[https://codeberg.org/jrapdx/nutils]] === Dependencies * [[srfi-1]] * [[srfi-13]] * [[srfi-14]] === nutils api ==== let-kw {{let-kw}} is somewhat like {{let-optionals}} and similar (chicken base), but with additional features that make it more useful. <syntax>(let-kw REST ((KEYWORD/SYMBOL/STRING VARIABLE DEFAULT) ...) BODY ...) * in - REST - a list, generally a dotted rest-list, containing arguments to parse * in - KEYWORD/SYMBOL/STRING - the argument/value that identifies the intended input * in - VARIABLE - an identifier naming the generated variable * in - DEFAULT - value assigned to the variable when K/S/S not an input. * in - BODY - like body of a {{let}}. Created variables are visible throughout body * returns: value is result of last expression in body. (import scheme.base nutils) (define (myproc . rest) (let-kw rest ((k0: k0 "cat")) (printnl "Animal:" k0))) ;; (myproc k0: "dog") => Animal: dog ;; (myproc) => Animal: cat As noted, the "key" can be almost any scheme object: (import ...) (define (proc2 . rest) (let-kw rest (('-animal k0 "cat")) (printnl "Animal:" k0))) ;; (proc2 '-animal "dog") => Animal: dog {{let-kw}} has another capability: it can be invoked with only a variable and default value. In this case it behaves like {{let-optional}}: (import ...) (define (proc-opt . rest) (let-kw rest ((k0 "cat")) (printnl "Animal:" k0))) ;; (proc-opt "dog") => Animal: dog ;; (proc-opt) => Animal: cat Where it differs from {{let-optional}} is that both keyed and unkeyed inputs can be used together with interesting results: (import ...) (define (two-in-one . rest) (let-kw rest ((keyed: keyed 'big-value)) (let-kw rest ((xxx 0) (aaa 101)) (printnl keyed xxx aaa)))) (two-in-one keyed: 'big-money 2578) ;; => big-money 2578 101 (two-in-one 2578 keyed: 'big-money) ;; => big-money 2578 101 (two-in-one 2578) ;; => big-value 2578 101 (two-in-one) ;; big-value 0 101 (two-in-one tiger: 'home) => big-value tiger: home Keyed arguments are removed from rest list, when it's passed to the second {{let-kw}} it no longer contains the keyed member and following value. So then {{let-kw}} matches the args in rest. If there are fewer args in rest than variables in {{let-kw}}, default values are assigned to the variables. This works because keyed args can't interfere with the "ordinary" optionals. There's no "rest confusion" due to DSSSL not removing keyword args from the rest list. Note that {{let-kw}} can be used without keyed args at all. Pure optional {{let-kw}} works like optionals/let-optionals are expected to. When using keyed and unkeyed binidng lists, the keyed lists must come first as illustrated above. Also keyed and unkeyed should not be mixed in a bindings list. ==== foreach and foreach* These are variations on {{for-each}} and {{map}} with similar basic semantics. However, {{foreach}} and {{foreach*}} invert, simplify and extend the syntax to allow accessing multiple elements of input lists at the same time. {{foreach}} and {{foreach*}} have identical syntax except {{foreach*}} returns a list while {{foreach}} does not. In this document {{foreach/*}} refers to information that applies to both. <syntax>(foreach/* ((VAR LIST) (VAR2 LIST2) ...) body ...)</syntax> * in - LIST ... - basic syntax. VAR, VAR2, etc., are variables visible in BODY. Each VAR holds one element of the respective list. * returns: {{foreach}} - no value, {{foreach*}} list of VAR, VAR2, etc., as determined by BODY expressions. If BODY result is the symbol {{_##_}} an element is not added to output list. <syntax>(foreach/* (((A B ...) LIST) ((C D ...) LIST2) ...) body ...)</syntax> * in - LIST ... - variables A B ..., C D ... hold one element of the respective list. IOW any number of elements can be extracted from lists at one time. The only restriction is that the same number of elements is taken from each list. Processing stops when shortest list runs out of elements. In {{foreach*}}, the result of BODY expressions is appended to output list, except if that value is the symbol {{_##_}}. * returns: as above. (import scheme.base nutils) (define op (open-output-file "demo1.txt")) (define row-vals '(123 282 366 471 543 612 798 882 936)) (foreach ((v row-vals)) (printnl v "divided by 3 is" (/ v 3) prn: op)) (close-output-port op) ;; demo1.txt contains rows: ;; 123 divided by 3 is 41 ;; 282 divided by 3 is 94 ;; ... (import scheme.base nutils srfi-1) (define critrs '("lion" "tiger" "elephant" "gorilla" "dingo" "hippo")) (let ((res (foreach* (((a b c) (iota 6)) ((x y z) critrs)) (let ((f0 (cons (combine/sym 'S #\- a) x)) (f1 (cons (combine/sym 'T #\- b) y)) (f2 (cons (combine/sym 'U #\- c) z))) (list f0 f1 f2))))) res) ;; res => (((S-0 . "lion") (T-1 . "tiger") (U-2 . "elephant")) ;; ((S-3 . "gorilla") (T-4 . "dingo") (U-5 . "hippo"))) ==== combine {{combine}} is similar to {{conc}} in the {{chicken.string}} module. That is, {{combine}} takes a number of objects, joining them into a string. What makes {{combine}} different is its 6 variations. <procedure>combine OBJ ...</procedure> * in - OBJ ... - any number of items: strings, symbols, characters, numbers, etc., * returns: all items as a single string without spaces between items. (combine 'q 245 "cat") => "q245cat" <procedure>combine/w OBJ ...</procedure> * in - OBJ ... - items to combine * returns: string with all items, preserves Scheme external representation. (combine/w 'q 245 "cat") => "q245\"cat\"" <procedure>combine/sym OBJ ...</procedure> * in - OBJ ... - items to combine * returns: symbol with all items combined without spaces between. (combine/sym 'q 245 "cat") => 'q245cat <procedure>combinesp OBJ ...</procedure> * in - OBJ ... - items to combine * returns: string containing all items with a space between items. (combinesp 'q 245 "cat") => "q 245 cat" <procedure>combinesp/w OBJ ...</procedure> * in - OBJ ... - items to combine * returns: string with all items and space between. Preserves Scheme external representation. (combinesp/w 'q 245 "cat") => "q 245 \"cat\"" <procedure>combinesp/sym OBJ ...</procedure> * in - OBJ ... - items to combine * returns: symbol with all items, space between. (combinesp/sym 'q 245 "cat") => |q 245 cat| ==== printnl and variants nutils provides procedures for terminal output with extended capabilites vs. the facilities of R7RS or chicken.io's {{print}} and {{print*}}. <procedure>printnl ITEM ... prn: <output-port></procedure> * in - ITEM - virtually any scheme object and any number of ITEMs. These are printed to the terminal via {{display}} with a space between ITEMs and newline after the last ITEM is printed. (printnl = print + nl, or newline.) * in - prn: <output-port> - by default, {{printnl}} (and all other print/write... procedures) print to {{current-output-port}}. However, the prn: keyword option directs printing to any open output port. * returns: no value. (import scheme.base nutils) (printnl 'test "my-output" 'isn\'t "bad at all!") => test my-output isn't bad at all! (define op (open-output-file "file1.txt")) ;; print to a file: (printnl "Here are test results:" prn: op) (printnl ... prn: op) ... (close-output-port op) <procedure>print0 ITEM ... prn: <output-port></procedure> * in - ITEM - as above. {{print0}} doesn't insert a space between ITEMs nor output {{newlinw}} after the last ITEM. * in - prn: <output-port> * returns: no value. <procedure>print0nl ITEM ...</procedure> * in - ITEM - same as {{printnl}}. Except {{print0nl}} does NOT insert a space between items, but does print {{newline}} after the last ITEM. * in - prn: <output-port> * returns: no value. <procedure>printsp ITEM ...</procedure> * in - ITEM - as above. Inserts a space between items and after last item. {{newline}} is not printed after the last item. Useful for printing a list of items one or a few at a time. * in - prn: <output-port> * returns: no value. <procedure>printsp0 ITEM ...</procedure> * in - ITEM - as above. Like {{printsp}} except does NOT leave a space after the last ITEM is printed. * in - prn: <output-port> * returns: no value. Each of the {{print...}} procedures has an analogous {{write...}} procedure. The difference between the series is that the latter uses {{write}} rather than {{display}}. Syntax of corresponding procedures is identical. <procedure>writenl ITEM ...</procedure> * in - ITEM ... * returns: no value <procedure>write0 ITEM ...</procedure> * in - ITEM ... * returns: no value <procedure>write0nl ITEM ...</procedure> * in - ITEM ... * returns: no value <procedure>writesp ITEM ...</procedure> * in - ITEM ... * returns: no value <procedure>writesp0 ITEM ...</procedure> * in - ITEM ... * returns: no value ===== Using the {{prn3port}} parameter While the keyword argument {{prn: ...}} allows printing to a port, it can be tedious and error-prone when many invocations of {{print...}} are necessary, for example, writing a complex document to a file. In such cases, using the {{prn3port}} parameter via {{parameterize}} makes a non-standard output port the default. Within the {{parameterize}} body, it's then unnecessary to use {{prn: ...}}. (import scheme.base nutils) (define op (open-output-file "complex-report-21.04.txt")) (parameterize ((prn3port op)) (printnl "Title:" article-title) (printnl "Author:" article-author) (printnl "Executive Summary:\n") (printnl summary-text) ... (printnl "References:" reference-list)) (close-output-port op) ==== string, symbol, number conversions Conversion among strings, symbols and numbers is a common operation. These "shortcuts" make it simple to accomplish and reduce visual clutter: <procedure>sym/str ITEM</procedure> * in - ITEM may be a string, symbol, number, or keyword * returns: a string, or #f if ITEM is none of the above. (sym/str 'kettle) => "kettle" (sym/str skillet:) => "skillet" (sym/str 1762) => "1762" (sym/str "String") => "String" <procedure>str/sym ITEM</procedure> * in - ITEM - a string, symbol, number, or keyword * returns: a symbol, or #f if ITEM is not one of the above. (str/sym "kettle") => 'kettle (str/sym 1762) => |1762| <procedure>sym/str->num ITEM NOTRIM?</procedure> * in - ITEM is a numeric string, symbol or keyword * in - NOTRIM: default is #f ("standard" trim "\n\t " is automatic). ** If NOTRIM is not #f, the default trim is not invoked. (By default, space, tab and newline chars at both ends of ITEM are removed). ** If other characters need to be trimmed, use {{(strim "string" "characters")}} to submit a string to {{sym/str->num}}. ** {{obj->num}} is an alias for {{sym/str->num}}. * returns: a number or #f if ITEM does not convert to a number. (sym/str->num "873") => 873 (sym/str->num '|873|) => 873 (sym/str->num #x873) => 2163 (sym/str->num "2163 ") => 2163 (sym/str->num " 2163 " #t) => #f (sym/str->num (strim "\t\t 2163--- \n" "-")) => 2163 (sym/str->num (strim '|\x9;\x9; 2163--- \xa;| "-")) => 2163 <syntax>strls->symls LIST</syntax> * in - LIST of strings * returns: list of symbols <syntax>symls->strls LIST</syntax> * in - LIST of symbols, numbers, keywords * returns: list of strings <syntax>strls->numls LIST</syntax> * in - LIST of symbols, strings, numbers * returns: list of numbers Note: list conversions will insert #f when an element of the input list can't be converted to the output list type. ==== alist replace/append/delete, and alist query The alist is commonly used as a database in Scheme programs for small data sets, such as configuration options or temporary data. Frequently modifying the data is called for, with 3 or 4 operations being used: * Adding a pair to the alist. * Deleting a pair from the alist. * Replacing the data (cdr) of a pair. * Appending data to the existing data of a pair/list. The semantics of these operations can be surprising. Using {{set-cdr!}} on a pair extracted from an alist by {{assoc}} is visible in the alist. Preserving the original alist requires a "deep copy" of the original to modify, leaving the original unaltered. (define lista '((a . bbb) (c . cdefg) (d . "dogs,dogs,dogs"))) (define pr (assoc 'c lista)) ;; pr => (c . cdefg) (set-cdr! pr 'qwert) ;; pr => (c . qwert) lista ;; => ((a . bbb) (c . qwert) (d . "dogs,dogs,dogs")) {{alist/r_}}, {{alist/a_}} and {{alist/d_}} provide adding, replacing, appending and removing alist data. The "+" variants return a new alist, the "!" change the input alist. <procedure>alist-dup ALIST</procedure> * in - ALIST - the alist to duplicate * returns: new alist {{equal?}} (but not {{eq?}}) to ALIST. <syntax>alist/r+ K V ALIST</syntax> * in - K - the ''key'', car, of a pair in the alist. * in - V - the ''value'', cdr, of the pair. * in - ALIST ** {{alist/r+}} replaces the value (cdr) of a pair with a new value, V. If the key, K, doesn't exist in any pair in the ALIST, a new K-V pair is added to the alist. In either case, a fresh ALIST is returned. * returns: altered ALIST, original ALIST is unchanged. <syntax>alist/r! K V ALIST</syntax> * Destructive variant of {{alist/r+}}, sets ALIST to new ALIST. * returns: modified input alist. <syntax>alist/a+ K V ALIST</syntax> * in - K, V as above. * in - ALIST ** ALIST is modified, V is ''appended to'' the pair's current value. If the key is not found in the ALIST, a new K-V pair is added to the ALIST. * returns: the amended ALIST without changing the original. <syntax>alist/a! K V ALIST</syntax> * Destructive variant of {{alist/a+}}, setting ALIST to modified result. * returns: altered original alist. <procedure>alist/d+ K ALIST</procedure> * in - K - key of pair to remove from ALIST. ** if K isn't found in ALIST, unchanged duplicate ALIST is returned. * returns: altered alist, original is unmodified. <syntax>alist/d! K ALIST</syntax> * in - K - key of pair to remove from ALIST. ** if K isn't found in ALIST, the unchanged ALIST is returned. * returns: modified alist. ==== alist query <procedure>k->v KEY ALIST</procedure> * in - KEY - Performs case-sensitive search of ALIST for KEY, or car of pair. * in - ALIST - ALIST to search. * returns: value associated with KEY, or #f if key not found. <procedure>k->vci KEY ALIST</procedure> * in - KEY - Performs case-insensitive search of ALIST for KEY, or car of pair. * in - ALIST - ALIST to search. * returns: value associated with KEY, or #f if not found. ==== list-recv, pair-recv {{list-recv}} is analogous to {{receive}}, except {{list-recv}} destructures a list instead of values. Most useful with procedures returning lists with small number of elements. <syntax>list-recv (VAR1 ...) LIST BODY</syntax> * in - VAR1 ... - number of variables must match length of LIST * in - LIST - each element is assigned to variable in VARs. * in - BODY - VAR1 ... are variables visible throughout BODY. * returns: result of body expressions. (import nutils) (define (myproc x y) (let ((a (* 101 x)) (b (* 202 y))) (list a b))) (list-recv (var-a var-b) (myproc 20 40) (printnl 'var-a 'is var-a) (printnl "var-b is" var-b)) ;; => var-a is 2020 ;; => var-b is 8080 <syntax>pair-recv (K V) PAIR BODY</syntax> * in - K,V - K is assigned the key (car PAIR), V is value (cdr PAIR) * in - PAIR - a key/value list, dotted pair, or procedure call returning a pair. * in - BODY - variables K and V visible throughout BODY. * returns: result of body expressions. ==== string indexing <procedure>str-ndx-ls STR CHR STARTAT FROMRIGHT?</procedure> * in - STR - search fro indexes of this string * in - CHR - character to search for * in - STARTAT - (optional) index to start from, default is 0. ** Note that with startat > 0, output indexes will not be different than 0 start. Applications can decrement the results by startat value if that is the desired semantics. * in - FROMRIGHT? - (optional) if true, STARTAT and resulting indexes will count from end of string towards the beginning. * returns: list of indexes that match input criteria or null list if none found. (import nutils) (define str "abc dws qthcd po9inmhy ecvhn") (str-ndx-ls str #\c) ;; => (2 11 24) (str-ndx-ls str #\c 0 #t) ;; (3 16 25) (str-ndx-ls str #\c 5 #t) ;; (16 25) (foreach* ((n '(16 25))) (- n 5)) ;; => (11 20) <procedure>find-string-ndx STR CHR RT?</procedure> * in - STR - search for indexes of this string * in - CHR - character to search for * in - RT? - search from right (#t) or left (#f) * returns: first index that satisfies criteria, or '() if none. <procedure>str-right-ndx STR CH</procedure> * in - STR - search for indexes of this string * in - CH - character to search for * returns: first rightmost index, or '() if none found. <procedure>str-left-ndx STR CH</procedure> * in - STR - search for indexes of this string * in - CH - character to search for * returns: first leftmost index, or '() if none found. <procedure>strip-extension FILENAME</procedure> * in - FILENAME - a string * returns: FILENAME without extension, or if no extension, input FILENAME. <procedure>get-extension FILENAME</procedure> * in - FILENAME - a string * returns: extension, or if no extension, the empty string (""). ==== qsort {{qsort}} is a simple, lightweight implementation of the qsort algorithm, useful for sorting short-moderate length lists encountered in many programs. <procedure>qsort LIST #!optional !< !>=</procedure> * in - LIST - the list to sort * in - !< !>= - (optional) comparison procedures: ** Defaults: !< is {{<}}, !>= is {{>=}}. User-supplied compare procedures must accept data type of LIST. * returns: sorted LIST. <procedure>qsort-str LIST</procedure> * in - LIST - unsorted list of strings * returns: sorted LIST. ==== sfmt {{sfmt}} formats data in a manner analogous to "printf", the unix command line utility. {{sfmt}} takes a format string with familiar "%..." specifiers as used by "printf". {{sfmt}} is intended for simple use cases, more complex formatting is undoubtedly better served by extensions such as [[/eggref/6/fmt|fmt]], [[/eggref/6/format|format]], etc. <procedure>sfmt FMT-STRING ITEMS</procedure> * in - FMT-STRING - a string containing "%" specifiers as documented for the unix "printf" command. ** Specifiers recognized by sfmt include {{d}}, {{f}}, {{s}}, {{u}}, {{c}}, {{x}}, {{X}} and the qualifier {{ll}}. Precision, +/- components are also recognized. A "%" character can be inserted with "%%". ** {{sfmt}} checks if ITEMS match the types of corresponding specifiers in FMT-STRING. If a type mismatch is evident, an error message is printed to the terminal and an empty string returned. * in - ITEMS - data to be formatted. Must match the types of specifiers. Number of specifiers in the format string must equal the number of ITEMS. * returns: formatted string or "" on error. (import scheme.base nutils) ... (define op (open-output-file "Report-doc.txt")) ;; print headers (printnl (sfmt "%-18s %-14s %-15s %-13s %s" "Title" "Author" "Pub. Date" "Dept." "Cost") prn: op ) (printnl (sfmt "%-18s %-14s %-15s %-13s %s" "------------" "-------" "----------" "------" "-------") prn: op) (printnl (sfmt "%-18s %-14s %-15s %-8d %12.2f" "Report No. 122" "Sam Jones" "2024-08-15" 1405 1223.57) prn: op) (close-output-port op) ;; file has this content: Title Author Pub. Date Dept. Cost ------------ ------- ---------- ------ ------- Report No. 122 Sam Jones 2024-08-15 1405 1223.57 ==== misc <syntax>while TEST BODY</syntax> * in - TEST - a boolean expression * in - BODY - BODY is evaluated until TEST is #f. The procedure {{break ...}} can be used to terminate the loop at any point. * returns: value of BODY's last expression, or value input to break if it's used. <procedure>strim STR OPTIONS</procedure> * in - STR - the string to trim * in - OPTIONS: ** Chars to trim are contained in strings/objects. The default set is " \t\n". ** Keyword arguments include: L: <object>, R: <object> and D: <object> ** If L: <object> is given and not false, object's characters are added to default set and applied only to left side of STR. ** If R: <object> is given and not false, characters are added to default set and used to remove characters only from right side of STR. ** If D: <object> is supplied, its characters ''replace'' the default set affecting both ends of STR. ** If an optional argument is given, that is, a string/object without a keyword, its characters are added to the default set and used for character removal from both ends of STR. ** Non-string objects (symbols, numbers, lists, etc.) must be convertible to strings. * returns: trimmed string (import nutils) (define str "\t\t---... A string. ---+++\n\n") (strim str "-.+") ;; => "A string" (note 'dot' removed from right end) (strim str "-+" L: ".") ;; => "A string." (L: -> rm only from left end) (strim str "-+" L: '(\.)) ;; => "A string." (strim str "-+" L: '|.|) ;; => "A string." <procedure>sym/str-split SYM-STR SPLITTER: KEEP-NULLS: KEEP-SPLITTER:</procedure> * in - SYM-STR - the string or symbol to split. A symbol is converted to string. * in - SPLITTER: - keyword, optional. The substring where the string will be split. Default is " ". * in - KEEP-NULLS: - keyword, optional. On splitting, null strings may be formed and are ignored by default. Using KEEP-NULLS: #t will retain the null strings in the result. * in - KEEP-SPLITTER: - keyword, optional. Ordinarily the "splitter" string is not part of the result. Using KEEP-SPLITTER: #t will keep the splitter string in the result. * returns: list of strings divided by the SPLITTER: string or #f if splitting is not possible. <procedure>split-str-at-char STR SCHARS KEEP</procedure> * in - STR - string to split * in - SCHARS - string containing characters where string string should be split. * in - KEEP - boolean, default #f. When #t, SCHARS are retained in result. * returns: list of strings (import nutils) (define str "xyzAbcdeabcdefghijAbcde") (split-str-at-char str "Ad") ;; => ("xyz" "bc" "eabc" "efghij" "bc" "e") (split-str-at-char str "Ad" #t) ;; => ("xyz" "Abc" "deabc" "defghij" "Abc" "de") <procedure>type-of ITEM</procedure> * in - ITEM - a scheme object * returns: a symbol describing the type of the item, or 'unknown if the item's type can't be determined. {{type-of}} can identify most scheme objects. Number-vectors above u8 are identified as "number-vector". (import nutils chicken.number-vector) (type-of 'cat) ; => 'symbol (type-of 22.22) ; => 'float (type-of 22) ; => 'exact-int (type-of #u8(0 0 0 0) ;; => 'bytevector (type-of #u32(0 0 0 0)) ;; => 'number-vector <procedure>deg2rad DEG</procedure> * in - DEG - angle (degrees) * returns: angle as radians <procedure>rad2deg RAD</procedure> * in - RAD - angle (radians) * returns: angle as degrees <syntax>mkconst VAR VAL</syntax> * in - VAR - the "variable", name of generated procedure * in - VAL - constant value returned from generated procedure * returns toplevel procedural constant (procedure of no arguments, returning VAL) <procedure>PI</procedure> * generated by {{mkconst}} * returns: value of PI, 3.14159265359 (import nutils) (print0nl "pi is approx " (PI)) ;; => pi is approx 3.14159265359 === LICENSE BSD-2 === Versions 0.2.8.1 - Completed fixes of 0.2.8 0.2.8 - Error fixes, added alist/d* 0.2.6 - Fixed tests 0.2.5 - Initial public release
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you multiply 7 by 5?