From 568e2ebcef5ce66454933d4e4bced7407b76f8a5 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 21:57:35 +0300 Subject: [PATCH 1/8] Updated the stdlib.go, to load *.lisp Rather than just loading two files we now load all lisp files in this directory. --- stdlib/stdlib.go | 49 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 5b4d459..0b193a1 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -1,19 +1,50 @@ -// Package stdlib contains a simple/small standard-library, which -// is written in lisp itself. +// Package stdlib contains a simple & small standard-library, +// written in lisp itself. +// +// By default our standard library is loaded prior to the +// execution of any user-supplied code. package stdlib import ( - _ "embed" // embedded-resource magic + "embed" // embedded-resource magic + "fmt" + "path/filepath" ) -//go:embed stdlib.lisp -var stdlib string - -//go:embed mal.lisp -var mal string +//go:embed *.lisp +var stdlib embed.FS // Contents returns the embedded contents of our Lisp standard-library. +// +// We embed "*.lisp" when we build our binary. func Contents() []byte { - return []byte(stdlib + "\n" + mal + "\n") + // Result + result := []byte{} + + // Read the list of entries + entries, err := stdlib.ReadDir(".") + if err != nil { + fmt.Printf("Failed to read embedded resources; fatal error\n") + return result + } + + // For each entry + for _, entry := range entries { + + // Get the filename + fp := filepath.Join(".", entry.Name()) + + // Read the content + data, err := stdlib.ReadFile(fp) + if err != nil { + fmt.Printf("Failed to read embedded resource - %s - fatal error\n", fp) + return result + } + + // Append to our result + result = append(result, data...) + } + + return result } From bfcbcfb2cc223e28fd956e9a4a6911896c874266 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:02:01 +0300 Subject: [PATCH 2/8] Remove the stdlib/*.lisp. Move lisp into a subdirectory. --- stdlib/mal.lisp | 182 ---------------- stdlib/stdlib.go | 50 ++++- stdlib/stdlib.lisp | 508 --------------------------------------------- 3 files changed, 41 insertions(+), 699 deletions(-) delete mode 100644 stdlib/mal.lisp delete mode 100644 stdlib/stdlib.lisp diff --git a/stdlib/mal.lisp b/stdlib/mal.lisp deleted file mode 100644 index 11b2362..0000000 --- a/stdlib/mal.lisp +++ /dev/null @@ -1,182 +0,0 @@ -;;; mal.lisp - Compatability with MAL, implemented in lisp. - -;; This is essentially prepended to any program the user tries to run, -;; and implements functions that are expected by any MAL implementation. -;; -;; More general functions can be found in stdlib.lisp. -;; - - -;; Traditionally we use `car` and `cdr` for accessing the first and rest -;; elements of a list. For readability it might be nice to vary that -(alias first car) -(alias rest cdr) - -;; Some simple tests of numbers -(set! zero? (fn* (n) - "Return true if the number supplied as the first argument to this function is equal to zero." - (= n 0))) - -(set! one? (fn* (n) - "Return true if the number supplied as the argument to this function is equal to one." - (= n 1))) - -(set! even? (fn* (n) - "Return true if the number supplied as the argument to this function is even." - (zero? (% n 2)))) - -(set! odd? (fn* (n) - "Return true if the number supplied as the argument to this function is odd." - (! (even? n)))) - -;; is the given argument "true", or "false"? -(def! true? (fn* (arg) - "Return true if the argument supplied to this function is true." - (if (eq #t arg) true false))) - -(def! false? (fn* (arg) - "Return true if the argument supplied to this function is false." - (if (eq #f arg) true false))) - - -;; Run an arbitrary series of statements, if the given condition is true. -;; -;; This is the more general/useful version of the "if2" macro, which -;; we demonstrate in mtest.lisp. -;; -;; Sample usage: -;; -;; (when (= 1 1) (print "OK") (print "Still OK") (print "final statement")) -;; -(defmacro! when (fn* (pred &rest) - "when is a macro which runs the specified body, providing the specified predicate is true. - -It is similar to an if-statement, however there is no provision for an 'else' clause, and the body specified may contain more than once expression to be evaluated." - `(if ~pred (do ~@rest)))) - -;; -;; If the specified predicate is true, then run the body. -;; -;; NOTE: This recurses, so it will eventually explode the stack. -;; -(defmacro! while (fn* (condition &body) - "while is a macro which repeatedly runs the specified body, while the condition returns a true-result" - (let* (inner-sym (gensym)) - `(let* (~inner-sym (fn* () - (if ~condition - (do - ~@body - (~inner-sym))))) - (~inner-sym))))) - - -;; -;; cond is a useful thing to have. -;; -(defmacro! cond (fn* (&xs) - "cond is a macro which accepts a list of conditions and results, and returns the value of the first matching condition. It is similar in functionality to a C case-statement." - (if (> (length xs) 0) - (list 'if (first xs) - (if (> (length xs) 1) - (nth xs 1) - (error "An odd number of forms to (cond..)")) - (cons 'cond (rest (rest xs))))))) - -;; A useful helper to apply a given function to each element of a list. -(set! apply (fn* (lst:list fun:function) - "Call the specified function on every element in the given list. - -See-also: apply-pairs, apply-hash" - (if (nil? lst) - () - (do - (fun (car lst)) - (apply (cdr lst) fun))))) - -;; Apply, but walking the list in pairs. -(set! apply-pairs (fn* (lst:list fun:function) - "Calling the specified function with two items on the specified list. - -This is similar to apply, but apply apply invokes the callback with a single list-item, and here we apply in pairs. - -Note: The list-length must be even, and if not that will raise an error. - -See-also: apply apply-hash -Example: (apply-pairs (list 1 2 3 4) (lambda (a b) (print \"Called with %s %s\" a b))) -" - (if (! (nil? lst)) - (if (= (% (length lst) 2) 0) - (let* (a (car lst) - b (car (cdr lst))) - (fun a b) - (apply-pairs (cdr (cdr lst) ) fun)) - (error "The list passed to (apply-pairs..) should have an even length"))))) - -;; Return the length of the given list. -(set! length (fn* (arg) - "Return the length of the supplied list. See-also strlen." - (if (list? arg) - (do - (if (nil? arg) 0 - (inc (length (cdr arg))))) - 0 - ))) - -(alias count length) - -;; Find the Nth item of a list -(set! nth (fn* (lst:list i:number) - "Return the Nth item of the specified list. - -Note that offset starts from 0, rather than 1, for the first item." - (if (> i (length lst)) - (error "Out of bounds on list-length") - (if (= 0 i) - (car lst) - (nth (cdr lst) (- i 1)))))) - - -(set! map (fn* (lst:list fun:function) - "Return a list with the contents of evaluating the given function on every item of the supplied list. - -See-also: map-pairs" - (if (nil? lst) - () - (cons (fun (car lst)) (map (cdr lst) fun))))) - -(set! map-pairs (fn* (lst:list fun:function) - "Return a list with the contents of evaluating the given function on every pair of items in the supplied list. - -See-also: map" - (if (! (nil? lst)) - (if (= (% (length lst) 2) 0) - (let* (a (car lst) - b (car (cdr lst))) - (cons (fun a b) (map-pairs (cdr (cdr lst)) fun))) - (error "The list passed should have an even length")) - ()))) - - -;; This is required for our quote/quasiquote/unquote/splice-unquote handling -;; -;; Testing is hard, but -;; -;; (define lst (quote (b c))) ; b c -;; (print (quasiquote (a lst d))) ; (a lst d) -;; (print (quasiquote (a (unquote lst) d))) ; (a (b c) d) -;; (print (quasiquote (a (splice-unquote lst) d))) ; (a b c d) -;; -(set! concat (fn* (seq1 seq2) - "Join two lists" - (if (nil? seq1) - seq2 - (cons (car seq1) (concat (cdr seq1) seq2))))) - - - -;; -;; Read a file -;; -(def! load-file (fn* (filename) - "Load and execute the contents of the supplied filename." - (eval (join (list "(do " (slurp filename) "\nnil)"))))) diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 0b193a1..14ec9d3 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -1,29 +1,47 @@ -// Package stdlib contains a simple & small standard-library, -// written in lisp itself. +// Package stdlib contains a simple/small standard-library, which is written in lisp itself. // -// By default our standard library is loaded prior to the -// execution of any user-supplied code. +// By default our standard library is loaded prior to the execution of any user-supplied +// code, however parts of it can be selectively ignored, or the whole thing. +// +// If the environmental varialbe "YAL_STDLIB_EXCLUDE_ALL" contains non-empty content then +// all of our standard-library is disabled. +// +// Otherwise if YAL_STDLIB_EXCLUDE is set to a non-empty string it will be assumed to be +// a comma-separated list of filename substrings to exclude. package stdlib import ( "embed" // embedded-resource magic "fmt" + "os" "path/filepath" + "strings" ) -//go:embed *.lisp +//go:embed stdlib/*.lisp var stdlib embed.FS // Contents returns the embedded contents of our Lisp standard-library. // -// We embed "*.lisp" when we build our binary. +// We embed "*.lisp" when we build our binary func Contents() []byte { // Result result := []byte{} + // We can allow disabling the stdlib. + if os.Getenv("YAL_STDLIB_EXCLUDE_ALL") != "" { + return result + } + + // We might exclude only one/two files + exclude := []string{} + if os.Getenv("YAL_STDLIB_EXCLUDE") != "" { + exclude = strings.Split(os.Getenv("YAL_STDLIB_EXCLUDE"), ",") + } + // Read the list of entries - entries, err := stdlib.ReadDir(".") + entries, err := stdlib.ReadDir("stdlib") if err != nil { fmt.Printf("Failed to read embedded resources; fatal error\n") return result @@ -33,12 +51,26 @@ func Contents() []byte { for _, entry := range entries { // Get the filename - fp := filepath.Join(".", entry.Name()) + fp := filepath.Join("stdlib", entry.Name()) + + // Does this match an excluded value? + skip := false + + for _, tmp := range exclude { + if strings.Contains(fp, tmp) { + skip = true + } + } + + if skip { + fmt.Printf("Skipping %s\n", fp) + continue + } // Read the content data, err := stdlib.ReadFile(fp) if err != nil { - fmt.Printf("Failed to read embedded resource - %s - fatal error\n", fp) + fmt.Printf("Failed to read embedded resource - %s - fatal error %s\n", fp, err) return result } diff --git a/stdlib/stdlib.lisp b/stdlib/stdlib.lisp deleted file mode 100644 index 228d4e6..0000000 --- a/stdlib/stdlib.lisp +++ /dev/null @@ -1,508 +0,0 @@ -;;; stdlib.lisp - Standard library as implemented in lisp. - -;; This is essentially prepended to any program the user tries to run, -;; and implements behaviour which is useful for users. -;; -;; For compatability with MAL mal.lisp is also considered part of our -;; standard-library. -;; - - - -;; There is a built in `type` function which returns the type of an object. -;; -;; Use this to define some simple methods to test argument-types -(set! boolean? (fn* (x) - "Returns true if the argument specified is a boolean value." - (eq (type x) "boolean"))) - -(set! error? (fn* (x) - "Returns true if the argument specified is an error-value." - (eq (type x) "error"))) - -(set! function? (fn* (x) "Returns true if the argument specified is a function, either a built-in function, or a user-written one." - (or - (list - (eq (type x) "procedure(lisp)") - (eq (type x) "procedure(golang)"))))) - -(set! hash? (fn* (x) - "Returns true if the argument specified is a hash." - (eq (type x) "hash"))) - -(set! macro? (fn* (x) - "Returns true if the argument specified is a macro." - (eq (type x) "macro"))) - -(set! list? (fn* (x) - "Returns true if the argument specified is a list." - (eq (type x) "list"))) - -(set! number? (fn* (x) - "Returns true if the argument specified is a number." - (eq (type x) "number"))) - -(set! string? (fn* (x) - "Returns true if the argument specified is a string." - (eq (type x) "string"))) - -(set! symbol? (fn* (x) - "Returns true if the argument specified is a symbol." - (eq (type x) "symbol"))) - -;; We've defined "<" in golang, we can now implement the missing -;; functions in terms of that: -;; -;; > -;; <= -;; >= -;; -(set! > (fn* (a b) - "Return true if a is greater than b." - (< b a))) - -(set! >= (fn* (a b) - "Return true if a is greater than, or equal to b." - (! (< a b)))) -(set! <= (fn* (a b) - "Return true if a is less than, or equal to, b." - (! (> a b)))) - -;; We have a built in function "date" to return the current date -;; as a list (DD MM YYYY). We also ahve a builtin function (time) -;; to return the time as a list (HH MM SS). -;; -;; Here we create some helper functions for retrieving the various -;; parts of the date/time, as well as some aliases for ease of typing. -(set! date:day (fn* () - "Return the day of the current month, as an integer." - (nth (date) 1))) - -(set! date:month (fn* () - "Return the number of the current month, as an integer." - (nth (date) 2))) - - -(set! date:weekday (fn* () - "Return a string containing the current day of the week." - (nth (date) 0))) - -(set! date:year (fn* () - "Return the current year, as an integer." - (nth (date) 3))) - -;; define legacy aliases -(alias day date:day) -(alias month date:month) -(alias weekday date:weekday) -(alias year date:year) - -(set! time:hour (fn* () - "Return the current hour, as an integer." - (nth (time) 0))) - -(set! time:minute (fn* () - "Return the current minute, as an integer." - (nth (time) 1))) - -(set! time:second (fn* () - "Return the current seconds, as an integer." - (nth (time) 2))) - -;; define legacy aliases -(alias hour time:hour) -(alias minute time:minute) -(alias second time:second) - -(set! zero-pad-single-number (fn* (num) - "Prefix the given number with zero, if the number is less than ten. - -This is designed to pad the hours, minutes, and seconds in (hms)." - (if (< num 10) - (sprintf "0%s" num) - num))) - -(set! time:hms (fn* () - "Return the current time, formatted as 'HH:MM:SS', as a string." - (sprintf "%s:%s:%s" - (zero-pad-single-number (hour)) - (zero-pad-single-number (minute)) - (zero-pad-single-number (second))))) -(alias hms time:hms) - - -;; -;; This is a bit sneaky. NOTE there is no short-circuiting here. -;; -;; Given a list use `filter` to return those items which are "true". -;; -;: If the length of the input list, and the length of the filtered list -;; are the same then EVERY element was true so our AND result is true. -;; -(set! and (fn* (xs:list) - "Return true if every item in the specified list is true." - (let* (res nil) - (set! res (filter xs (lambda (x) (if x true false)))) - (if (= (length res) (length xs)) - true - false)))) - -;; -;; This is also a bit sneaky. NOTE there is no short-circuiting here. -;; -;; Given a list use `filter` to return those items which are "true". -;; -;; If the output list has at least one element that was true then the -;; OR result is true. -;; -(set! or (fn* (xs:list) - "Return true if any value in the specified list contains a true value." - (let* (res nil) - (set! res (filter xs (lambda (x) (if x true false)))) - (if (> (length res) 0) - true - false)))) - - -;; every is useful and almost a logical operation -(set! every (fn* (xs:list fun:function) - "Return true if applying every element of the list through the specified function resulted in a true result." - (let* (res (map xs fun)) - (if (and res) - true - false)))) - - -;; Useful for creating a list of numbers -(set! repeated (fn* (n:number x) - "Return a list of length n whose elements are all x." - (when (pos? n) - (cons x (repeated (dec n) x))))) - -;; inc/dec are useful primitives to have -(set! inc (fn* (n:number) - "inc will add one to the supplied value, and return the result." - (+ n 1))) - -(set! dec (fn* (n:number) - "dec will subtract one from the supplied value, and return the result." - (- n 1))) - -;; Not is useful -(set! ! (fn* (x) - "Return the inverse of the given boolean value." - (if x #f #t))) - -(alias not !) - -;; Square root -(set! sqrt (fn* (x:number) - "Calculate the square root of the given value." - (# x 0.5))) - -;; Return the last element of a list -;; -;; NOTE: This could be written more simply, for example: -;; -;; (set! last (fn* (lst:list) "Return the last element of the given list" (car (reverse lst)))) -;; -(set! last (fn* (lst:list) - "last returns the last item in the specified list, it is the inverse of (butlast) and the logical opposite of (car)." - (let* (c (cdr lst)) - (if (! (nil? c)) - (last c) - (car lst))))) - -;; Setup a simple function to run a loop N times -;; -(set! repeat (fn* (n body) - "Execute the supplied body of code N times." - (if (> n 0) - (do - (body n) - (repeat (- n 1) body))))) - -;; A helper to apply a function to each key/value pair of a hash -(set! apply-hash (fn* (hs:hash fun:function) - "Call the given function to every key in the specified hash. - -See-also: apply, apply-pairs" - (let* (lst (keys hs)) - (apply lst (lambda (x) (fun x (get hs x))))))) - - -;; Count the length of a string -(set! strlen (fn* (str:string) - "Calculate and return the length of the supplied string." - (length (split str "")))) - - -;; More mathematical functions relating to negative numbers. -(set! neg (fn* (n:number) - "Negate the supplied number, and return it." - (- 0 n))) - -(set! neg? (fn* (n:number) - "Return true if the supplied number is negative." - (< n 0))) - -(set! pos? (fn* (n:number) - "Return true if the supplied number is positive." - (> n 0))) - -(set! abs (fn* (n:number) - "Return the absolute value of the supplied number." - (if (neg? n) (neg n) n))) - -(set! sign (fn* (n:number) - "Return 1 if the specified number is positive, and -1 if it is negative." - (if (neg? n) (neg 1) 1))) - - -;; Create ranges of numbers in a list -(set! range (fn* (start:number end:number step:number) - "Create a list of numbers between the start and end bounds, inclusive, incrementing by the given offset each time." - (if (<= start end) - (cons start (range (+ start step) end step)) - ()))) - -;; Create sequences from 0/1 to N -(set! seq (fn* (n:number) - "Create, and return, list of number ranging from 0-N, inclusive." - (range 0 n 1))) -(set! nat (fn* (n:number) - "Create, and return, a list of numbers ranging from 1-N, inclusive." - (range 1 n 1))) - - -;; Remove items from a list where the predicate function is not T -(set! filter (fn* (xs:list f:function) - "Remove any items from the specified list, if the result of calling the provided function on that item is not true." - (if (nil? xs) - () - (if (f (car xs)) - (cons (car xs)(filter (cdr xs) f)) - (filter (cdr xs) f))))) - - - - -;; reduce function -(set! reduce (fn* (xs f acc) - "This is our reduce function, which uses a list, a function, and the accumulator." - (if (nil? xs) - acc - (reduce (cdr xs) f (f acc (car xs)))))) - -;; Now define min/max using reduce -(set! min (fn* (xs:list) - "Return the smallest integer from the list of numbers supplied." - (if (nil? xs) - () - (reduce xs - (lambda (a b) - (if (< a b) a b)) - (car xs))))) - -(set! max (fn* (xs:list) - "Return the maximum integer from the list of numbers supplied." - (if (nil? xs) - () - (reduce xs - (lambda (a b) - (if (< a b) b a)) - (car xs))))) - - -; O(n^2) behavior with linked lists -(set! append (fn* (xs:list el) - "Append the given element to the specified list." - (if (nil? xs) - (list el) - (cons (car xs) (append (cdr xs) el))))) - - -(set! reverse (fn* (x:list) - "Return a list containing all values in the supplied list, in reverse order." - (if (nil? x) - () - (append (reverse (cdr x)) (car x))))) - -;; -;; This is either gross or cool. -;; -;; Define a hash which has literal characters and their upper-case, and -;; lower-cased versions -;; -(set! upper-table { - a "A" - b "B" - c "C" - d "D" - e "E" - f "F" - g "G" - h "H" - i "I" - j "J" - k "K" - l "L" - m "M" - n "N" - o "O" - p "P" - q "Q" - r "R" - s "S" - t "T" - u "U" - v "V" - w "W" - x "X" - y "Y" - z "Z" - } ) - -(set! lower-table { - A "a" - B "b" - C "c" - D "d" - E "e" - F "f" - G "g" - H "h" - I "i" - J "j" - K "k" - L "l" - M "m" - N "n" - O "o" - P "p" - Q "q" - R "r" - S "s" - T "t" - U "u" - V "v" - W "w" - X "x" - Y "y" - Z "z" - } ) - - -;; Translate the elements of the string using the specified hash -(set! translate (fn* (x:string hsh:hash) - "Translate each character in the given string, via the means of the supplied lookup-table. - -This is used by both 'upper' and 'lower'." - (let* (chrs (split x "")) - (join (map chrs (lambda (x) - (if (get hsh x) - (get hsh x) - x))))))) - -;; Convert the given string to upper-case, via the lookup table. -(set! upper (fn* (x:string) - "Convert each character from the supplied string to upper-case, and return that string." - (translate x upper-table))) - -;; Convert the given string to upper-case, via the lookup table. -(set! lower (fn* (x:string) - "Convert each character from the supplied string to lower-case, and return that string." - (translate x lower-table))) - - -;; Get the first N items from a list. -(set! take (fn* (n l) - "Return the first N items from the specified list." - (cond (zero? n) nil - (nil? l) nil - true (cons (car l) (take (- n 1) (cdr l)))))) - -;; Remove the first N items from a list. -(set! drop (fn* (n l) - "Remove the first N items from the specified list." - (cond (zero? n) l - (nil? l) nil - true (drop (- n 1) (cdr l))))) - -;; Return everything but the last element. -(set! butlast (fn* (l) - "Return everything but the last element from the specified list." - - (take (dec (length l)) l))) - -;; Wrappers for our file functions -(set! file:stat:size (fn* (path) - "Return the size of the given file, return -1 on error." - (let* (info (file:stat path)) - (cond - (nil? info) -1 - true (nth info 1))))) - -(set! file:stat:uid (fn* (path) - "Return the UID of the given file owner, return '' on error." - (let* (info (file:stat path)) - (cond - (nil? info) "" - true (nth info 2))))) - - -(set! file:stat:gid (fn* (path) - "Return the GID of the given file owner, return '' on error." - (let* (info (file:stat path)) - (cond - (nil? info) "" - true (nth info 3))))) - -(set! file:stat:mode (fn* (path) - "Return the mode of the given file, return '' on error." - (let* (info (file:stat path)) - (cond - (nil? info) "" - true (nth info 4))))) - -(set! file:which (fn* (binary) - "Return the complete path to the specified binary, found via the users' PATH setting. - -If the binary does not exist in a directory located upon the PATH nil will be returned. - -NOTE: This is a non-portable function! - - 1. It assumes that the environmental variable PATH exists. - 2. It assumes $PATH can be split by ':' - 3. It assumes '/' works as a directory separator. -" - (let* (path (split (getenv "PATH") ":") - res (filter path (lambda (dir) (exists? (join (list dir "/" binary)))))) - (if res - (join (list (car res) "/" binary)))))) - - -;; Define a legacy alias -(alias slurp file:read) - - -;; Handy function to invoke a callback on files -(set! directory:walk (fn* (path:string fn:function) - "Invoke the specified callback on every file beneath the given path." - - (apply (directory:entries path) fn))) - - -;; Add some simple random functions, using our "random" primitive. -(set! random:char (fn* (&x) - "Return a random character by default from the set a-z. - -If an optional string is provided it will be used as a list of characters to choose from." - (let* (chars (split "abcdefghijklmnopqrstuvwxyz" "")) - (if (list? x) - (set! chars (split (car x) ""))) - (random:item chars)))) - -(set! random:item (fn* (lst:list) - "Return a random element from the specified list." - (nth lst (random (length lst))))) From e19143f09daa09ff90faefc9ad2dc04545747063 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:13:57 +0300 Subject: [PATCH 3/8] Added stdlib, broken into themed files --- stdlib/stdlib/comparisons.lisp | 45 +++++++++++ stdlib/stdlib/date.lisp | 32 ++++++++ stdlib/stdlib/directory.lisp | 8 ++ stdlib/stdlib/file.lisp | 57 +++++++++++++ stdlib/stdlib/logical.lisp | 51 ++++++++++++ stdlib/stdlib/mal.lisp | 141 +++++++++++++++++++++++++++++++++ stdlib/stdlib/maths.lisp | 55 +++++++++++++ stdlib/stdlib/random.lisp | 17 ++++ stdlib/stdlib/stdlib.lisp | 117 +++++++++++++++++++++++++++ stdlib/stdlib/string-case.lisp | 87 ++++++++++++++++++++ stdlib/stdlib/time.lisp | 41 ++++++++++ stdlib/stdlib/type-checks.lisp | 42 ++++++++++ 12 files changed, 693 insertions(+) create mode 100644 stdlib/stdlib/comparisons.lisp create mode 100644 stdlib/stdlib/date.lisp create mode 100644 stdlib/stdlib/directory.lisp create mode 100644 stdlib/stdlib/file.lisp create mode 100644 stdlib/stdlib/logical.lisp create mode 100644 stdlib/stdlib/mal.lisp create mode 100644 stdlib/stdlib/maths.lisp create mode 100644 stdlib/stdlib/random.lisp create mode 100644 stdlib/stdlib/stdlib.lisp create mode 100644 stdlib/stdlib/string-case.lisp create mode 100644 stdlib/stdlib/time.lisp create mode 100644 stdlib/stdlib/type-checks.lisp diff --git a/stdlib/stdlib/comparisons.lisp b/stdlib/stdlib/comparisons.lisp new file mode 100644 index 0000000..a0f1f0a --- /dev/null +++ b/stdlib/stdlib/comparisons.lisp @@ -0,0 +1,45 @@ +;;; comparisons.lisp - Numerical and integer comparisons + +;; We've defined "<" in natively, in golang. We can +;; define the other relational comparisons in terms of that. +(set! > (fn* (a b) + "Return true if a is greater than b." + (< b a))) + +(set! >= (fn* (a b) + "Return true if a is greater than, or equal to b." + (! (< a b)))) +(set! <= (fn* (a b) + "Return true if a is less than, or equal to, b." + (! (> a b)))) + + +;; +;; Some simple tests of specific numbers. +;; +(set! zero? (fn* (n) + "Return true if the number supplied as the first argument to this function is equal to zero." + (= n 0))) + +(set! one? (fn* (n) + "Return true if the number supplied as the argument to this function is equal to one." + (= n 1))) + +(set! even? (fn* (n) + "Return true if the number supplied as the argument to this function is even." + (zero? (% n 2)))) + +(set! odd? (fn* (n) + "Return true if the number supplied as the argument to this function is odd." + (! (even? n)))) + +;; +;; Some simple tests of specific boolean results +;; +(def! true? (fn* (arg) + "Return true if the argument supplied to this function is true." + (if (eq #t arg) true false))) + +(def! false? (fn* (arg) + "Return true if the argument supplied to this function is false." + (if (eq #f arg) true false))) diff --git a/stdlib/stdlib/date.lisp b/stdlib/stdlib/date.lisp new file mode 100644 index 0000000..07cb0e9 --- /dev/null +++ b/stdlib/stdlib/date.lisp @@ -0,0 +1,32 @@ +;;; date.lisp - Date-related functions. + +;; We have a built in function "date" to return the current date +;; as a list (XXXX DD MM YYYY). +;; +;; Here we create some helper functions for retrieving the various +;; parts of the date, as well as some aliases for ease of typing. +(set! date:day (fn* () + "Return the day of the current month, as an integer." + (nth (date) 1))) + +(set! date:month (fn* () + "Return the number of the current month, as an integer." + (nth (date) 2))) + + +(set! date:weekday (fn* () + "Return a string containing the current day of the week." + (nth (date) 0))) + +(set! date:year (fn* () + "Return the current year, as an integer." + (nth (date) 3))) + + +;; +;; define legacy aliases +;; +(alias day date:day) +(alias month date:month) +(alias weekday date:weekday) +(alias year date:year) diff --git a/stdlib/stdlib/directory.lisp b/stdlib/stdlib/directory.lisp new file mode 100644 index 0000000..8e5a6e4 --- /dev/null +++ b/stdlib/stdlib/directory.lisp @@ -0,0 +1,8 @@ +;;; directory.lisp - Directory-related functions + + +;; Handy function to invoke a callback on files +(set! directory:walk (fn* (path:string fn:function) + "Invoke the specified callback on every file beneath the given path." + + (apply (directory:entries path) fn))) diff --git a/stdlib/stdlib/file.lisp b/stdlib/stdlib/file.lisp new file mode 100644 index 0000000..327d569 --- /dev/null +++ b/stdlib/stdlib/file.lisp @@ -0,0 +1,57 @@ +;;; file.lisp - File-related primitives + + +;; Wrappers for accessing results of (file:stat) +(set! file:stat:size (fn* (path) + "Return the size of the given file, return -1 on error." + (let* (info (file:stat path)) + (cond + (nil? info) -1 + true (nth info 1))))) + +(set! file:stat:uid (fn* (path) + "Return the UID of the given file owner, return '' on error." + (let* (info (file:stat path)) + (cond + (nil? info) "" + true (nth info 2))))) + + +(set! file:stat:gid (fn* (path) + "Return the GID of the given file owner, return '' on error." + (let* (info (file:stat path)) + (cond + (nil? info) "" + true (nth info 3))))) + +(set! file:stat:mode (fn* (path) + "Return the mode of the given file, return '' on error." + (let* (info (file:stat path)) + (cond + (nil? info) "" + true (nth info 4))))) + +(set! file:which (fn* (binary) + "Return the complete path to the specified binary, found via the users' PATH setting. + +If the binary does not exist in a directory located upon the PATH nil will be returned. + +NOTE: This is a non-portable function! + + 1. It assumes that the environmental variable PATH exists. + 2. It assumes $PATH can be split by ':' + 3. It assumes '/' works as a directory separator. +" + (let* (path (split (getenv "PATH") ":") + res (filter path (lambda (dir) (exists? (join (list dir "/" binary)))))) + (if res + (join (list (car res) "/" binary)))))) + + +;; Define a legacy alias +(alias slurp file:read) + +;; Read a file, and execute the contents +(def! load-file (fn* (filename) + "Load and execute the contents of the supplied filename." + (eval (join (list "(do " (slurp filename) "\nnil)"))))) diff --git a/stdlib/stdlib/logical.lisp b/stdlib/stdlib/logical.lisp new file mode 100644 index 0000000..c73eafd --- /dev/null +++ b/stdlib/stdlib/logical.lisp @@ -0,0 +1,51 @@ +;;; logical.lisp - Logical functions. + + +;; Not is useful +(set! not (fn* (x) + "Return the inverse of the given boolean value." + (if x #f #t))) + +(alias ! not) + + +;; This is a bit sneaky. NOTE there is no short-circuiting here. +;; +;; Given a list use `filter` to return those items which are "true". +;; +;: If the length of the input list, and the length of the filtered list +;; are the same then EVERY element was true so our AND result is true. +(set! and (fn* (xs:list) + "Return true if every item in the specified list is true." + (let* (res nil) + (set! res (filter xs (lambda (x) (if x true false)))) + (if (= (length res) (length xs)) + true + false)))) + +(alias && and) + +;; This is also a bit sneaky. NOTE there is no short-circuiting here. +;; +;; Given a list use `filter` to return those items which are "true". +;; +;; If the output list has at least one element that was true then the +;; OR result is true. +(set! or (fn* (xs:list) + "Return true if any value in the specified list contains a true value." + (let* (res nil) + (set! res (filter xs (lambda (x) (if x true false)))) + (if (> (length res) 0) + true + false)))) + +(alias || or) + + +;; every is useful and almost a logical operation +(set! every (fn* (xs:list fun:function) + "Return true if applying every element of the list through the specified function resulted in a true result." + (let* (res (map xs fun)) + (if (and res) + true + false)))) diff --git a/stdlib/stdlib/mal.lisp b/stdlib/stdlib/mal.lisp new file mode 100644 index 0000000..4222d4a --- /dev/null +++ b/stdlib/stdlib/mal.lisp @@ -0,0 +1,141 @@ +;;; mal.lisp - Compatability with MAL, implemented in lisp. + + +;; Traditionally we use `car` and `cdr` for accessing the first and rest +;; elements of a list. For readability it might be nice to vary that +(alias first car) +(alias rest cdr) + + +;; Run an arbitrary series of statements, if the given condition is true. +;; +;; This is the more general/useful version of the "if2" macro, which +;; we demonstrate in mtest.lisp. +;; +;; Sample usage: +;; +;; (when (= 1 1) (print "OK") (print "Still OK") (print "final statement")) +;; +(defmacro! when (fn* (pred &rest) + "when is a macro which runs the specified body, providing the specified predicate is true. + +It is similar to an if-statement, however there is no provision for an 'else' clause, and the body specified may contain more than once expression to be evaluated." + `(if ~pred (do ~@rest)))) + +;; +;; If the specified predicate is true, then run the body. +;; +;; NOTE: This recurses, so it will eventually explode the stack. +;; +(defmacro! while (fn* (condition &body) + "while is a macro which repeatedly runs the specified body, while the condition returns a true-result" + (let* (inner-sym (gensym)) + `(let* (~inner-sym (fn* () + (if ~condition + (do + ~@body + (~inner-sym))))) + (~inner-sym))))) + + +;; +;; cond is a useful thing to have. +;; +(defmacro! cond (fn* (&xs) + "cond is a macro which accepts a list of conditions and results, and returns the value of the first matching condition. It is similar in functionality to a C case-statement." + (if (> (length xs) 0) + (list 'if (first xs) + (if (> (length xs) 1) + (nth xs 1) + (error "An odd number of forms to (cond..)")) + (cons 'cond (rest (rest xs))))))) + +;; A useful helper to apply a given function to each element of a list. +(set! apply (fn* (lst:list fun:function) + "Call the specified function on every element in the given list. + +See-also: apply-pairs, apply-hash" + (if (nil? lst) + () + (do + (fun (car lst)) + (apply (cdr lst) fun))))) + +;; Apply, but walking the list in pairs. +(set! apply-pairs (fn* (lst:list fun:function) + "Calling the specified function with two items on the specified list. + +This is similar to apply, but apply apply invokes the callback with a single list-item, and here we apply in pairs. + +Note: The list-length must be even, and if not that will raise an error. + +See-also: apply apply-hash +Example: (apply-pairs (list 1 2 3 4) (lambda (a b) (print \"Called with %s %s\" a b))) +" + (if (! (nil? lst)) + (if (= (% (length lst) 2) 0) + (let* (a (car lst) + b (car (cdr lst))) + (fun a b) + (apply-pairs (cdr (cdr lst) ) fun)) + (error "The list passed to (apply-pairs..) should have an even length"))))) + +;; Return the length of the given list. +(set! length (fn* (arg) + "Return the length of the supplied list. See-also strlen." + (if (list? arg) + (do + (if (nil? arg) 0 + (inc (length (cdr arg))))) + 0 + ))) + +(alias count length) + +;; Find the Nth item of a list +(set! nth (fn* (lst:list i:number) + "Return the Nth item of the specified list. + +Note that offset starts from 0, rather than 1, for the first item." + (if (> i (length lst)) + (error "Out of bounds on list-length") + (if (= 0 i) + (car lst) + (nth (cdr lst) (- i 1)))))) + + +(set! map (fn* (lst:list fun:function) + "Return a list with the contents of evaluating the given function on every item of the supplied list. + +See-also: map-pairs" + (if (nil? lst) + () + (cons (fun (car lst)) (map (cdr lst) fun))))) + +(set! map-pairs (fn* (lst:list fun:function) + "Return a list with the contents of evaluating the given function on every pair of items in the supplied list. + +See-also: map" + (if (! (nil? lst)) + (if (= (% (length lst) 2) 0) + (let* (a (car lst) + b (car (cdr lst))) + (cons (fun a b) (map-pairs (cdr (cdr lst)) fun))) + (error "The list passed should have an even length")) + ()))) + + +;; This is required for our quote/quasiquote/unquote/splice-unquote handling +;; +;; Testing is hard, but +;; +;; (define lst (quote (b c))) ; b c +;; (print (quasiquote (a lst d))) ; (a lst d) +;; (print (quasiquote (a (unquote lst) d))) ; (a (b c) d) +;; (print (quasiquote (a (splice-unquote lst) d))) ; (a b c d) +;; +(set! concat (fn* (seq1 seq2) + "Join two lists" + (if (nil? seq1) + seq2 + (cons (car seq1) (concat (cdr seq1) seq2))))) diff --git a/stdlib/stdlib/maths.lisp b/stdlib/stdlib/maths.lisp new file mode 100644 index 0000000..79b0685 --- /dev/null +++ b/stdlib/stdlib/maths.lisp @@ -0,0 +1,55 @@ +;;; maths.lisp - Some simple maths-related primitives + +;; inc/dec are useful primitives to have +(set! inc (fn* (n:number) + "inc will add one to the supplied value, and return the result." + (+ n 1))) + +(set! dec (fn* (n:number) + "dec will subtract one from the supplied value, and return the result." + (- n 1))) + +;; Square root +(set! sqrt (fn* (x:number) + "Calculate the square root of the given value." + (# x 0.5))) + +;; More mathematical functions relating to negative numbers. +(set! neg (fn* (n:number) + "Negate the supplied number, and return it." + (- 0 n))) + +(set! neg? (fn* (n:number) + "Return true if the supplied number is negative." + (< n 0))) + +(set! pos? (fn* (n:number) + "Return true if the supplied number is positive." + (> n 0))) + +(set! abs (fn* (n:number) + "Return the absolute value of the supplied number." + (if (neg? n) (neg n) n))) + +(set! sign (fn* (n:number) + "Return 1 if the specified number is positive, and -1 if it is negative." + (if (neg? n) (neg 1) 1))) + +;; Now define min/max using reduce +(set! min (fn* (xs:list) + "Return the smallest integer from the list of numbers supplied." + (if (nil? xs) + () + (reduce xs + (lambda (a b) + (if (< a b) a b)) + (car xs))))) + +(set! max (fn* (xs:list) + "Return the maximum integer from the list of numbers supplied." + (if (nil? xs) + () + (reduce xs + (lambda (a b) + (if (< a b) b a)) + (car xs))))) diff --git a/stdlib/stdlib/random.lisp b/stdlib/stdlib/random.lisp new file mode 100644 index 0000000..2efb75a --- /dev/null +++ b/stdlib/stdlib/random.lisp @@ -0,0 +1,17 @@ +;;; random.lisp - Random things. + + +;; Choose a random character from a string, or a-z if unspecified +(set! random:char (fn* (&x) + "Return a random character by default from the set a-z. + +If an optional string is provided it will be used as a list of characters to choose from." + (let* (chars (split "abcdefghijklmnopqrstuvwxyz" "")) + (if (list? x) + (set! chars (split (car x) ""))) + (random:item chars)))) + +;; random list item +(set! random:item (fn* (lst:list) + "Return a random element from the specified list." + (nth lst (random (length lst))))) diff --git a/stdlib/stdlib/stdlib.lisp b/stdlib/stdlib/stdlib.lisp new file mode 100644 index 0000000..0fed167 --- /dev/null +++ b/stdlib/stdlib/stdlib.lisp @@ -0,0 +1,117 @@ +;;; stdlib.lisp - Standard library as implemented in lisp. + + + + +;; Useful for creating a list of numbers +(set! repeated (fn* (n:number x) + "Return a list of length n whose elements are all x." + (when (pos? n) + (cons x (repeated (dec n) x))))) + +;; Return the last element of a list +;; +;; NOTE: This could be written more simply, for example: +;; +;; (set! last (fn* (lst:list) "Return the last element of the given list" (car (reverse lst)))) +;; +(set! last (fn* (lst:list) + "last returns the last item in the specified list, it is the inverse of (butlast) and the logical opposite of (car)." + (let* (c (cdr lst)) + (if (! (nil? c)) + (last c) + (car lst))))) + +;; Setup a simple function to run a loop N times +;; +(set! repeat (fn* (n body) + "Execute the supplied body of code N times." + (if (> n 0) + (do + (body n) + (repeat (- n 1) body))))) + +;; A helper to apply a function to each key/value pair of a hash +(set! apply-hash (fn* (hs:hash fun:function) + "Call the given function to every key in the specified hash. + +See-also: apply, apply-pairs" + (let* (lst (keys hs)) + (apply lst (lambda (x) (fun x (get hs x))))))) + + +;; Count the length of a string +(set! strlen (fn* (str:string) + "Calculate and return the length of the supplied string." + (length (split str "")))) + + +;; Create ranges of numbers in a list +(set! range (fn* (start:number end:number step:number) + "Create a list of numbers between the start and end bounds, inclusive, incrementing by the given offset each time." + (if (<= start end) + (cons start (range (+ start step) end step)) + ()))) + +;; Create sequences from 0/1 to N +(set! seq (fn* (n:number) + "Create, and return, list of number ranging from 0-N, inclusive." + (range 0 n 1))) +(set! nat (fn* (n:number) + "Create, and return, a list of numbers ranging from 1-N, inclusive." + (range 1 n 1))) + + +;; Remove items from a list where the predicate function is not T +(set! filter (fn* (xs:list f:function) + "Remove any items from the specified list, if the result of calling the provided function on that item is not true." + (if (nil? xs) + () + (if (f (car xs)) + (cons (car xs)(filter (cdr xs) f)) + (filter (cdr xs) f))))) + + + + +;; reduce function +(set! reduce (fn* (xs f acc) + "This is our reduce function, which uses a list, a function, and the accumulator." + (if (nil? xs) + acc + (reduce (cdr xs) f (f acc (car xs)))))) + +; O(n^2) behavior with linked lists +(set! append (fn* (xs:list el) + "Append the given element to the specified list." + (if (nil? xs) + (list el) + (cons (car xs) (append (cdr xs) el))))) + + +(set! reverse (fn* (x:list) + "Return a list containing all values in the supplied list, in reverse order." + (if (nil? x) + () + (append (reverse (cdr x)) (car x))))) + + +;; Get the first N items from a list. +(set! take (fn* (n l) + "Return the first N items from the specified list." + (cond (zero? n) nil + (nil? l) nil + true (cons (car l) (take (- n 1) (cdr l)))))) + +;; Remove the first N items from a list. +(set! drop (fn* (n l) + "Remove the first N items from the specified list." + (cond (zero? n) l + (nil? l) nil + true (drop (- n 1) (cdr l))))) + +;; Return everything but the last element. +(set! butlast (fn* (l) + "Return everything but the last element from the specified list." + + (take (dec (length l)) l))) diff --git a/stdlib/stdlib/string-case.lisp b/stdlib/stdlib/string-case.lisp new file mode 100644 index 0000000..8525154 --- /dev/null +++ b/stdlib/stdlib/string-case.lisp @@ -0,0 +1,87 @@ +;;; string-case.lisp - Convert a string to upper/lower case + +;; +;; This is either gross or cool. +;; +;; Define a hash which has literal characters and their upper-case, and +;; lower-cased versions +;; +(set! upper-table { + a "A" + b "B" + c "C" + d "D" + e "E" + f "F" + g "G" + h "H" + i "I" + j "J" + k "K" + l "L" + m "M" + n "N" + o "O" + p "P" + q "Q" + r "R" + s "S" + t "T" + u "U" + v "V" + w "W" + x "X" + y "Y" + z "Z" + } ) + +(set! lower-table { + A "a" + B "b" + C "c" + D "d" + E "e" + F "f" + G "g" + H "h" + I "i" + J "j" + K "k" + L "l" + M "m" + N "n" + O "o" + P "p" + Q "q" + R "r" + S "s" + T "t" + U "u" + V "v" + W "w" + X "x" + Y "y" + Z "z" + } ) + + +;; Translate the elements of the string using the specified hash +(set! translate (fn* (x:string hsh:hash) + "Translate each character in the given string, via the means of the supplied lookup-table. + +This is used by both 'upper' and 'lower'." + (let* (chrs (split x "")) + (join (map chrs (lambda (x) + (if (get hsh x) + (get hsh x) + x))))))) + +;; Convert the given string to upper-case, via the lookup table. +(set! upper (fn* (x:string) + "Convert each character from the supplied string to upper-case, and return that string." + (translate x upper-table))) + +;; Convert the given string to upper-case, via the lookup table. +(set! lower (fn* (x:string) + "Convert each character from the supplied string to lower-case, and return that string." + (translate x lower-table))) diff --git a/stdlib/stdlib/time.lisp b/stdlib/stdlib/time.lisp new file mode 100644 index 0000000..b555d00 --- /dev/null +++ b/stdlib/stdlib/time.lisp @@ -0,0 +1,41 @@ +;;; time.lisp - Time related functions + + +;; We have a built in function "time" to return the current time +;; as a list (HH MM SS). +;; +;; Here we create some helper functions for retrieving the various +;; parts of the time, as well as some aliases for ease of typing. + +(set! time:hour (fn* () + "Return the current hour, as an integer." + (nth (time) 0))) + +(set! time:minute (fn* () + "Return the current minute, as an integer." + (nth (time) 1))) + +(set! time:second (fn* () + "Return the current seconds, as an integer." + (nth (time) 2))) + +;; define legacy aliases +(alias hour time:hour) +(alias minute time:minute) +(alias second time:second) + +(set! zero-pad-single-number (fn* (num) + "Prefix the given number with zero, if the number is less than ten. + +This is designed to pad the hours, minutes, and seconds in (hms)." + (if (< num 10) + (sprintf "0%s" num) + num))) + +(set! time:hms (fn* () + "Return the current time, formatted as 'HH:MM:SS', as a string." + (sprintf "%s:%s:%s" + (zero-pad-single-number (hour)) + (zero-pad-single-number (minute)) + (zero-pad-single-number (second))))) +(alias hms time:hms) diff --git a/stdlib/stdlib/type-checks.lisp b/stdlib/stdlib/type-checks.lisp new file mode 100644 index 0000000..e5294be --- /dev/null +++ b/stdlib/stdlib/type-checks.lisp @@ -0,0 +1,42 @@ +;;; type-checks.lisp - Type-comparisions for given objects + +;; There is a built in `type` function which returns the type of an object. +;; +;; Use this to define some simple methods to test argument-types +(set! boolean? (fn* (x) + "Returns true if the argument specified is a boolean value." + (eq (type x) "boolean"))) + +(set! error? (fn* (x) + "Returns true if the argument specified is an error-value." + (eq (type x) "error"))) + +(set! function? (fn* (x) "Returns true if the argument specified is a function, either a built-in function, or a user-written one." + (or + (list + (eq (type x) "procedure(lisp)") + (eq (type x) "procedure(golang)"))))) + +(set! hash? (fn* (x) + "Returns true if the argument specified is a hash." + (eq (type x) "hash"))) + +(set! macro? (fn* (x) + "Returns true if the argument specified is a macro." + (eq (type x) "macro"))) + +(set! list? (fn* (x) + "Returns true if the argument specified is a list." + (eq (type x) "list"))) + +(set! number? (fn* (x) + "Returns true if the argument specified is a number." + (eq (type x) "number"))) + +(set! string? (fn* (x) + "Returns true if the argument specified is a string." + (eq (type x) "string"))) + +(set! symbol? (fn* (x) + "Returns true if the argument specified is a symbol." + (eq (type x) "symbol"))) From 0ddff1d9c5f7746873b4bda38bbd201a5952b04a Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:14:07 +0300 Subject: [PATCH 4/8] Minimal test-coverage --- stdlib/stdlib_test.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/stdlib/stdlib_test.go b/stdlib/stdlib_test.go index f86bb9d..5307f14 100644 --- a/stdlib/stdlib_test.go +++ b/stdlib/stdlib_test.go @@ -5,27 +5,24 @@ import ( "testing" ) -func TestStdlib(t *testing.T) { - x := Contents() +// Test we can exclude all +func TestStdlibExcludeAll(t *testing.T) { - var core []byte - var mal []byte - var err error + // By default we get "stuff" + x := Contents() - core, err = os.ReadFile("stdlib.lisp") - if err != nil { - t.Fatalf("failed to read: %s", err) + if len(x) < 1 { + t.Fatalf("Failed to get contents of stdlib") } - mal, err = os.ReadFile("mal.lisp") - if err != nil { - t.Fatalf("failed to read: %s", err) - } + // Excluding everything should return nothing + os.Setenv("YAL_STDLIB_EXCLUDE_ALL", "yes") - // Add one for the newline we add in the middle. - total := len(core) + 1 + len(mal) + 1 - if total != len(x) { - t.Fatalf("stdlib size mismatch") + x = Contents() + if len(x) != 0 { + t.Fatalf("We expected no content, but got something, despite $YAL_STDLIB_EXCLUDE_ALL") } + // restore + os.Setenv("YAL_STDLIB_EXCLUDE_ALL", "") } From 6ed543400e366c8117ed04da0e2350c724f26329 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:16:07 +0300 Subject: [PATCH 5/8] Link to the stdlib/stdlib/ content, not the old files --- PRIMITIVES.md | 7 +++---- README.md | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/PRIMITIVES.md b/PRIMITIVES.md index 03f4abf..df35d67 100644 --- a/PRIMITIVES.md +++ b/PRIMITIVES.md @@ -188,10 +188,9 @@ Things you'll find here include: The standard library consists of routines, and helpers, which are written in 100% yal itself. -The implementation of these primitives can be found in the following two files: +The implementation of these primitives can be found in the following directory: -* [stdlib/stdlib.lisp](stdlib/stdlib.lisp) -* [stdlib/mal.lisp](stdlib/mal.lisp) +* [stdlib/stdlib/](stdlib/stdlib/) The code in those files is essentially **prepended** to any script that is supplied upon the command-line. @@ -282,7 +281,7 @@ Functions here include: * `map` * Return the results of applying the specified function to every element of the given list. * `map-pairs` - * Return the results of applying the specified function to every pair of elements in the given list. + * Return the results of applying the specified function to every pair of elements in the given list. * `max` * Return the maximum value in the specified list. * `min` diff --git a/README.md b/README.md index dc17bba..39631e1 100644 --- a/README.md +++ b/README.md @@ -81,10 +81,10 @@ A reasonable amount of sample code can be found in the various included examples As noted there is a standard-library of functions which are loaded along with any user-supplied script. These functions are implemented in lisp and also serve as a demonstration of syntax and features: -* [stdlib/stdlib.lisp](stdlib/stdlib.lisp) -* [stdlib/mal.lisp](stdlib/mal.lisp) +* [stdlib/stdlib/](stdlib/stdlib/) -Running these example will produce output, for example: +Running any of our supplied examples will produce output, for example here's the result +of running the `fibonacci.lisp` file: ```lisp $ yal fibonacci.lisp From 8ad897a3c773ed61c4cab9b37e2b2a4b63951fa9 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:34:04 +0300 Subject: [PATCH 6/8] More test-coverage --- stdlib/stdlib_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/stdlib/stdlib_test.go b/stdlib/stdlib_test.go index 5307f14..6d4032d 100644 --- a/stdlib/stdlib_test.go +++ b/stdlib/stdlib_test.go @@ -2,6 +2,7 @@ package stdlib import ( "os" + "strings" "testing" ) @@ -26,3 +27,39 @@ func TestStdlibExcludeAll(t *testing.T) { // restore os.Setenv("YAL_STDLIB_EXCLUDE_ALL", "") } + +// Test we can exclude time.lisp +func TestStdlibExcludeTime(t *testing.T) { + + // By default we get "stuff" + x := Contents() + + if len(x) < 1 { + t.Fatalf("Failed to get contents of stdlib") + } + + // ensure we have "hms" function defined + expected := "(hms)" + + content := string(x) + if !strings.Contains(content, expected) { + t.Fatalf("failed to find expected function: %s", expected) + } + + // Now exclude "time" + os.Setenv("YAL_STDLIB_EXCLUDE", "time") + + // Re-read content + x = Contents() + if len(x) < 1 { + t.Fatalf("Failed to get contents of stdlib") + } + + content = string(x) + if strings.Contains(content, expected) { + t.Fatalf("we shouldn't find the excluded function, but we did: %s", expected) + } + + // restore + os.Setenv("YAL_STDLIB_EXCLUDE", "") +} From 0c618adf4c86eb274fa0921a71e4bb26c3a58187 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:34:17 +0300 Subject: [PATCH 7/8] document excluding all/some of the standard-library --- README.md | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 39631e1..4a7d23b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ * [yet another lisp](#yet-another-lisp) * [Building / Installing](#building--installing) +* [Standard Library](#standard-library) * [Usage](#usage) * [Examples](#examples) * [Fuzz Testing](#fuzz-testing) @@ -70,6 +71,36 @@ v0.11.0 f21d032e812ee6eadad5eac23f079a11f5e1041a ``` + +## Standard Library + +When user-code is executed, whether a simple statement supplied via the command-line, or read from a file, a standard-library is loaded from beneath the directory: + +* [stdlib/stdlib/](stdlib/stdlib/) + + +Our standard-library consists of primitive functions such as `(map..)`, `(min..)` and similar, is written in 100% yal-lisp. + +The standard library may be entirely excluded via the use of the environmental varilable `YAL_STDLIB_EXCLUDE_ALL`: + +``` +$ yal -e "(print (hms))" +22:30:57 + +$ YAL_STDLIB_EXCLUDE_ALL=true yal -e "(print (hms))" +Error running: error expanding argument [hms] for call to (print ..): + ERROR{argument 'hms' not a function} +``` + +If you prefer you may exclude specific _parts_ of the standard library, by specify the filenames you wish to exclude separated by commas: + +``` +$ YAL_STDLIB_ECLUDE=date,type-checks yal -e "(print (hms))" +22:30:57 +``` + + + ## Examples A reasonable amount of sample code can be found in the various included examples: @@ -79,12 +110,11 @@ A reasonable amount of sample code can be found in the various included examples * [fizzbuzz.lisp](fizzbuzz.lisp) is a standalone sample of solving the fizzbuzz problem. * [mtest.lisp](mtest.lisp) shows some macro examples. -As noted there is a standard-library of functions which are loaded along with any user-supplied script. These functions are implemented in lisp and also serve as a demonstration of syntax and features: +As noted there is a standard-library of functions which are loaded along with any user-supplied script - that library of functions may also provide a useful reference and example of yal-code: * [stdlib/stdlib/](stdlib/stdlib/) -Running any of our supplied examples will produce output, for example here's the result -of running the `fibonacci.lisp` file: +Running any of our supplied examples should produce useful output for reference. For example here's the result of running the `fibonacci.lisp` file: ```lisp $ yal fibonacci.lisp From 1feefa0c1bf432432289c3f9eca8d518a56d2ab6 Mon Sep 17 00:00:00 2001 From: Steve Kemp Date: Fri, 21 Oct 2022 22:37:36 +0300 Subject: [PATCH 8/8] Remove nil-checking; can't fail. test coverage back to 100% --- stdlib/stdlib.go | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 14ec9d3..6552357 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -40,12 +40,8 @@ func Contents() []byte { exclude = strings.Split(os.Getenv("YAL_STDLIB_EXCLUDE"), ",") } - // Read the list of entries - entries, err := stdlib.ReadDir("stdlib") - if err != nil { - fmt.Printf("Failed to read embedded resources; fatal error\n") - return result - } + // Read the list of entries - can't fail + entries, _ := stdlib.ReadDir("stdlib") // For each entry for _, entry := range entries { @@ -67,12 +63,8 @@ func Contents() []byte { continue } - // Read the content - data, err := stdlib.ReadFile(fp) - if err != nil { - fmt.Printf("Failed to read embedded resource - %s - fatal error %s\n", fp, err) - return result - } + // Read the content - can't fail + data, _ := stdlib.ReadFile(fp) // Append to our result result = append(result, data...)