Document not found (404)
+This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..0f9eb67b9 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.aarch64-unknown-linux-gnu] +linker = "/usr/bin/aarch64-linux-gnu-gcc" + +[alias] +xtask = "run --package xtask --" diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..2f7896d1d --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +target/ diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..5c738e052 --- /dev/null +++ b/.envrc @@ -0,0 +1,3 @@ +watch_file flake.lock + +use flake diff --git a/book/.nojekyll b/book/.nojekyll new file mode 100644 index 000000000..f17311098 --- /dev/null +++ b/book/.nojekyll @@ -0,0 +1 @@ +This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/book/404.html b/book/404.html new file mode 100644 index 000000000..296691fc8 --- /dev/null +++ b/book/404.html @@ -0,0 +1,220 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +Multiplies the given numbers.
+(* . nums) -> number?
+> (* 5 3) ;; => 15
+> (* 10 3 2) ;; => 60
+> (*) ;; => 1
+
+Adds the given numbers.
+(+ . nums) -> number?
+> (+ 5 3) ;; => 8
+> (+ 10 3 2) ;; => 15
+> (+) ;; => 0
+
+Subtracts the given numbers.
+(- . nums) -> number?
+> (- 5 3) ;; => 2
+> (- 10 3 2) ;; => 5
+> (- -5) ;; => 5
+
+Divides the given numbers.
+(/ . nums) -> number?
+> (/ 10 2) ;; => 5
+> (/ 10 2 2.0) ;; => 2.5
+> (/ 1 3.0) ;; => 0.3333333333333333
+> (/ 1 3) ;; => 1/3
+
+Compares real numbers to check if any number is less than the subsequent.
+(< x . rest) -> bool?
+> (< 1) ;; => #t
+> (< 3 2) ;; => #f
+> (< 2 3) ;; => #t
+> (< 3/2 1.5) ;; => #f
+> (< 2.5 3/2) ;; => #t
+> (< 2 5/2 3) ;; #t
+
+Compares real numbers to check if any number is less than or equal than the subsequent.
+(<= x . rest) -> bool?
+> (<= 1) ;; => #t
+> (<= 3 2) ;; => #f
+> (<= 2 3) ;; => #t
+> (<= 3/2 1.5) ;; => #t
+> (<= 2.5 3/2) ;; => #f
+> (<= 2 6/2 3) ;; #t
+
+Compares real numbers to check if any number is greater than the subsequent.
+(> x . rest) -> bool?
+> (> 1) ;; => #t
+> (> 3 2) ;; => #t
+> (> 1 1) ;; => #f
+> (> 3/2 1.5) ;; => #f
+> (> 3/2 1.4) ;; => #t
+> (> 3 4/2 1) ;; #t
+
+Compares real numbers to check if any number is greater than or equal than the subsequent.
+(>= x . rest) -> bool?
+> (>= 1) ;; => #t
+> (>= 3 2) ;; => #t
+> (>= 2 3) ;; => #f
+> (>= 3/2 1.5) ;; => #t
+> (>= 3/2 1.4) ;; => #t
+> (>= 2 4/2 1) ;; #t
+
+Computes the absolute value of the given number.
+(abs number) -> number?
+> (abs 42) ;; => 42
+> (abs -42) ;; => 42
+> (abs 0) ;; => 0
+
+Appends the given lists together. If provided with no lists, will return the empty list.
+(append lst ...)
+lst : list?
+> (append (list 1 2) (list 3 4)) ;; => '(1 2 3 4)
+> (append) ;; => '()
+
+Applies the given function
with arguments as the contents of the list
.
(apply function lst) -> any?
+> (apply + (list 1 2 3 4)) ;; => 10
+> (apply list (list 1 2 3 4)) ;; => '(1 2 3 4)
+
+Returns #t
if the given value is a byte, meaning an exact
+integer between 0 and 255 inclusive, #f
otherwise.
(byte? 65) ;; => #t
+(byte? 0) ;; => #t
+(byte? 256) ;; => #f
+(byte? 100000) ;; => #f
+(byte? -1) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytes b ...)
+(bytes 65 112 112 108 101)
+
+Converts the bytevector to the equivalent list representation.
+(bytes->list (bytes 0 1 2 3 4 5)) ;; => '(0 1 2 3 4 5)
+
+Decodes a string from a bytevector containing valid UTF-8.
+(bytes->string/utf8 buf [start] [end]) -> string?
+(bytes->string/utf8 (bytes #xe5 #x8d #x83 #xe8 #x91 #x89)) ;; => "千葉"
+
+Append multiple byte vectors into a new bytevector.
+(bytes-append #u8(0 1 2) #u8(3 4 5)) ;; => #u8(#x00 #x01 #x02 #x03 #x04 #x05)
+
+(bytes-append #u8(0) #u8(1) #u8() #u8(2)) ;; => #u8(#x00 #x01 #x02)
+
+Returns the length of the given byte vector
+(bytes-length (bytes 1 2 3 4 5)) ;; => 5
+
+Fetches the byte at the given index within the bytevector. +If the index is out of bounds, this will error.
+(bytes-ref vector index)
+(bytes-ref (bytes 0 1 2 3 4 5) 3) ;; => 4
+(bytes-ref (bytes) 10) ;; error
+
+Sets the byte at the given index to the given byte. Will error +if the index is out of bounds.
+(bytes-set! vector index byte)
+(define my-bytes (bytes 0 1 2 3 4 5))
+(bytes-set! my-bytes 0 100)
+(bytes-ref my-bytes 0) ;; => 100
+
+Returns #t
if this value is a bytevector
(bytes? (bytes 0 1 2)) ;; => #t
+(bytes? (list 10 20 30)) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytevector b ...)
+(bytevector 65 112 112 108 101)
+
+Creates a copy of a bytevector.
+(bytevector-copy vector [start end]) -> bytes?
+(define vec (bytes 1 2 3 4 5))
+
+(bytevector-copy vec) ;; => (bytes 1 2 3 4 5)
+(bytevector-copy vec 1 3) ;; => (bytes 2 3)
+
+Returns canonical path with all components normalized
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Returns the rest of the list. Will raise an error if the list is empty.
+(cdr l) -> list?
+> (cdr (list 10 20 30)) ;; => '(20 30)
+> (cdr (list 10)) ;; => '()
+> (cdr '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (cdr '())
+│ ^^^ cdr expects a non empty list
+
+Rounds the given number up to the nearest integer not less than it.
+(ceiling number) -> integer?
+> (ceiling 42) ;; => 42
+> (ceiling 42.1) ;; => 43
+> (ceiling -42.1) ;; => -42
+
+Change the current working directory
+Returns the Unicode codepoint of a given character.
+(char->integer char?) -> integer?
+Attemps to convert the character into a decimal digit,
+and returns #f
on failure.
Returns #t
if the character is a decimal digit.
Returns the lower case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns the upper case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns #t
if the character is a whitespace character.
Compares characters according to their codepoints, in a "less-than-or-equal" fashion.
+(char<=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "less-than" fashion.
+(char<? char1 char2 ... ) -> bool?
+Checks if all characters are equal.
+Requires that all inputs are characters, and will otherwise raise an error.
+(char=? char1 char2 ...) -> bool?
+Compares characters according to their codepoints, in a "greater-than-or-equal" fashion.
+(char>=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "greater-than" fashion.
+(char>? char1 char2 ... ) -> bool?
+Returns the command line passed to this process, +including the command name as first argument.
+Checks if the given value is a complex number
+(complex? value) -> boolean?
+> (complex? 3+4i) ;; => #t
+> (complex? 42) ;; => #t
+> (complex? "hello") ;; => #f
+
+Returns a newly allocated list whose first element is a
and second element is d
.
(cons a d) -> list?
+> (cons 1 2) ;; => '(1 . 2)
+> (cons 1 '()) ;; => '(1)
+
+Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Returns the number of milliseconds since the Unix epoch as an inexact number.
+(current-inexact-milliseconds) -> inexact?
+Returns the number of milliseconds since the Unix epoch as an integer.
+(current-milliseconds) -> int?
+Returns the number of seconds since the Unix epoch as an integer.
+(current-second) -> int?
+Deletes the directory
+Deletes the file
+Retrieves the denominator of the given rational number.
+(denominator number) -> integer?
+> (denominator 1/2) ;; => 2
+> (denominator 3/4) ;; => 4
+> (denominator 4) ;; => 1
+
+Returns #t
if the value is an disconnected-channel object.
(eof-object? any/c) -> bool?
+Returns a string representation of a duration
+(duration->string dur)
+Returns #t
if the value is an empty-channel object.
(empty-channel-object? any/c) -> bool?
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Returns an EOF object.
+(eof-object) -> eof-object?
+Returns #t
if the value is an EOF object.
(eof-object? any/c) -> bool?
+Returns the message of an error object.
+(error-object-message error?) -> string?
+Converts an exact number to an inexact number.
+(exact->inexact num) -> number?
+> (exact->inexact 10) ;; => 10
+> (exact->inexact 1/2) ;; => 0.5
+> (exact->inexact 1+2i) ;; => 1+2i
+
+Computes the integer square root of the given non-negative integer.
+(exact-integer-sqrt number) -> (integer? integer?)
+> (exact-integer-sqrt 25) ;; => (5 0)
+> (exact-integer-sqrt 35) ;; => (5 10)
+
+Checks if the given value is an exact integer
+(exact-integer? value) -> boolean?
+> (exact-integer? 42) ;; => #t
+> (exact-integer? -42) ;; => #t
+> (exact-integer? 4.0) ;; => #f
+
+Checks if the given value is exact.
+(exact? val) -> boolean?
+> (exact? 42) ;; => #t
+> (exact? 3.14) ;; => #f
+> (exact? "hello") ;; => #f
+
+Returns Euler’s number raised to the power of z.
+(exp z) -> number?
+> (exp 0) ;; => 1
+> (exp 2) ;; => 7.38905609893065
+> (exp 1.5) ;; => 4.4816890703380645
+
+Raises the left operand to the power of the right operand.
+(expt base exponent) -> number?
+> (expt 2 3) ;; => 8
+> (expt 2.0 0.5) ;; => 1.4142135623730951
+> (expt 9 0.5) ;; => 3
+
+Gets the filename for a given path
+Returns #t
if the given number is finite.
(finite? number) -> boolean?
+> (finite? 42) ;; => #t
+> (finite? 0.1) ;; => #t
+> (finite? +inf.0) ;; => #f
+> (finite? -inf.0) ;; => #f
+> (finite? +nan.0) ;; => #f
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Checks if the given value is a floating-point number
+(float? value) -> boolean?
+> (float? 42) ;; => #f
+> (float? 3.14) ;; => #t
+> (float? #t) ;; => #f
+
+Computes the largest integer less than or equal to the given number.
+(floor number) -> number?
+> (floor 3.14) ;; => 3
+> (floor 4.99) ;; => 4
+> (floor -2.5) ;; => -3
+
+Extracts the contents from a port created with open-output-bytevector
.
(get-output-bytevector port?) -> bytes?
+Extracts the string contents from a port created with open-output-string
.
(get-output-string port?) -> string?
+Get the value out of the thread local storage slot.
+Creates an immutable hash table with each given key
mapped to the following val
.
+Each key must have a val, so the total number of arguments must be even.
(hash key val ...) -> hash?
+key : hashable? +val : any/c
+Note: the keys must be hashable.
+> (hash 'a 10 'b 20)",
+r#"=> #<hashmap {
+'a: 10,
+'b: 20,
+}>"#,
+
+Clears the entries out of the existing hashmap. +Will attempt to reuse the existing memory if there are no other references +to the hashmap.
+(hash-clear h) -> hash?
+h: hash?
+> (hash-clear (hash 'a 10 'b 20))
+=> '#hash()
+
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Checks whether the hash map is empty
+(hash-empty? m) -> bool?
+m: hash?
+> (hash-empty? (hash 'a 10)) ;; => #f
+> (hash-emptY? (hash)) ;; => #true
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Returns the keys of the given hash map as a list.
+(hash-keys->list map) -> (listof hashable?)
+
+> (hash-keys->list? (hash 'a 'b 20)) ;; => '(a b)
+
+Returns the keys of the given hash map as an immutable vector
+(hash-keys->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> ['a 'b]",
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-ref (hash 'a 10 'b 20) 'b) ;; => 20
+
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Constructs the union of two hashmaps, keeping the values +in the left map when the keys exist in both maps.
+Will reuse memory where possible.
+(hash-union l r) -> hash?
+> (hash-union (hash 'a 10) (hash 'b 20)) ;; => '#hash((a . 10) (b . 20))
+
+Returns the values of the given hash map as a list
+(hash-values->list? map) -> (listof any/c)?
+map: hash?
+> (hash-values->list? (hash 'a 10 'b 20)),
+=> '(10 20)",
+
+Returns the values of the given hash map as an immutable vector
+(hash-values->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> [10 10]",
+
+Converts an inexact number to an exact number.
+(inexact->exact num) -> number?
+> (inexact->exact 10.0) ;; => 10
+> (inexact->exact 1.5) ;; => 3/2
+> (inexact->exact 1.5+2.5i) ;; => 3/2+5/2i
+
+Checks if the given value is inexact.
+(inexact? val) -> boolean?
+> (inexact? 42) ;; => #f
+> (inexact? 3.14) ;; => #t
+
+Returns #t
if the given number is infinite.
(infinite? number) -> boolean?
+> (infinite? 42) ;; => #f
+> (infinite? -nan.0) ;; => #f
+> (infinite? +inf.0) ;; => #t
+
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Checks if the given value is an integer, an alias for integer?
(int? value) -> boolean?
+> (int? 42) ;; => #t
+> (int? 3.14) ;; => #f
+> (int? "hello") ;; => #f
+
+Returns the character corresponding to a given Unicode codepoint.
+(integer->char integer?) -> char?
+Checks if the given value is an integer, an alias for int?
(integer? value) -> boolean?
+> (integer? 42) ;; => #t
+> (integer? 3.14) ;; => #f
+> (integer? "hello") ;; => #f
+
+Checks if a path is a directory
+Checks if a path is a file
+Returns the last element in the list. Takes time proportional to the length of the list.
+(last l) -> any/c
+> (list (list 1 2 3 4)) ;; => 4
+
+Returns the length of the list.
+(length l) -> int?
+> (length (list 10 20 30)) ;; => 3
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Converts the list of bytes to the equivalent bytevector representation.
+The list must contain only values which satisfy the byte?
predicate,
+otherwise this function will error.
(list->bytes (list 0 1 2 3 4 5)) ;; => (bytes 0 1 2 3 4 5)
+
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the +time complexity is O(n/64). Meaning, for small lists this can be constant.
+(list-ref lst index) -> list?
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Returns the local time in the format given by the input string (using chrono::Local::format
).
(local-time/now! fmt) -> string?
+Lock the given mutex
+Unlock the given mutex
+Computes the natural logarithm of the given number.
+(log number [base]) -> number?
+> (log 10) ;; => 2.302585092994046
+> (log 100 10) ;; => 2
+> (log 27 3) ;; => 3
+
+Computes the magnitude of the given number.
+(magnitude number) -> number?
+> (magnitude 3+4i) ;; => 5
+> (magnitude 5) ;; => 5
+> (magnitude -5) ;; => 5
+
+Creates a bytevector given a length and a default value.
+(make-bytes len default) -> bytes?
+(make-bytes 6 42) ;; => (bytes 42 42 42 42 42)
+
+Creates a string of a given length, filled with an optional character
+(which defaults to #\0
).
(make-string len [char]) -> string?
+Creates a thread local storage slot. These slots are static, and will not be reclaimed.
+When spawning a new thread, the value inside will be shared into that slot, however +future updates to the slot will be local to that thread.
+Construct a new mutex
+Returns #t
if the real number is Nan.
(nan? value) -> boolean?
+(nan? +nan.0) => #t
+(nan? 100000) => #f
+
+Checks if the given real number is negative.
+(negative? num) -> boolean?
+> (negative? 0) ;; => #f
+> (negative? 1) ;; => #f
+> (negative? -1) ;; => #t
+
+Converts the given number to a string.
+Checks if the given value is a number
+(number? value) -> boolean?
+> (number? 42) ;; => #t
+> (number? "hello") ;; => #f
+> (number? 'symbol) ;; => #f
+
+Retrieves the numerator of the given rational number.
+(numerator number) -> number?
+> (numerator 3/4) ;; => 3
+> (numerator 5/2) ;; => 5
+> (numerator -2) ;; => -2
+
+Creates an input port from a bytevector, that will return the bytevector contents.
+(open-input-bytevector bytes?) -> input-port?
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Creates an input port from a string, that will return the string contents.
+(open-input-string string?) -> input-port?
+Creates an output port that accumulates what is written into a bytevector.
+These bytes can be recovered calling get-output-bytevector
.
(open-output-bytevector) -> output-port?
+(define out (open-output-bytevector))
+
+
+(write-byte 30 out)
+(write-byte 250 out)
+
+(get-output-bytevector out) ;; => (bytes 30 250)
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Creates an output port that accumulates what is written into a string.
+This string can be recovered calling get-output-string
.
(open-output-string) -> output-port?
+(define out (open-output-string))
+
+
+(write-char "α" out)
+(write-char "ω" out)
+
+(get-output-string out) ;; => "αω"
+
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Checks if the given value can be treated as a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Gets the parent directory name for a given path
+Gets the extension from a path
+Checks if a path exists
+Peeks the next byte from an input port.
+(peek-byte [port]) -> byte?
+Checks if the given real number is positive.
+(positive? num) -> boolean?
+> (positive? 0) ;; => #f
+> (positive? 1) ;; => #t
+> (positive? -1) ;; => #f
+
+Returns quotient of dividing numerator by denomintator.
+(quotient numerator denominator) -> integer?
+> (quotient 11 2) ;; => 5
+> (quotient 10 2) ;; => 5
+> (quotient -10 2) ;; => -5
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Returns #t if obj is a rational number, #f otherwise. +Rational numbers are numbers that can be expressed as the quotient of two numbers. +For example, 3/4, -5/2, 0.25, and 0 are rational numbers.
+(rational? value) -> bool?
+Examples:
+> (rational? (/ 0.0)) ;; => #f
+> (rational? 3.5) ;; => #t
+> (rational? 6/10) ;; => #t
+> (rational? +nan.0) ;; => #f
+
+Reads a single byte from an input port.
+(read-byte [port]) -> byte?
+Reads the next character from an input port.
+(read-char [port]) -> char?
+Returns the contents of the directory as a list
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Checks if the given value is a real number
+(real? value) -> boolean?
+> (real? 42) ;; => #t
+> (real? 3+4i) ;; => #f
+> (real? "hello") ;; => #f
+
+Blocks until one of the channels passed in is ready to receive. +Returns the index of the channel arguments passed in which is ready.
+Using this directly is not recommended.
+Returns the rest of the list. Will raise an error if the list is empty.
+(rest l) -> list?
+> (rest (list 10 20 30)) ;; => '(20 30)
+> (rest (list 10)) ;; => '()
+> (rest (list 10))
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (rest '())
+│ ^^^^ rest expects a non empty list
+
+Returns a list that has the same elements as lst
, but in reverse order.
+This function takes time proportional to the length of lst
.
(reverse lst) -> list?
+> (reverse (list 1 2 3 4)) ;; '(4 3 2 1)
+
+Rounds the given number to the nearest integer.
+(round number) -> number?
+> (round 3.14) ;; => 3
+> (round 4.6) ;; => 5
+> (round -2.5) ;; => -3
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **set-tls!**
+Set the value in the the thread local storage. Only this thread will see the updates associated
+with this TLS.
+### **split-many**
+Splits a string given a separator pattern into a list of strings.
+
+(split-many str pat) -> (listof string?)
+
+* str : string?
+* pat : string?
+
+#### Examples
+```scheme
+(split-many "foo,bar,baz" ",") ;; => '("foo" "bar" "baz")
+(split-many "foo|bar|" "|") ;; => '("foo" "bar" "")
+(split-many "" "&") ;; => '("")
+
+Splits a string given a separator at most once, yielding +a list with at most 2 elements.
+(split-once str pat) -> string?
+(split-once "foo,bar,baz" ",") ;; => '("foo" "bar,baz")
+(split-once "foo|bar|" "|") ;; => '("foo" "bar|")
+(split-once "" "&") ;; => '("")
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Computes the square root of the given number.
+(sqrt number) -> number?
+> (sqrt 4) ;; => 2
+> (sqrt 2) ;; => 1.4142135623730951
+> (sqrt -1) ;; => 0+1i
+
+Computes the square of the given number.
+(square number) -> number?
+> (square 5) ;; => 25
+> (square -3) ;; => 9
+> (square 2.5) ;; => 6.25
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Constructs a string from the given characters
+Encodes a string as UTF-8 into a bytevector.
+(string->bytes string?) -> bytes?
+(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Deserializes a JSON string into a Steel value.
+(string->jsexpr json) -> any/c
+(string->jsexpr "{\"foo\": [3]}") ;; => '#hash((foo . (3)))
+
+Converts a string into a list of characters.
+(string->list s [start] [end]) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Creates a new lowercased version of the input string
+(string->lower string?) -> string?
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Converts the given string to a number, with an optional radix.
+On failure, it returns #f
(string->number digits [radix]) -> (or/c number? boolean?)
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Alias of string->bytes
.
Returns a vector containing the characters of a given string
+(string->vector string?) -> vector?
+(string->vector "hello") ;; => '#(#\h #\e #\l #\l #\o)
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+Compares strings lexicographically (as in"less-than-or-equal"), +in a case insensitive fashion.
+(string-ci<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than"), +in a case insensitive fashion.
+(string-ci<? s1 s2 ... ) -> bool?
+Compares strings for equality, in a case insensitive fashion.
+Compares strings lexicographically (as in"greater-than-or-equal"), +in a case insensitive fashion.
+(string-ci>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than"), +in a case insensitive fashion.
+(string-ci>? s1 s2 ... ) -> bool?
+Searches a string to check if it contains the second argument.
+(string-contains? string? string?) -> bool?
+(string-contains? "hello" "lo") ;;=> #t
+(string-contains? "hello" "world") ;;=> #f
+
+Joins the given list of strings, with an optional separator.
+(string-join strings [sep]) -> string?
+(string-join '("a" "b" "c")) ;; => "abc"
+(string-join '("one" "two" "three") ", ") ;; => "one, two, three"
+
+Get the length of the given string in UTF-8 bytes.
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+> (string-length "✅") ;; => 3
+> (string-length "🤖") ;; => 4
+
+Extracts the nth character out of a given string.
+(string-ref str n) -> char?
+Replaces all occurrences of a pattern into the given string
+(string-replace str from to) -> string?
+(string-replace "hello world" "o" "@") ;; => "hell@ w@rld"
+
+Compares strings lexicographically (as in"less-than-equal-to").
+(string<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than").
+(string<? s1 s2 ... ) -> bool?
+Compares strings for equality.
+(string=? string1 string2 ...) -> bool?
+Compares strings lexicographically (as in"greater-than-or-equal").
+(string>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than").
+(string>? s1 s2 ... ) -> bool?
+Creates a substring slicing the characters between two indices.
+(substring str start end) -> string?
+(substring "hello" 1 4) ;; => "ell"
+(substring "hello" 10 15) ;; => error
+
+Returns the first n elements of the list l as a new list.
+(take l n) -> list?
+> (take '(1 2 3 4) 2) ;; => '(0 1)
+> (take (range 0 10) 4) ;; => '(0 1 2 3)
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Check if the given thread is finished running.
+Interrupts the thread. Note, this will not interrupt any native code +that is potentially running in the thread, and will attempt to block +at the next bytecode instruction that is running.
+Block until this thread finishes.
+Resume a suspended thread. This does nothing if the thread is already joined.
+Suspend the thread. Note, this will not interrupt any native code that is +potentially running in the thread, and will attempt to block at the next +bytecode instruction that is running.
+Sleeps the thread for a given number of milliseconds.
+(time/sleep-ms ms)
+Concatenates all of the inputs to their string representation, separated by spaces.
+(to-string xs ...)
+> (to-string 10) ;; => "10"
+> (to-string 10 20) ;; => "10 20"
+> (to-string "hello" "world") ;; => "hello world"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Returns a new string with the given pat
repeatedly removed from the end
+of the string
(trim-end-matches string? string?) -> string?
+
+> (trim-end-matches "123foo1bar123123" "123") ;; => "123foo1bar"
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Returns a new string with the given pat
repeatedly removed from the start
+of the string
(trim-start-matches string? string?) -> string?
+
+> (trim-start-matches "123foo1bar123123" "123") ;; => "foo1bar123123"
+
+Alias of bytes->string/utf8
.
Serializes a Steel value into a string.
+(value->jsexpr-string any/c) -> string?
+(value->jsexpr-string `(,(hash "foo" #t))) ;; => "[{\"foo\":true}]"
+
+The void value, returned by many forms with side effects, such as define
.
Writes a single byte to an output port.
+(write-byte b [port])
+Writes the contents of a bytevector into an output port.
+(write-bytes buf [port])
+Checks if the given real number is zero.
+(zero? num) -> boolean?
+> (zero? 0) ;; => #t
+> (zero? 0.0) ;; => #t
+> (zero? 0.1) ;; => #f
+
+Returns #t
if the given value is a byte, meaning an exact
+integer between 0 and 255 inclusive, #f
otherwise.
(byte? 65) ;; => #t
+(byte? 0) ;; => #t
+(byte? 256) ;; => #f
+(byte? 100000) ;; => #f
+(byte? -1) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytes b ...)
+(bytes 65 112 112 108 101)
+
+Converts the bytevector to the equivalent list representation.
+(bytes->list (bytes 0 1 2 3 4 5)) ;; => '(0 1 2 3 4 5)
+
+Decodes a string from a bytevector containing valid UTF-8.
+(bytes->string/utf8 buf [start] [end]) -> string?
+(bytes->string/utf8 (bytes #xe5 #x8d #x83 #xe8 #x91 #x89)) ;; => "千葉"
+
+Append multiple byte vectors into a new bytevector.
+(bytes-append #u8(0 1 2) #u8(3 4 5)) ;; => #u8(#x00 #x01 #x02 #x03 #x04 #x05)
+
+(bytes-append #u8(0) #u8(1) #u8() #u8(2)) ;; => #u8(#x00 #x01 #x02)
+
+Returns the length of the given byte vector
+(bytes-length (bytes 1 2 3 4 5)) ;; => 5
+
+Fetches the byte at the given index within the bytevector. +If the index is out of bounds, this will error.
+(bytes-ref vector index)
+(bytes-ref (bytes 0 1 2 3 4 5) 3) ;; => 4
+(bytes-ref (bytes) 10) ;; error
+
+Sets the byte at the given index to the given byte. Will error +if the index is out of bounds.
+(bytes-set! vector index byte)
+(define my-bytes (bytes 0 1 2 3 4 5))
+(bytes-set! my-bytes 0 100)
+(bytes-ref my-bytes 0) ;; => 100
+
+Returns #t
if this value is a bytevector
(bytes? (bytes 0 1 2)) ;; => #t
+(bytes? (list 10 20 30)) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytevector b ...)
+(bytevector 65 112 112 108 101)
+
+Creates a copy of a bytevector.
+(bytevector-copy vector [start end]) -> bytes?
+(define vec (bytes 1 2 3 4 5))
+
+(bytevector-copy vec) ;; => (bytes 1 2 3 4 5)
+(bytevector-copy vec 1 3) ;; => (bytes 2 3)
+
+Converts the list of bytes to the equivalent bytevector representation.
+The list must contain only values which satisfy the byte?
predicate,
+otherwise this function will error.
(list->bytes (list 0 1 2 3 4 5)) ;; => (bytes 0 1 2 3 4 5)
+
+Creates a bytevector given a length and a default value.
+(make-bytes len default) -> bytes?
+(make-bytes 6 42) ;; => (bytes 42 42 42 42 42)
+
+Alias of bytes->string/utf8
.
Miscellaneous constants
+The void value, returned by many forms with side effects, such as define
.
Filesystem functions, mostly just thin wrappers around the std::fs
functions in
+the Rust std library.
Returns canonical path with all components normalized
+Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Change the current working directory
+Deletes the directory
+Deletes the file
+Gets the filename for a given path
+Gets the parent directory name for a given path
+Checks if a path is a directory
+Checks if a path is a file
+Gets the extension from a path
+Checks if a path exists
+Returns the contents of the directory as a list
+ +Creates an immutable hash table with each given key
mapped to the following val
.
+Each key must have a val, so the total number of arguments must be even.
(hash key val ...) -> hash?
+key : hashable? +val : any/c
+Note: the keys must be hashable.
+> (hash 'a 10 'b 20)",
+r#"=> #<hashmap {
+'a: 10,
+'b: 20,
+}>"#,
+
+Clears the entries out of the existing hashmap. +Will attempt to reuse the existing memory if there are no other references +to the hashmap.
+(hash-clear h) -> hash?
+h: hash?
+> (hash-clear (hash 'a 10 'b 20))
+=> '#hash()
+
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Checks whether the hash map is empty
+(hash-empty? m) -> bool?
+m: hash?
+> (hash-empty? (hash 'a 10)) ;; => #f
+> (hash-emptY? (hash)) ;; => #true
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Returns the keys of the given hash map as a list.
+(hash-keys->list map) -> (listof hashable?)
+
+> (hash-keys->list? (hash 'a 'b 20)) ;; => '(a b)
+
+Returns the keys of the given hash map as an immutable vector
+(hash-keys->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> ['a 'b]",
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-ref (hash 'a 10 'b 20) 'b) ;; => 20
+
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Constructs the union of two hashmaps, keeping the values +in the left map when the keys exist in both maps.
+Will reuse memory where possible.
+(hash-union l r) -> hash?
+> (hash-union (hash 'a 10) (hash 'b 20)) ;; => '#hash((a . 10) (b . 20))
+
+Returns the values of the given hash map as a list
+(hash-values->list? map) -> (listof any/c)?
+map: hash?
+> (hash-values->list? (hash 'a 10 'b 20)),
+=> '(10 20)",
+
+Returns the values of the given hash map as an immutable vector
+(hash-values->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> [10 10]",
+
+Checks if the given value is a complex number
+(complex? value) -> boolean?
+> (complex? 3+4i) ;; => #t
+> (complex? 42) ;; => #t
+> (complex? "hello") ;; => #f
+
+Returns #t
if the value is an EOF object.
(eof-object? any/c) -> bool?
+Checks if the given value is an exact integer
+(exact-integer? value) -> boolean?
+> (exact-integer? 42) ;; => #t
+> (exact-integer? -42) ;; => #t
+> (exact-integer? 4.0) ;; => #f
+
+Checks if the given value is a floating-point number
+(float? value) -> boolean?
+> (float? 42) ;; => #f
+> (float? 3.14) ;; => #t
+> (float? #t) ;; => #f
+
+Checks if the given value is an integer, an alias for integer?
(int? value) -> boolean?
+> (int? 42) ;; => #t
+> (int? 3.14) ;; => #f
+> (int? "hello") ;; => #f
+
+Checks if the given value is an integer, an alias for int?
(integer? value) -> boolean?
+> (integer? 42) ;; => #t
+> (integer? 3.14) ;; => #f
+> (integer? "hello") ;; => #f
+
+Checks if the given value is a number
+(number? value) -> boolean?
+> (number? 42) ;; => #t
+> (number? "hello") ;; => #f
+> (number? 'symbol) ;; => #f
+
+Returns #t if obj is a rational number, #f otherwise. +Rational numbers are numbers that can be expressed as the quotient of two numbers. +For example, 3/4, -5/2, 0.25, and 0 are rational numbers.
+(rational? value) -> bool?
+Examples:
+> (rational? (/ 0.0)) ;; => #f
+> (rational? 3.5) ;; => #t
+> (rational? 6/10) ;; => #t
+> (rational? +nan.0) ;; => #f
+
+Checks if the given value is a real number
+(real? value) -> boolean?
+> (real? 42) ;; => #t
+> (real? 3+4i) ;; => #f
+> (real? "hello") ;; => #f
+
+Pushes a value to the back of the vector, returning a new vector.
+De/serialization from/to JSON.
+Deserializes a JSON string into a Steel value.
+(string->jsexpr json) -> any/c
+(string->jsexpr "{\"foo\": [3]}") ;; => '#hash((foo . (3)))
+
+Serializes a Steel value into a string.
+(value->jsexpr-string any/c) -> string?
+(value->jsexpr-string `(,(hash "foo" #t))) ;; => "[{\"foo\":true}]"
+
+
+ Lists in Steel have an interface that matches those of classic schemes or lisps. +At face value, they appear to be implemented as cons cells - however, under the hood +they are actually implemented as unrolled linked lists.
+This means that for most practical purposes, interaction with lists is the same. +That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.
+Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.
+(list 10 20 30 40) ;; => '(10 20 30 40)
+
+Appends the given lists together. If provided with no lists, will return the empty list.
+(append lst ...)
+lst : list?
+> (append (list 1 2) (list 3 4)) ;; => '(1 2 3 4)
+> (append) ;; => '()
+
+Applies the given function
with arguments as the contents of the list
.
(apply function lst) -> any?
+> (apply + (list 1 2 3 4)) ;; => 10
+> (apply list (list 1 2 3 4)) ;; => '(1 2 3 4)
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Returns the rest of the list. Will raise an error if the list is empty.
+(cdr l) -> list?
+> (cdr (list 10 20 30)) ;; => '(20 30)
+> (cdr (list 10)) ;; => '()
+> (cdr '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (cdr '())
+│ ^^^ cdr expects a non empty list
+
+Returns a newly allocated list whose first element is a
and second element is d
.
(cons a d) -> list?
+> (cons 1 2) ;; => '(1 . 2)
+> (cons 1 '()) ;; => '(1)
+
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Returns the last element in the list. Takes time proportional to the length of the list.
+(last l) -> any/c
+> (list (list 1 2 3 4)) ;; => 4
+
+Returns the length of the list.
+(length l) -> int?
+> (length (list 10 20 30)) ;; => 3
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the +time complexity is O(n/64). Meaning, for small lists this can be constant.
+(list-ref lst index) -> list?
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Checks if the given value can be treated as a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Returns the rest of the list. Will raise an error if the list is empty.
+(rest l) -> list?
+> (rest (list 10 20 30)) ;; => '(20 30)
+> (rest (list 10)) ;; => '()
+> (rest (list 10))
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (rest '())
+│ ^^^^ rest expects a non empty list
+
+Returns a list that has the same elements as lst
, but in reverse order.
+This function takes time proportional to the length of lst
.
(reverse lst) -> list?
+> (reverse (list 1 2 3 4)) ;; '(4 3 2 1)
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **take**
+Returns the first n elements of the list l as a new list.
+
+(take l n) -> list?
+
+* l : list?
+* n : (and/c positive? int?)
+
+#### Examples
+
+```scheme
+> (take '(1 2 3 4) 2) ;; => '(0 1)
+> (take (range 0 10) 4) ;; => '(0 1 2 3)
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Returns the command line passed to this process, +including the command name as first argument.
+Returns the message of an error object.
+(error-object-message error?) -> string?
+Multiplies the given numbers.
+(* . nums) -> number?
+> (* 5 3) ;; => 15
+> (* 10 3 2) ;; => 60
+> (*) ;; => 1
+
+Adds the given numbers.
+(+ . nums) -> number?
+> (+ 5 3) ;; => 8
+> (+ 10 3 2) ;; => 15
+> (+) ;; => 0
+
+Subtracts the given numbers.
+(- . nums) -> number?
+> (- 5 3) ;; => 2
+> (- 10 3 2) ;; => 5
+> (- -5) ;; => 5
+
+Divides the given numbers.
+(/ . nums) -> number?
+> (/ 10 2) ;; => 5
+> (/ 10 2 2.0) ;; => 2.5
+> (/ 1 3.0) ;; => 0.3333333333333333
+> (/ 1 3) ;; => 1/3
+
+Computes the absolute value of the given number.
+(abs number) -> number?
+> (abs 42) ;; => 42
+> (abs -42) ;; => 42
+> (abs 0) ;; => 0
+
+Rounds the given number up to the nearest integer not less than it.
+(ceiling number) -> integer?
+> (ceiling 42) ;; => 42
+> (ceiling 42.1) ;; => 43
+> (ceiling -42.1) ;; => -42
+
+Retrieves the denominator of the given rational number.
+(denominator number) -> integer?
+> (denominator 1/2) ;; => 2
+> (denominator 3/4) ;; => 4
+> (denominator 4) ;; => 1
+
+Converts an exact number to an inexact number.
+(exact->inexact num) -> number?
+> (exact->inexact 10) ;; => 10
+> (exact->inexact 1/2) ;; => 0.5
+> (exact->inexact 1+2i) ;; => 1+2i
+
+Computes the integer square root of the given non-negative integer.
+(exact-integer-sqrt number) -> (integer? integer?)
+> (exact-integer-sqrt 25) ;; => (5 0)
+> (exact-integer-sqrt 35) ;; => (5 10)
+
+Checks if the given value is exact.
+(exact? val) -> boolean?
+> (exact? 42) ;; => #t
+> (exact? 3.14) ;; => #f
+> (exact? "hello") ;; => #f
+
+Returns Euler’s number raised to the power of z.
+(exp z) -> number?
+> (exp 0) ;; => 1
+> (exp 2) ;; => 7.38905609893065
+> (exp 1.5) ;; => 4.4816890703380645
+
+Raises the left operand to the power of the right operand.
+(expt base exponent) -> number?
+> (expt 2 3) ;; => 8
+> (expt 2.0 0.5) ;; => 1.4142135623730951
+> (expt 9 0.5) ;; => 3
+
+Returns #t
if the given number is finite.
(finite? number) -> boolean?
+> (finite? 42) ;; => #t
+> (finite? 0.1) ;; => #t
+> (finite? +inf.0) ;; => #f
+> (finite? -inf.0) ;; => #f
+> (finite? +nan.0) ;; => #f
+
+Computes the largest integer less than or equal to the given number.
+(floor number) -> number?
+> (floor 3.14) ;; => 3
+> (floor 4.99) ;; => 4
+> (floor -2.5) ;; => -3
+
+Converts an inexact number to an exact number.
+(inexact->exact num) -> number?
+> (inexact->exact 10.0) ;; => 10
+> (inexact->exact 1.5) ;; => 3/2
+> (inexact->exact 1.5+2.5i) ;; => 3/2+5/2i
+
+Checks if the given value is inexact.
+(inexact? val) -> boolean?
+> (inexact? 42) ;; => #f
+> (inexact? 3.14) ;; => #t
+
+Returns #t
if the given number is infinite.
(infinite? number) -> boolean?
+> (infinite? 42) ;; => #f
+> (infinite? -nan.0) ;; => #f
+> (infinite? +inf.0) ;; => #t
+
+Computes the natural logarithm of the given number.
+(log number [base]) -> number?
+> (log 10) ;; => 2.302585092994046
+> (log 100 10) ;; => 2
+> (log 27 3) ;; => 3
+
+Computes the magnitude of the given number.
+(magnitude number) -> number?
+> (magnitude 3+4i) ;; => 5
+> (magnitude 5) ;; => 5
+> (magnitude -5) ;; => 5
+
+Returns #t
if the real number is Nan.
(nan? value) -> boolean?
+(nan? +nan.0) => #t
+(nan? 100000) => #f
+
+Checks if the given real number is negative.
+(negative? num) -> boolean?
+> (negative? 0) ;; => #f
+> (negative? 1) ;; => #f
+> (negative? -1) ;; => #t
+
+Retrieves the numerator of the given rational number.
+(numerator number) -> number?
+> (numerator 3/4) ;; => 3
+> (numerator 5/2) ;; => 5
+> (numerator -2) ;; => -2
+
+Checks if the given real number is positive.
+(positive? num) -> boolean?
+> (positive? 0) ;; => #f
+> (positive? 1) ;; => #t
+> (positive? -1) ;; => #f
+
+Returns quotient of dividing numerator by denomintator.
+(quotient numerator denominator) -> integer?
+> (quotient 11 2) ;; => 5
+> (quotient 10 2) ;; => 5
+> (quotient -10 2) ;; => -5
+
+Rounds the given number to the nearest integer.
+(round number) -> number?
+> (round 3.14) ;; => 3
+> (round 4.6) ;; => 5
+> (round -2.5) ;; => -3
+
+Computes the square root of the given number.
+(sqrt number) -> number?
+> (sqrt 4) ;; => 2
+> (sqrt 2) ;; => 1.4142135623730951
+> (sqrt -1) ;; => 0+1i
+
+Computes the square of the given number.
+(square number) -> number?
+> (square 5) ;; => 25
+> (square -3) ;; => 9
+> (square 2.5) ;; => 6.25
+
+Checks if the given real number is zero.
+(zero? num) -> boolean?
+> (zero? 0) ;; => #t
+> (zero? 0.0) ;; => #t
+> (zero? 0.1) ;; => #f
+
+Real numbers ordering module.
+Compares real numbers to check if any number is less than the subsequent.
+(< x . rest) -> bool?
+> (< 1) ;; => #t
+> (< 3 2) ;; => #f
+> (< 2 3) ;; => #t
+> (< 3/2 1.5) ;; => #f
+> (< 2.5 3/2) ;; => #t
+> (< 2 5/2 3) ;; #t
+
+Compares real numbers to check if any number is less than or equal than the subsequent.
+(<= x . rest) -> bool?
+> (<= 1) ;; => #t
+> (<= 3 2) ;; => #f
+> (<= 2 3) ;; => #t
+> (<= 3/2 1.5) ;; => #t
+> (<= 2.5 3/2) ;; => #f
+> (<= 2 6/2 3) ;; #t
+
+Compares real numbers to check if any number is greater than the subsequent.
+(> x . rest) -> bool?
+> (> 1) ;; => #t
+> (> 3 2) ;; => #t
+> (> 1 1) ;; => #f
+> (> 3/2 1.5) ;; => #f
+> (> 3/2 1.4) ;; => #t
+> (> 3 4/2 1) ;; #t
+
+Compares real numbers to check if any number is greater than or equal than the subsequent.
+(>= x . rest) -> bool?
+> (>= 1) ;; => #t
+> (>= 3 2) ;; => #t
+> (>= 2 3) ;; => #f
+> (>= 3/2 1.5) ;; => #t
+> (>= 3/2 1.4) ;; => #t
+> (>= 2 4/2 1) ;; #t
+
+
+ Returns an EOF object.
+(eof-object) -> eof-object?
+Extracts the contents from a port created with open-output-bytevector
.
(get-output-bytevector port?) -> bytes?
+Extracts the string contents from a port created with open-output-string
.
(get-output-string port?) -> string?
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Creates an input port from a bytevector, that will return the bytevector contents.
+(open-input-bytevector bytes?) -> input-port?
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Creates an input port from a string, that will return the string contents.
+(open-input-string string?) -> input-port?
+Creates an output port that accumulates what is written into a bytevector.
+These bytes can be recovered calling get-output-bytevector
.
(open-output-bytevector) -> output-port?
+(define out (open-output-bytevector))
+
+
+(write-byte 30 out)
+(write-byte 250 out)
+
+(get-output-bytevector out) ;; => (bytes 30 250)
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Creates an output port that accumulates what is written into a string.
+This string can be recovered calling get-output-string
.
(open-output-string) -> output-port?
+(define out (open-output-string))
+
+
+(write-char "α" out)
+(write-char "ω" out)
+
+(get-output-string out) ;; => "αω"
+
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Peeks the next byte from an input port.
+(peek-byte [port]) -> byte?
+Reads a single byte from an input port.
+(read-byte [port]) -> byte?
+Reads the next character from an input port.
+(read-char [port]) -> char?
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Writes a single byte to an output port.
+(write-byte b [port])
+Writes the contents of a bytevector into an output port.
+(write-bytes buf [port])
+Strings in Steel are immutable, fixed length arrays of characters. They are heap allocated, and
+are implemented under the hood as referenced counted Rust Strings
. Rust Strings
are stored
+as UTF-8 encoded bytes.
Returns the Unicode codepoint of a given character.
+(char->integer char?) -> integer?
+Attemps to convert the character into a decimal digit,
+and returns #f
on failure.
Returns #t
if the character is a decimal digit.
Returns the lower case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns the upper case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns #t
if the character is a whitespace character.
Compares characters according to their codepoints, in a "less-than-or-equal" fashion.
+(char<=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "less-than" fashion.
+(char<? char1 char2 ... ) -> bool?
+Checks if all characters are equal.
+Requires that all inputs are characters, and will otherwise raise an error.
+(char=? char1 char2 ...) -> bool?
+Compares characters according to their codepoints, in a "greater-than-or-equal" fashion.
+(char>=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "greater-than" fashion.
+(char>? char1 char2 ... ) -> bool?
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Returns the character corresponding to a given Unicode codepoint.
+(integer->char integer?) -> char?
+Creates a string of a given length, filled with an optional character
+(which defaults to #\0
).
(make-string len [char]) -> string?
+Converts the given number to a string.
+Splits a string given a separator pattern into a list of strings.
+(split-many str pat) -> (listof string?)
+(split-many "foo,bar,baz" ",") ;; => '("foo" "bar" "baz")
+(split-many "foo|bar|" "|") ;; => '("foo" "bar" "")
+(split-many "" "&") ;; => '("")
+
+Splits a string given a separator at most once, yielding +a list with at most 2 elements.
+(split-once str pat) -> string?
+(split-once "foo,bar,baz" ",") ;; => '("foo" "bar,baz")
+(split-once "foo|bar|" "|") ;; => '("foo" "bar|")
+(split-once "" "&") ;; => '("")
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Constructs a string from the given characters
+Encodes a string as UTF-8 into a bytevector.
+(string->bytes string?) -> bytes?
+(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Converts a string into a list of characters.
+(string->list s [start] [end]) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Creates a new lowercased version of the input string
+(string->lower string?) -> string?
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Converts the given string to a number, with an optional radix.
+On failure, it returns #f
(string->number digits [radix]) -> (or/c number? boolean?)
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Alias of string->bytes
.
Returns a vector containing the characters of a given string
+(string->vector string?) -> vector?
+(string->vector "hello") ;; => '#(#\h #\e #\l #\l #\o)
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+Compares strings lexicographically (as in"less-than-or-equal"), +in a case insensitive fashion.
+(string-ci<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than"), +in a case insensitive fashion.
+(string-ci<? s1 s2 ... ) -> bool?
+Compares strings for equality, in a case insensitive fashion.
+Compares strings lexicographically (as in"greater-than-or-equal"), +in a case insensitive fashion.
+(string-ci>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than"), +in a case insensitive fashion.
+(string-ci>? s1 s2 ... ) -> bool?
+Searches a string to check if it contains the second argument.
+(string-contains? string? string?) -> bool?
+(string-contains? "hello" "lo") ;;=> #t
+(string-contains? "hello" "world") ;;=> #f
+
+Joins the given list of strings, with an optional separator.
+(string-join strings [sep]) -> string?
+(string-join '("a" "b" "c")) ;; => "abc"
+(string-join '("one" "two" "three") ", ") ;; => "one, two, three"
+
+Get the length of the given string in UTF-8 bytes.
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+> (string-length "✅") ;; => 3
+> (string-length "🤖") ;; => 4
+
+Extracts the nth character out of a given string.
+(string-ref str n) -> char?
+Replaces all occurrences of a pattern into the given string
+(string-replace str from to) -> string?
+(string-replace "hello world" "o" "@") ;; => "hell@ w@rld"
+
+Compares strings lexicographically (as in"less-than-equal-to").
+(string<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than").
+(string<? s1 s2 ... ) -> bool?
+Compares strings for equality.
+(string=? string1 string2 ...) -> bool?
+Compares strings lexicographically (as in"greater-than-or-equal").
+(string>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than").
+(string>? s1 s2 ... ) -> bool?
+Creates a substring slicing the characters between two indices.
+(substring str start end) -> string?
+(substring "hello" 1 4) ;; => "ell"
+(substring "hello" 10 15) ;; => error
+
+Concatenates all of the inputs to their string representation, separated by spaces.
+(to-string xs ...)
+> (to-string 10) ;; => "10"
+> (to-string 10 20) ;; => "10 20"
+> (to-string "hello" "world") ;; => "hello world"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Returns a new string with the given pat
repeatedly removed from the end
+of the string
(trim-end-matches string? string?) -> string?
+
+> (trim-end-matches "123foo1bar123123" "123") ;; => "123foo1bar"
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Returns a new string with the given pat
repeatedly removed from the start
+of the string
(trim-start-matches string? string?) -> string?
+
+> (trim-start-matches "123foo1bar123123" "123") ;; => "foo1bar123123"
+
+
+ Contains direct wrappers around the Rust std::time::Instant
and std::time::Duration
modules.
+For example, to measure the time something takes:
(define t (instant/now))
+(displayln "Hello world")
+(displayln (instant/elapsed t))
+
+Returns the number of milliseconds since the Unix epoch as an inexact number.
+(current-inexact-milliseconds) -> inexact?
+Returns the number of milliseconds since the Unix epoch as an integer.
+(current-milliseconds) -> int?
+Returns the number of seconds since the Unix epoch as an integer.
+(current-second) -> int?
+Returns a string representation of a duration
+(duration->string dur)
+Returns the local time in the format given by the input string (using chrono::Local::format
).
(local-time/now! fmt) -> string?
+Sleeps the thread for a given number of milliseconds.
+(time/sleep-ms ms)
+Rust values, types, and functions are easily embedded into Steel. Using the register_fn
call, you can embed functions easily:
use steel_vm::engine::Engine;
+use steel_vm::register_fn::RegisterFn;
+
+fn external_function(arg1: usize, arg2: usize) -> usize {
+ arg1 + arg2
+}
+
+fn option_function(arg1: Option<String>) -> Option<String> {
+ arg1
+}
+
+fn result_function(arg1: Option<String>) -> Result<String, String> {
+ if let Some(inner) = arg1 {
+ Ok(inner)
+ } else {
+ Err("Got a none".to_string())
+ }
+}
+
+pub fn main() {
+ let mut vm = Engine::new();
+
+ // Here we can register functions
+ // Any function can accept parameters that implement `FromSteelVal` and
+ // return values that implement `IntoSteelVal`
+ vm.register_fn("external-function", external_function);
+
+ // See the docs for more information about `FromSteelVal` and `IntoSteelVal`
+ // but we can see even functions that accept/return Option<T> or Result<T,E>
+ // can be registered
+ vm.register_fn("option-function", option_function);
+
+ // Result values will map directly to errors in the VM and bubble back up
+ vm.register_fn("result-function", result_function);
+
+ vm.run(
+ r#"
+ (define foo (external-function 10 25))
+ (define bar (option-function "applesauce"))
+ (define baz (result-function "bananas"))
+ "#,
+ )
+ .unwrap();
+
+ let foo = vm.extract::<usize>("foo").unwrap();
+ println!("foo: {}", foo);
+ assert_eq!(35, foo);
+
+ // Can also extract a value by specifying the type on the variable
+ let bar: String = vm.extract("bar").unwrap();
+ println!("bar: {}", bar);
+ assert_eq!("applesauce".to_string(), bar);
+
+ let baz: String = vm.extract("baz").unwrap();
+ println!("baz: {}", baz);
+ assert_eq!("bananas".to_string(), baz);
+}
+We can also embed structs themselves:
+use steel_vm::engine::Engine;
+use steel_vm::register_fn::RegisterFn;
+
+use steel_derive::Steel;
+
+// In order to register a type with Steel,
+// it must implement Clone, Debug, and Steel
+#[derive(Clone, Debug, Steel, PartialEq)]
+pub struct ExternalStruct {
+ foo: usize,
+ bar: String,
+ baz: f64,
+}
+
+impl ExternalStruct {
+ pub fn new(foo: usize, bar: String, baz: f64) -> Self {
+ ExternalStruct { foo, bar, baz }
+ }
+
+ // Embedding functions that take self must take by value
+ pub fn method_by_value(self) -> usize {
+ self.foo
+ }
+
+ // Setters should update the value and return a new instance (functional set)
+ pub fn set_foo(mut self, foo: usize) -> Self {
+ self.foo = foo;
+ self
+ }
+}
+
+pub fn main() {
+ let mut vm = Engine::new();
+
+ // Registering a type gives access to a predicate for the type
+ vm.register_type::<ExternalStruct>("ExternalStruct?");
+
+ // Structs in steel typically have a constructor that is the name of the struct
+ vm.register_fn("ExternalStruct", ExternalStruct::new);
+
+ // register_fn can be chained
+ vm.register_fn("method-by-value", ExternalStruct::method_by_value)
+ .register_fn("set-foo", ExternalStruct::set_foo);
+
+ let external_struct = ExternalStruct::new(1, "foo".to_string(), 12.4);
+
+ // Registering an external value is fallible if the conversion fails for some reason
+ // For instance, registering an Err(T) is fallible. However, most implementation outside of manual
+ // ones should not fail
+ vm.register_external_value("external-struct", external_struct)
+ .unwrap();
+
+ let output = vm
+ .run(
+ r#"
+ (define new-external-struct (set-foo external-struct 100))
+ (define get-output (method-by-value external-struct))
+ (define second-new-external-struct (ExternalStruct 50 "bananas" 72.6))
+ "last-result"
+ "#,
+ )
+ .unwrap();
+
+ let new_external_struct = vm.extract::<ExternalStruct>("new-external-struct").unwrap();
+ println!("new_external_struct: {:?}", new_external_struct);
+ assert_eq!(
+ ExternalStruct::new(100, "foo".to_string(), 12.4),
+ new_external_struct
+ );
+
+ // Can also extract a value by specifying the type on the variable
+ let get_output: usize = vm.extract("get-output").unwrap();
+ println!("get_output: {}", get_output);
+ assert_eq!(1, get_output);
+
+ let second_new_external_struct: ExternalStruct =
+ vm.extract("second-new-external-struct").unwrap();
+ println!(
+ "second_new_external_struct: {:?}",
+ second_new_external_struct
+ );
+ assert_eq!(
+ ExternalStruct::new(50, "bananas".to_string(), 72.6),
+ second_new_external_struct
+ );
+
+ // We also get the output of the VM as the value of every expression run
+ // we can inspect the results just by printing like so
+ println!("{:?}", output);
+}
+Types that implement IntoSteelVal
and FromSteelVal
and be returned and passed into rust functions, respectively. Take the following for example:
+#![allow(unused)] + +fn main() { +fn foo(value: isize) -> String { + ... +} + +}
This means that steel values will attempt to be coerced to the type in the function signature, and the value that this function returns will then attempt to be coerced into a that Steel understands.
+There are some special case conversions that happen specifically:
+IntoSteelVal
Ok(T)
then T else (error E)
Some(T)
then T else #false
How do we define symbols? And more importantly, how do we assign values to them? Let's address these +questions now.
+λ > a
+
+Congratulations, we've defined our first symbol.
+error[E02]: FreeIdentifier
+ ┌─ :1:1
+ │
+1 │ a
+ │ ^ a
+
+Well, not so fast.
+As mentioned earlier in the book, the interpreter evaluates S-expressions that it encounters, and
+the symbol a
is no exception. Since symbol evaluation involves substituting it with a bound value,
+and there is none bound to it, we get an error.
Let's change that.
+define
This is how we bind values to symbols, using (define symbol value)
:
λ > (define a 1337)
+λ > a
+=> 1337
+
+Here, we've created a binding between the symbol a
and a number literal. Consequently, further
+evaluation of a
yields the elite integer. It's worth noting that regardless of whether a
is
+bound and used across different expressions (in separate interpreter entries), it still retains its
+value. Why? Because the Steel session we're in introduces a scope, a context used in evaluation to
+resolve symbol bindings.
Another example:
+λ > (define c (+ 1000 337))
+λ > c
+=> 1337
+
+Although evaluation rules differ for define
(symbol c
is not evaluated, otherwise it would
+result in an error and make no sense), the value part follows standard rules.
let
and local scope define
If define
seems reminiscent of global variables to you, you're not alone. While it's suitable for
+defining function bindings (more on that later) or constants, there must be a better way for local
+bindings.
Coming from Scheme, Steel has two ways to do it: one is a Lisp classic, let
bindings, and the
+other is local scope define
.
let
Allow me to introduce let
, a term familiar from our host language, Rust, with the same meaning.
let
is used as follows: (let ((symbol-a value-a) (symbol-b value-b) ...) body)
.
λ > (let ((a (* 25 4))
+ (b 3))
+ (* a b))
+=> 300
+λ > a
+=> 1337
+
+Three points to note here:
+let
symbol has no function bound to it, and in
+nested forms, only certain things get evaluated. This is the effect macros give us.let
expression.a
became 100, but only within the last expression nested in let
, because it introduced a
+narrower scope. However, it didn't change the previous binding of a
, but it shadowed it. And as
+you can probably guess, b
is not defined beyond let
.define
For the sake of example, we'll use let
once again to define a local scope. However, we won't
+define any symbols with it but use define
within the body of let
(this shall work with
+other ways to define a local scope, but we don't know about them yet, so this one should work just
+enough):
λ > (let ()
+ (define a (+ 100 100))
+ (define b (+ 2 3))
+ (* a b))
+=> 1000
+λ > a
+error[E02]: FreeIdentifier
+ ┌─ :1:1
+ │
+1 │ a
+ │ ^ a
+
+λ > b
+error[E02]: FreeIdentifier
+ ┌─ :1:1
+ │
+1 │ b
+ │ ^ b
+
+As we can see, in Steel, define
inside of a local scope could be used instead of let
bindings,
+and it won't alter the outer scope.
A key to understanding any Lisp is grasping the idea of how expressions should be evaluated. +Roughly, the steps are:
+In subsequent chapters, we'll demystify all of it. For now, steps 1 and 2 will be transparent.
+ +We're familiar with two variants of S-expressions: atoms and expressions. We've already seen how +numbers and string literals, which are examples of atoms, are evaluated. Now, let's delve into the +evaluation of expressions.
+Let's revisit the previous expression example:
+(+ 1 3 (- 7 4) 7)
+
+Skipping the reader and macro expansion steps mentioned in the +evaluation chapter, the interpreter will evaluate this expression in the +following steps:
+Step 1: Evaluate all items
++
evaluates to the value behind it, in this case, the value of the addition
+function:λ > +
+=> #<function:+>
+
+More details on symbols will follow in the symbols chapter.
+At this point, the expression is represented as follows (not proper input, but a human-readable +representation of the process):
+(#<function:+> 1 3 (- 7 4) 7)
+
+Step 2: Evaluate all items, level + 1
+Here, we address the nested expression.
+-
also evaluates to a subtraction function value, just like the previous case
+with addition.Expression evaluation follows a scheme where the first (0th, or more aptly, "the head") value +represents a function value. This results in a function call familiar to us. With scheme, we have the +freedom to instruct the interpreter not to evaluate it and leave it as is, but we'll address this +case later in the book.
+Thus, the expression (- 7 4)
transitions from (#<function:-> 7 4)
to 3
due to function application.
Step 3: Returning to level 0
+After reducing the nested expression, we have:
+(#<function:+> 1 3 3 7)
+
+The evaluation of an expression eventually results in a function application unless instructed +otherwise. Here, there's no exception:
+=> 14
+
+
+ Literals are evaluated to themselves: numbers and string literals.
+Let's fire up a Steel interpreter +(or use the online one) +to take a look at some examples.
+In the following code examples, lambda precedes user input, and =>
indicates the evaluation result
+returned from the interpreter.
λ > 5
+=> 5
+
+λ > -1337
+=> -1337
+
+λ > 1.5
+=> 1.5
+
+λ > 0
+=> 0
+
+As we can see, an input text read as a sexp in a variant of an atom made of a number literal is +evaluated to itself. What about string literals?
+λ > "hello"
+=> "hello"
+
+λ > "steel"
+=> "steel"
+
+Nothing unexpected happens, as for characters:
+λ > #\y
+=> #\y
+
+While not very fascinating by itself, it starts to become useful when built upon, as we're about to +see next.
+ +The ideas of Scheme are straightforward and easily combined, resulting in a minimalistic +syntax that may appear excessive at first glance. While the abundance of parentheses can seem +off-putting and archaic, expressions are actually nested lists and atoms, making it a recursive +data structure. This inherent structure enables easy manipulation, allowing for straightforward +metaprogramming and direct AST manipulations with proper editor support.
+Now, let's welcome an S-expression (or sexp
for short):
(+ 1 3 (- 7 4) 7)
+
+As mentioned earlier, its structure is recursive; this particular one is comprised of a top-level +expression and nested atoms, as well as one nested expression. Such a construction can be +manipulated in various ways before being evaluated. Although the list of syntactic rules will +be minimally supplemented later in this book, it already provides a stable foundation to fit the +language in any domain area.
+ +We've previously learned that +
is not merely a function, but rather a symbol representing a
+function. But what exactly is a symbol?
In Scheme, a symbol is a fundamental data type—a sequence of characters, distinct from a string. +Entities are named using symbols rather than strings because symbols serve as identifiers and are +evaluated differently.
+An analogy from Rust can shed light on the concept of symbols. Consider the question "what is a
+in let a = 2;
" from a metaprogramming perspective. In this context, a
can be understood as an
+identifier. Similarly, when invoking a declarative macro like hello!(b)
with a macro definition
+of macro_rules! hello { ($x:ident) => {} }
, it won't raise an error if b
is not defined. In this
+scenario, b
acts akin to a symbol, the one we don't evaluate to any value behind it, but it
+still exists and usable, just on a different level.
Symbols can reference values depending on the context in which they are evaluated. However, in some +cases, symbols are used as themselves and may be compared directly. For example, when defining +a domain-specific language (DSL), certain symbols may act as "operators" or "keywords" without +having a value associated with them. Instead, they impart meaning within the code that processes +the DSL. We'll delve deeper into this topic later in the discussion on macros because, by default, +symbols are evaluated, which may not always align with our intended usage, especially if we assign +them a "keyword" meaning.
+Next, we'll explore how to assign values to symbols to give them something to evaluate to.
+ +Steel uses a combination of reference counting and a mark and sweep garbage collector in order to manage memory. Important notes regarding this:
+All immutable values are reference counted, this includes built in data structures such as:
+All primitive types are unboxed, these include:
+#<void>
typeMutable values are transformed into boxes
- which are mutable memory locations managed by the garbage collector.
Mutable vectos are a special form handled by the garbage collector as well.
+Mutable structs, are immutable vectors with boxes in each slot for each mutable memory location.
+Heap allocated boxes are weak references to a reference counted pointer living in the heap. Thus, even memory managed by the garbage collector is also reference counted. This means that unboxing a mutable memory location requires two pointer jumps - first the weak pointer to the heap, then the strong pointer to the underlying value.
+One consequence of using reference counted variables is that there will be a non trivial amount of time spent performing reference count operations on values coming and going from the stack. The steel compiler and vm performs a few optimizations to reduce the reference counting thrash, and also to improve the usage of the functional data structures built in.
+Consider the following:
+
+(define (reverse input output)
+ (if (null? input)
+ output
+ (reverse (cdr input) (cons (car input) output))))
+
+(reverse (list 10 20 30 40) '()) ;; => (list 40 30 20 10)
+
+
+This is a simple function that takes an input list and reverses it. It does so recursively, calling reverse
in tail position, meaning we'll get a tail call optimization and this gets converted into a JUMP
in the VM.
Lists in Steel aren't your classic cons cells - they're implemented more like unrolled linked lists or vlists - meaning they're more like chunks of large contiguous vectors strung together. Copying those on each write to it would be silly, so the compiler analyzes a function and attempts to find the last usage of variable. For every last usage of a variable, the VM doesn't just copy the value from the stack, it actually moves it off of the VM stack - meaning the reference count has the potential to be 1 by the time it hits the last usage of a variable.
+When the reference count is 1, we can perform an in-place mutation of the resulting list - which gives us a nice performance win! So in the above snippet, we see the following bytecode:
+0 SDEF : 0 reverse
+1 PUREFUNC : 21 lambda
+2 PASS : 0
+3 PASS : 256
+4 READLOCAL0 : 0 input
+5 CALLGLOBAL : 155 null?
+6 FUNC : 1 null?
+7 IF : 6
+8 READLOCAL1 : 1 output
+9 JMP : 17
+10 READLOCAL0 : 0 input
+11 CALLGLOBAL : 86 cdr
+12 FUNC : 1 cdr
+13 MOVEREADLOCAL0 : 0 input ;; <- Last usage of input
+14 CALLGLOBAL : 92 car
+15 FUNC : 1 car
+16 MOVEREADLOCAL1 : 1 output ;; <- last usage of output
+17 CALLGLOBAL : 94 cons
+18 FUNC : 2 cons
+19 TCOJMP : 2 reverse
+20 PASS : 0
+21 POPPURE : 2
+22 ECLOSURE : 2
+23 EDEF : 0
+24 BIND : 975 reverse
+25 VOID : 0
+26 POPPURE : 0
+
+Which corresponds to these call sites:
+
+(define (reverse input output)
+ (if (null? input)
+ output
+ (reverse (cdr input) (cons (car input) output))))
+ ^^^^^ ^^^^^^
+
+In this case, the mutation of output
is great - since we can cons
onto the list and this under the hood is just a push onto the underlying vector. However, for input it doesn't mean much; car
is a reference operation and has no optimizations associated with it. The compiler isn't quite smart enough to do this yet, but rewriting this slightly, we can get the optimization we want:
+(define (reverse input output)
+ (if (null? input)
+ output
+ (let ([first-element (car input)])
+ (reverse (cdr input) (cons first-element output)))))
+
+
+Which results in this bytecode:
+0 SDEF : 0 reverse
+1 PUREFUNC : 24 lambda
+2 PASS : 0
+3 PASS : 256
+4 READLOCAL0 : 0 input
+5 CALLGLOBAL : 156 null?
+6 FUNC : 1 null?
+7 IF : 6
+8 READLOCAL1 : 1 output
+9 JMP : 20
+10 BEGINSCOPE : 0
+11 READLOCAL0 : 0 input
+12 CALLGLOBAL : 98 car
+13 FUNC : 1 car
+14 MOVEREADLOCAL0 : 0 input ;; <- Here
+15 CALLGLOBAL : 97 cdr
+16 FUNC : 1 cdr
+17 READLOCAL2 : 2 first-element
+18 MOVEREADLOCAL1 : 1 output ;; <- Here
+19 CALLGLOBAL : 86 cons
+20 FUNC : 2 cons
+21 TCOJMP : 2 reverse
+22 PASS : 0
+23 LETENDSCOPE : 2
+24 POPPURE : 2
+25 ECLOSURE : 2
+26 EDEF : 0
+27 BIND : 975 reverse
+28 VOID : 0
+29 POPPURE : 0
+
+
+cdr
when coupled with a unique list will just move the view of the underlying storage over one element - the element is still there - but we don't see it, and it means we don't have to allocate a new list (since in this new world, cdr
can allocate). But - we get some nice cache locality for this!
The first example takes 123 ms to reverse a list of 100000 - whereas the second example takes only 23 ms! In racket, the equivalent code takes somewhere between 2 and 5 ms.
+Another toy example of this optimization can be seen here, comparing Steel to Racket:
+
+(define values-to-insert
+ (map (lambda (n)
+ (cons (number->string n) n)) (range 0 10000)))
+
+(define (hash-insert-loop-test hmap values)
+ (if (null? values)
+ hmap
+ (let ([p (car values)])
+ (hash-insert-loop-test (hash-insert hmap (car p) (cdr p)) (cdr values)))))
+
+(hash-insert-loop-test (hash) values-to-insert)
+
+This snippet takes about 10-20 ms on my machine, and the equivalent body of code in Racket (replacing hash-insert
with hash-set
) takes about 8 ms. So this is a nice win. Without this optimization for hash maps, the same code took 5 seconds!
(log! level arg-list)
+
+Log directly on the specified level the with arguments, as a list
+(log/info! . args)
+
+Log the arguments using the info target, i.e. log on INFO
+(log/warn! . args)
+
+Log the arguments using the warn target, i.e. log on WARN
+(log/debug! . args)
+
+Log the arguments using the debug target, i.e. log on DEBUG
+(log/error! . args)
+
+Log the arguments using the error target, i.e. log on ERROR
+(log/trace! . args)
+
+Log the arguments using the trace target, i.e. log on TRACE
+ +Welcome to "The Steel Book" — a guide for a dynamic and versatile programming language +designed to harmonize with Rust, known as Steel. Born from the Lisp family and descended from +Scheme, Steel offers a blend of dynamic expressiveness within Rust's strict environment, creating a +powerful synergy.
+But why choose a Lisp dialect as an extension language?
+Rust provides robustness and safety, ensuring reliable and efficient software development. However, +its rigidity can sometimes feel limiting when facing dynamic and evolving requirements, such as +those encountered in a text editor, a crucial tool that developers tailor to their needs. Enter +Lisp — a language family renowned for its extensibility and dynamic nature. It serves as the +ultimate extension language, designed to be extendable itself by allowing developers to create DSLs +and tweak the evaluation process to fit any domain or task, even those unforeseen or irrelevant to +the original application.
+In "The Steel Book," our aim is twofold: to demystify language basics for developers unfamiliar +with Scheme intricacies and to teach about Steel in particular. That said, no prior Scheme +experience is needed; we've got you covered.
+ +Welcome to "The Steel Book" — a guide for a dynamic and versatile programming language +designed to harmonize with Rust, known as Steel. Born from the Lisp family and descended from +Scheme, Steel offers a blend of dynamic expressiveness within Rust's strict environment, creating a +powerful synergy.
+But why choose a Lisp dialect as an extension language?
+Rust provides robustness and safety, ensuring reliable and efficient software development. However, +its rigidity can sometimes feel limiting when facing dynamic and evolving requirements, such as +those encountered in a text editor, a crucial tool that developers tailor to their needs. Enter +Lisp — a language family renowned for its extensibility and dynamic nature. It serves as the +ultimate extension language, designed to be extendable itself by allowing developers to create DSLs +and tweak the evaluation process to fit any domain or task, even those unforeseen or irrelevant to +the original application.
+In "The Steel Book," our aim is twofold: to demystify language basics for developers unfamiliar +with Scheme intricacies and to teach about Steel in particular. That said, no prior Scheme +experience is needed; we've got you covered.
+ +Welcome to "The Steel Book" — a guide for a dynamic and versatile programming language +designed to harmonize with Rust, known as Steel. Born from the Lisp family and descended from +Scheme, Steel offers a blend of dynamic expressiveness within Rust's strict environment, creating a +powerful synergy.
+But why choose a Lisp dialect as an extension language?
+Rust provides robustness and safety, ensuring reliable and efficient software development. However, +its rigidity can sometimes feel limiting when facing dynamic and evolving requirements, such as +those encountered in a text editor, a crucial tool that developers tailor to their needs. Enter +Lisp — a language family renowned for its extensibility and dynamic nature. It serves as the +ultimate extension language, designed to be extendable itself by allowing developers to create DSLs +and tweak the evaluation process to fit any domain or task, even those unforeseen or irrelevant to +the original application.
+In "The Steel Book," our aim is twofold: to demystify language basics for developers unfamiliar +with Scheme intricacies and to teach about Steel in particular. That said, no prior Scheme +experience is needed; we've got you covered.
+The ideas of Scheme are straightforward and easily combined, resulting in a minimalistic +syntax that may appear excessive at first glance. While the abundance of parentheses can seem +off-putting and archaic, expressions are actually nested lists and atoms, making it a recursive +data structure. This inherent structure enables easy manipulation, allowing for straightforward +metaprogramming and direct AST manipulations with proper editor support.
+Now, let's welcome an S-expression (or sexp
for short):
(+ 1 3 (- 7 4) 7)
+
+As mentioned earlier, its structure is recursive; this particular one is comprised of a top-level +expression and nested atoms, as well as one nested expression. Such a construction can be +manipulated in various ways before being evaluated. Although the list of syntactic rules will +be minimally supplemented later in this book, it already provides a stable foundation to fit the +language in any domain area.
+A key to understanding any Lisp is grasping the idea of how expressions should be evaluated. +Roughly, the steps are:
+In subsequent chapters, we'll demystify all of it. For now, steps 1 and 2 will be transparent.
+Literals are evaluated to themselves: numbers and string literals.
+Let's fire up a Steel interpreter +(or use the online one) +to take a look at some examples.
+In the following code examples, lambda precedes user input, and =>
indicates the evaluation result
+returned from the interpreter.
λ > 5
+=> 5
+
+λ > -1337
+=> -1337
+
+λ > 1.5
+=> 1.5
+
+λ > 0
+=> 0
+
+As we can see, an input text read as a sexp in a variant of an atom made of a number literal is +evaluated to itself. What about string literals?
+λ > "hello"
+=> "hello"
+
+λ > "steel"
+=> "steel"
+
+Nothing unexpected happens, as for characters:
+λ > #\y
+=> #\y
+
+While not very fascinating by itself, it starts to become useful when built upon, as we're about to +see next.
+We're familiar with two variants of S-expressions: atoms and expressions. We've already seen how +numbers and string literals, which are examples of atoms, are evaluated. Now, let's delve into the +evaluation of expressions.
+Let's revisit the previous expression example:
+(+ 1 3 (- 7 4) 7)
+
+Skipping the reader and macro expansion steps mentioned in the +evaluation chapter, the interpreter will evaluate this expression in the +following steps:
+Step 1: Evaluate all items
++
evaluates to the value behind it, in this case, the value of the addition
+function:λ > +
+=> #<function:+>
+
+More details on symbols will follow in the symbols chapter.
+At this point, the expression is represented as follows (not proper input, but a human-readable +representation of the process):
+(#<function:+> 1 3 (- 7 4) 7)
+
+Step 2: Evaluate all items, level + 1
+Here, we address the nested expression.
+-
also evaluates to a subtraction function value, just like the previous case
+with addition.Expression evaluation follows a scheme where the first (0th, or more aptly, "the head") value +represents a function value. This results in a function call familiar to us. With scheme, we have the +freedom to instruct the interpreter not to evaluate it and leave it as is, but we'll address this +case later in the book.
+Thus, the expression (- 7 4)
transitions from (#<function:-> 7 4)
to 3
due to function application.
Step 3: Returning to level 0
+After reducing the nested expression, we have:
+(#<function:+> 1 3 3 7)
+
+The evaluation of an expression eventually results in a function application unless instructed +otherwise. Here, there's no exception:
+=> 14
+
+We've previously learned that +
is not merely a function, but rather a symbol representing a
+function. But what exactly is a symbol?
In Scheme, a symbol is a fundamental data type—a sequence of characters, distinct from a string. +Entities are named using symbols rather than strings because symbols serve as identifiers and are +evaluated differently.
+An analogy from Rust can shed light on the concept of symbols. Consider the question "what is a
+in let a = 2;
" from a metaprogramming perspective. In this context, a
can be understood as an
+identifier. Similarly, when invoking a declarative macro like hello!(b)
with a macro definition
+of macro_rules! hello { ($x:ident) => {} }
, it won't raise an error if b
is not defined. In this
+scenario, b
acts akin to a symbol, the one we don't evaluate to any value behind it, but it
+still exists and usable, just on a different level.
Symbols can reference values depending on the context in which they are evaluated. However, in some +cases, symbols are used as themselves and may be compared directly. For example, when defining +a domain-specific language (DSL), certain symbols may act as "operators" or "keywords" without +having a value associated with them. Instead, they impart meaning within the code that processes +the DSL. We'll delve deeper into this topic later in the discussion on macros because, by default, +symbols are evaluated, which may not always align with our intended usage, especially if we assign +them a "keyword" meaning.
+Next, we'll explore how to assign values to symbols to give them something to evaluate to.
+How do we define symbols? And more importantly, how do we assign values to them? Let's address these +questions now.
+λ > a
+
+Congratulations, we've defined our first symbol.
+error[E02]: FreeIdentifier
+ ┌─ :1:1
+ │
+1 │ a
+ │ ^ a
+
+Well, not so fast.
+As mentioned earlier in the book, the interpreter evaluates S-expressions that it encounters, and
+the symbol a
is no exception. Since symbol evaluation involves substituting it with a bound value,
+and there is none bound to it, we get an error.
Let's change that.
+define
This is how we bind values to symbols, using (define symbol value)
:
λ > (define a 1337)
+λ > a
+=> 1337
+
+Here, we've created a binding between the symbol a
and a number literal. Consequently, further
+evaluation of a
yields the elite integer. It's worth noting that regardless of whether a
is
+bound and used across different expressions (in separate interpreter entries), it still retains its
+value. Why? Because the Steel session we're in introduces a scope, a context used in evaluation to
+resolve symbol bindings.
Another example:
+λ > (define c (+ 1000 337))
+λ > c
+=> 1337
+
+Although evaluation rules differ for define
(symbol c
is not evaluated, otherwise it would
+result in an error and make no sense), the value part follows standard rules.
let
and local scope define
If define
seems reminiscent of global variables to you, you're not alone. While it's suitable for
+defining function bindings (more on that later) or constants, there must be a better way for local
+bindings.
Coming from Scheme, Steel has two ways to do it: one is a Lisp classic, let
bindings, and the
+other is local scope define
.
let
Allow me to introduce let
, a term familiar from our host language, Rust, with the same meaning.
let
is used as follows: (let ((symbol-a value-a) (symbol-b value-b) ...) body)
.
λ > (let ((a (* 25 4))
+ (b 3))
+ (* a b))
+=> 300
+λ > a
+=> 1337
+
+Three points to note here:
+let
symbol has no function bound to it, and in
+nested forms, only certain things get evaluated. This is the effect macros give us.let
expression.a
became 100, but only within the last expression nested in let
, because it introduced a
+narrower scope. However, it didn't change the previous binding of a
, but it shadowed it. And as
+you can probably guess, b
is not defined beyond let
.define
For the sake of example, we'll use let
once again to define a local scope. However, we won't
+define any symbols with it but use define
within the body of let
(this shall work with
+other ways to define a local scope, but we don't know about them yet, so this one should work just
+enough):
λ > (let ()
+ (define a (+ 100 100))
+ (define b (+ 2 3))
+ (* a b))
+=> 1000
+λ > a
+error[E02]: FreeIdentifier
+ ┌─ :1:1
+ │
+1 │ a
+ │ ^ a
+
+λ > b
+error[E02]: FreeIdentifier
+ ┌─ :1:1
+ │
+1 │ b
+ │ ^ b
+
+As we can see, in Steel, define
inside of a local scope could be used instead of let
bindings,
+and it won't alter the outer scope.
The Steel Playground allows you to try Steel out directly from your browser. Visit the Steel Playground at:
+https://mattwparas.github.io/steel-playground/dev/
+The Steel Playground's environment is as follows:
+The output prints the results of all expressions. Additional
+information can be printed out with the display
and displayln
+functions.
Bytecode renders the Bytecode that Steel generates from the Steel +code. The Bytecode is a low level representation of the code that is +executed by the Steel interpretter.
+Raw AST exposes the parsed AST. This expands some macros. For example:
+(define (foo bar)
+ (+ bar bar))
+
+(define baz '(1 2 3))
+
+is actually shorthand for
+(define foo
+ (λ (bar)
+ (+ bar bar)))
+
+(define baz (quote 1 2 3))
+
+This is similar to the Raw AST but provides more detailed information.
+Sometimes you may want to create a Steel module by calling Rust. This can be +useful to:
+Whatever the case, Steel provides facilities to create reusable modules based on
+Rust code. This process involves compiling Rust into a dylib
.
In this guide, we'll make a dylib
that will wrap the sys-info
crate. There
+are roughly 3 steps:
$STEEL_HOME/native
.#%require-dylib
.To start, create a new library using cargo
:
$ cargo new --lib steel-sys-info
+
+This should create a directory structure as follows:
+├── Cargo.toml
+├── src
+│ └── lib.rs
+
+We'll want to make this a cdylib
library for Steel, so we'll perform the following adjustments in Cargo.toml
:
crate-type
to "cdylib"
.steel-core
with the dylibs
feature as a dependency.abi_stable
as a dependency. This is required by some steel-core
+macros.[package]
+name = "steel-sys-info"
+version.workspace = true
+edition = "2021"
+
+[lib]
+name = "steel_sys_info"
+crate-type = ["cdylib"]
+
+[dependencies]
+# I'm running this example based on the `steel-sys-info` library found in the steel repo. If you're
+# running this on your own, use whichever steel version you'd like to target and pin to that.
+steel-core = { workspace = true, features = ["dylibs"] }
+abi_stable = "0.11.1"
+sys-info = "0.9.1"
+
+This means that when we run cargo build
we'll produce a shared library (.so
+file). The shared library can be loaded into other programs, Steel in our case.
For the purposes of this example, we'll create a module that wraps the MemInfo
+struct, and expose the information there. Since we'll be implementing traits
+that are defined inside the steel
crate, we'll need to create a struct to wrap
+the sys_info::MemInfo
struct:
struct MemoryInfo {
+ info: sys_info::MemInfo,
+}
+
+impl MemoryInfo {
+ fn total(&self) -> isize {
+ self.info.total as isize
+ }
+
+ fn avail(&self) -> isize {
+ self.info.avail as isize
+ }
+
+ fn free(&self) -> isize {
+ self.info.free as isize
+ }
+
+ fn buffers(&self) -> isize {
+ self.info.buffers as isize
+ }
+
+ fn cached(&self) -> isize {
+ self.info.cached as isize
+ }
+
+ fn swap_total(&self) -> isize {
+ self.info.swap_total as isize
+ }
+
+ fn swap_free(&self) -> isize {
+ self.info.swap_free as isize
+ }
+}
+Now that we've done that, we can expose this to steel by implementing the
+Custom
type for the struct, and declaring an FFIModule
:
// Using ABI Stable types is very important
+use steel::{
+ declare_module,
+ rvals::Custom,
+ steel_vm::ffi::{FFIModule, RegisterFFIFn},
+};
+
+impl Custom for MemoryInfo {}
+
+declare_module!(create_module);
+
+fn create_module() -> FFIModule {
+ let mut module = FFIModule::new("steel/sys-info");
+
+ module.register_fn("mem-info", || MemoryInfo {
+ info: sys_info::mem_info().unwrap(),
+ });
+
+ module
+ .register_fn("MemoryInfo-total", MemoryInfo::total)
+ .register_fn("MemoryInfo-avail", MemoryInfo::avail)
+ .register_fn("MemoryInfo-free", MemoryInfo::free)
+ .register_fn("MemoryInfo-buffers", MemoryInfo::buffers)
+ .register_fn("MemoryInfo-cached", MemoryInfo::cached)
+ .register_fn("MemoryInfo-swap-total", MemoryInfo::swap_total)
+ .register_fn("MemoryInfo-swap-free", MemoryInfo::swap_free);
+
+ module
+}
+The register_fn
API will perform all of the necessary coercions necessary to
+make this as safe as possible. At the end of the day, this is FFI and we are
+loading shared libraries, so there is some unsafe Rust code, however steel uses
+the underlying abi_stable
library in order to make interactions with the
+shared library as safe as possible.
To install the dylib in a location where the steel
interpreter will find it,
+from the root of the library just run:
$ cargo steel-lib
+
+This will build the crate, and copy the resulting dylib to $STEEL_HOME/native
.
To load the library, use the syntax #%require-dylib
- This operates similary
+to a standard require
, in that all of the modifiers you're used to using work,
+such as only-in
and prefix-in
. However, the argument is no longer the path
+to the library, but rather the name of the library without the extension. By
+default, the library will be named the [lib]
name used in the toml, prefixed
+with lib
.
(#%require-dylib "libsteel_sys_info"
+ (only-in mem-info
+ MemoryInfo-total
+ MemoryInfo-avail
+ MemoryInfo-free
+ MemoryInfo-buffers
+ MemoryInfo-cached
+ MemoryInfo-swap-total
+ MemoryInfo-swap-free))
+
+(provide current-memory-usage memory-usage-as-percentage)
+
+(define (current-memory-usage #:memory-info (memory-info (mem-info)))
+ (- (MemoryInfo-total memory-info) (MemoryInfo-free memory-info) (MemoryInfo-cached memory-info)))
+
+(define (memory-usage-as-percentage #:memory-info (memory-info (mem-info)))
+ (/ (current-memory-usage #:memory-info memory-info) (MemoryInfo-total memory-info)))
+
+
+This can then be installed as a library itself on the machine, and required just
+like any other library, using a cog.scm
file for the manifest.
Steel can be used as a scripting language within a Rust program. You can achieve
+this by creating a Steel Engine
object. The Engine
object allows you to
+execute Scheme code and interact with it from your Rust program. For more
+details about Engine
, see the Engine API.
The Steel Virtual Machine is provided by the steel-core
trait.
[dependencies]
+steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" }
+
+The following example runs a few expressions in a Steel Engine
and asserts
+that the results are as expected.
use steel::steel_vm::engine::Engine;
+use steel::SteelVal;
+
+fn main() {
+ let mut steel_engine = Engine::new();
+ let answer = steel_engine.run(
+ (r#"
+ (+ 1 2 3 4)
+ (+ 5 6 7 8)
+ "#),
+ );
+ assert_eq!(answer, vec![SteelVal::IntV(10), SteelVal::IntV(26)])
+}
+Creates a new engine. The Engine
is used to run Steel Scheme code. Note that
+the Engine
is not Send
or Sync
. This means it is bound to the current
+thread and cannot be shared or sent across other threads.
let mut steel_engine = Engine::new();
+Runs a Steel expression and returns the result as a Vec<SteelVal>
. If any
+error occurs, then Err(SteelErr)
is returned.
let mut steel_engine = Engine::new();
+assert_eq!(steel_engine.run("(+ 1 1)"), Ok(vec![SteelVal::IntV(2)]));
+assert!(steel_engine.run("(+ 1 undefined-identifier)").is_err());
+Repl functionality is provided by the steel-repl
crate.
[dependencies]
+steel-repl = { git="https://github.com/mattwparas/steel.git", branch = "master" }
+
+run_repl
runs the Steel repl until an IO error is encountered or the user
+exits the repl. The repl may be exited by:
(quit)
Steel Scheme function.ctrl+c
or ctrl+d
within the repl.let steel_engine = Engine::new();
+steel_repl::run_repl(steel_engine).unwrap();
+Rust values, types, and functions are easily embedded into Steel. Using the register_fn
call, you can embed functions easily:
use steel_vm::engine::Engine;
+use steel_vm::register_fn::RegisterFn;
+
+fn external_function(arg1: usize, arg2: usize) -> usize {
+ arg1 + arg2
+}
+
+fn option_function(arg1: Option<String>) -> Option<String> {
+ arg1
+}
+
+fn result_function(arg1: Option<String>) -> Result<String, String> {
+ if let Some(inner) = arg1 {
+ Ok(inner)
+ } else {
+ Err("Got a none".to_string())
+ }
+}
+
+pub fn main() {
+ let mut vm = Engine::new();
+
+ // Here we can register functions
+ // Any function can accept parameters that implement `FromSteelVal` and
+ // return values that implement `IntoSteelVal`
+ vm.register_fn("external-function", external_function);
+
+ // See the docs for more information about `FromSteelVal` and `IntoSteelVal`
+ // but we can see even functions that accept/return Option<T> or Result<T,E>
+ // can be registered
+ vm.register_fn("option-function", option_function);
+
+ // Result values will map directly to errors in the VM and bubble back up
+ vm.register_fn("result-function", result_function);
+
+ vm.run(
+ r#"
+ (define foo (external-function 10 25))
+ (define bar (option-function "applesauce"))
+ (define baz (result-function "bananas"))
+ "#,
+ )
+ .unwrap();
+
+ let foo = vm.extract::<usize>("foo").unwrap();
+ println!("foo: {}", foo);
+ assert_eq!(35, foo);
+
+ // Can also extract a value by specifying the type on the variable
+ let bar: String = vm.extract("bar").unwrap();
+ println!("bar: {}", bar);
+ assert_eq!("applesauce".to_string(), bar);
+
+ let baz: String = vm.extract("baz").unwrap();
+ println!("baz: {}", baz);
+ assert_eq!("bananas".to_string(), baz);
+}
+We can also embed structs themselves:
+use steel_vm::engine::Engine;
+use steel_vm::register_fn::RegisterFn;
+
+use steel_derive::Steel;
+
+// In order to register a type with Steel,
+// it must implement Clone, Debug, and Steel
+#[derive(Clone, Debug, Steel, PartialEq)]
+pub struct ExternalStruct {
+ foo: usize,
+ bar: String,
+ baz: f64,
+}
+
+impl ExternalStruct {
+ pub fn new(foo: usize, bar: String, baz: f64) -> Self {
+ ExternalStruct { foo, bar, baz }
+ }
+
+ // Embedding functions that take self must take by value
+ pub fn method_by_value(self) -> usize {
+ self.foo
+ }
+
+ // Setters should update the value and return a new instance (functional set)
+ pub fn set_foo(mut self, foo: usize) -> Self {
+ self.foo = foo;
+ self
+ }
+}
+
+pub fn main() {
+ let mut vm = Engine::new();
+
+ // Registering a type gives access to a predicate for the type
+ vm.register_type::<ExternalStruct>("ExternalStruct?");
+
+ // Structs in steel typically have a constructor that is the name of the struct
+ vm.register_fn("ExternalStruct", ExternalStruct::new);
+
+ // register_fn can be chained
+ vm.register_fn("method-by-value", ExternalStruct::method_by_value)
+ .register_fn("set-foo", ExternalStruct::set_foo);
+
+ let external_struct = ExternalStruct::new(1, "foo".to_string(), 12.4);
+
+ // Registering an external value is fallible if the conversion fails for some reason
+ // For instance, registering an Err(T) is fallible. However, most implementation outside of manual
+ // ones should not fail
+ vm.register_external_value("external-struct", external_struct)
+ .unwrap();
+
+ let output = vm
+ .run(
+ r#"
+ (define new-external-struct (set-foo external-struct 100))
+ (define get-output (method-by-value external-struct))
+ (define second-new-external-struct (ExternalStruct 50 "bananas" 72.6))
+ "last-result"
+ "#,
+ )
+ .unwrap();
+
+ let new_external_struct = vm.extract::<ExternalStruct>("new-external-struct").unwrap();
+ println!("new_external_struct: {:?}", new_external_struct);
+ assert_eq!(
+ ExternalStruct::new(100, "foo".to_string(), 12.4),
+ new_external_struct
+ );
+
+ // Can also extract a value by specifying the type on the variable
+ let get_output: usize = vm.extract("get-output").unwrap();
+ println!("get_output: {}", get_output);
+ assert_eq!(1, get_output);
+
+ let second_new_external_struct: ExternalStruct =
+ vm.extract("second-new-external-struct").unwrap();
+ println!(
+ "second_new_external_struct: {:?}",
+ second_new_external_struct
+ );
+ assert_eq!(
+ ExternalStruct::new(50, "bananas".to_string(), 72.6),
+ second_new_external_struct
+ );
+
+ // We also get the output of the VM as the value of every expression run
+ // we can inspect the results just by printing like so
+ println!("{:?}", output);
+}
+Types that implement IntoSteelVal
and FromSteelVal
and be returned and passed into rust functions, respectively. Take the following for example:
+#![allow(unused)] + +fn main() { +fn foo(value: isize) -> String { + ... +} + +}
This means that steel values will attempt to be coerced to the type in the function signature, and the value that this function returns will then attempt to be coerced into a that Steel understands.
+There are some special case conversions that happen specifically:
+IntoSteelVal
Ok(T)
then T else (error E)
Some(T)
then T else #false
Steel contains a limited form of the syntax-rules
that scheme provides. These macros build on the small primary language constructs that exist. Consider the following:
(define-syntax or
+ (syntax-rules ()
+ [(or) #f]
+ [(or x) x]
+ [(or x y) (let ([z x])
+ (if z z y))]
+ [(or x y ...) (or x (or y ...))]))
+
+(or #f #f #t)
+
+This will actually expand into something like this
+((λ (__z)
+ (if __z __z ((λ (__z) (if __z __z #t)) #f)))
+ #f)
+
+These macros allow for a simple extension of Steel to however you see fit - defining macros in terms of the syntax rules format is fairly straightforward.
+Inspired by Racket's higher order contracts, Steel
implements* higher order contracts to enable design by contract, made easy with a define/contract
macro for easier ergonomics. Racket makes use of a concept known as blame which seeks to identify the violating party - Steel
does not quite have fully fleshed out blame but that is a work in progress. Here are some examples:
;; Simple flat contracts
+(define/contract (test x y)
+ (->/c even? even? odd?)
+ (+ x y 1))
+
+(test 2 2) ;; => 5
+
+(define/contract (test-violation x y)
+ (->/c even? even? odd?)
+ (+ x y 1))
+
+(test-violation 1 2) ;; contract violation
+
+
+Contracts are implemented as values, so they are bound to functions. This enables the use of contract checking on functions themselves since functions can be passed around:
+;; Higher order contracts, check on application
+(define/contract (higher-order func y)
+ (->/c (->/c even? odd?) even? even?)
+ (+ 1 (func y)))
+
+(higher-order (lambda (x) (+ x 1)) 2) ;; => 4
+
+(define/contract (higher-order-violation func y)
+ (->/c (->/c even? odd?) even? even?)
+ (+ 1 (func y)))
+
+(higher-order-violation (lambda (x) (+ x 2)) 2) ;; contract violation
+
+Contracts on functions do not get checked until they are applied, so a function returning a contracted function won't cause a violation until that function is actually used:
+;; More higher order contracts, get checked on application
+(define/contract (output)
+ (->/c (->/c string? int?))
+ (lambda (x) 10))
+
+(define/contract (accept func)
+ (->/c (->/c string? int?) string?)
+ "cool cool cool")
+
+(accept (output)) ;; => "cool cool cool"
+
+;; different contracts on the argument
+(define/contract (accept-violation func)
+ (->/c (->/c string? string?) string?)
+ (func "applesauce")
+ "cool cool cool")
+
+(accept-violation (output)) ;; contract violation
+
+;; generates a function
+(define/contract (generate-closure)
+ (->/c (->/c string? int?))
+ (lambda (x) 10))
+
+;; calls generate-closure which should result in a contract violation
+(define/contract (accept-violation)
+ (->/c (->/c string? string?))
+ (generate-closure))
+
+((accept-violation) "test") ;; contract violation
+
+Perhaps a more nuanced case:
+(define/contract (output)
+ (->/c (->/c string? int?))
+ (lambda (x) 10.2))
+
+(define/contract (accept)
+ (->/c (->/c string? number?))
+ (output))
+
+
+((accept) "test") ;; contract violation 10.2 satisfies number? but _not_ int?
+
+Inspired by clojure's transducers, Steel
has a similar object that is somewhere half way in between transducers and iterators. Consider the following:
+(mapping (lambda (x) (+ x 1))) ;; => <#iterator>
+(filtering even?) ;; => <#iterator>
+(taking 15) ;; => <#iterator>
+
+(compose
+ (mapping add1)
+ (filtering odd?)
+ (taking 15)) ;; => <#iterator>
+
+Each of these expressions emit an <#iterator>
object, which means they're compatible with execute
and transduce
. Execute takes a transducer (i.e. <#iterator>
) and a collection that can be iterated (list
, vector
, or stream
) and applies the transducer.
;; Accepts lists
+(execute (mapping (lambda (x) (+ x 1))) (list 1 2 3 4 5)) ;; => '(2 3 4 5 6)
+
+;; Accepts vectors
+(execute (mapping (lambda (x) (+ x 1))) (vector 1 2 3 4 5)) ;; '#(2 3 4 5 6)
+
+;; Even accepts streams!
+(define (integers n)
+ (stream-cons n (lambda () (integers (+ 1 n)))))
+
+(execute (taking 5) (integers 0)) ;; => '(0 1 2 3 4)
+
+Transduce is just reduce
with more bells and whistles and works similarly:
;; (-> transducer reducing-function initial-value iterable)
+(transduce (mapping (lambda (x) (+ x 1))) + 0 (list 0 1 2 3)) ;; => 10
+
+Compose just combines the iterator functions and lets us avoid intermediate allocation. The composition works left to right - it chains each value through the functions and then accumulates into the output type. See the following:
+(define xf
+ (compose
+ (mapping add1)
+ (filtering odd?)
+ (taking 5)))
+
+(execute xf (range 0 100)) ;; => '(1 3 5 7 9)
+
+By default, execute outputs to the same type that was passed in. In other words, if you execute
a list
, it will return a list
. However, if you so choose, you can pass in a symbol specifying the output type of your choosing like so:
(define xf
+ (compose
+ (mapping add1)
+ (filtering odd?)
+ (taking 5)))
+
+;; Takes a list and returns a vector
+(execute xf (range 0 100) 'vector) ;; => '#(1 3 5 7 9)
+
+;; Takes a vector and returns a list
+(execute xf (vector 0 1 2 3 4 5 6 7 8 9 10) 'list) ;; => '(1 3 5 7 9)
+
+In order to support a growing codebase, Steel has module support for projects spanning multiple files. Steel files can provide
values (with contracts attached) and require
modules from other files:
;; main.scm
+(require "provide.scm")
+
+(even->odd 10)
+
+
+;; provide.scm
+(provide
+ (contract/out even->odd (->/c even? odd?))
+ no-contract
+ flat-value)
+
+(define (even->odd x)
+ (+ x 1))
+
+(define (accept-number x) (+ x 10))
+
+(define (no-contract) "cool cool cool")
+(define flat-value 15)
+
+(displayln "Calling even->odd with some bad inputs but its okay")
+(displayln (even->odd 1))
+
+Here we can see if we were to run main
that it would include the contents of provide
, and only provided values would be accessible from main
. The contract is attached at the contract boundary, so inside the provide
module, you can violate the contract, but outside the module the contract will be applied.
A few notes on modules:
+A
requires B
and C
, and B
requires C
, C
will be compiled once and shared between A
and B
.
+(require (prefix-in export. ;; prefix-in will prefix all of the bound identifiers with the given prefix
+ (only-in "export.scm" ;; only-in will only introduce the identifiers listed.
+ thing-should-not-escape
+ Applesauce
+ bananas
+ new-identifier
+ my-fun-contracted-function
+ contract-out-test)))
+
+If no modifiers are given, require
-ing a module will introduce all of the provided values into the top level scope.
Macros defined using define-syntax
can be handled just like normal values:
;; main.scm
+(require "provide.scm")
+
+(foo-bar x) ;; Will expand into (displayln "Hello world")
+
+;; provide.scm
+(provide foo-bar)
+
+(define-syntax foo-bar
+ (syntax-rules ()
+ [(foo-bar x) (displayln "Hello world!")]))
+
+
+The module system will take care to keep the namespaces separate - any macros that expand +into macros that exist in the originating module will be expanded, but those will not be available to +the requiring module. In addition, macros can expand into private values (those that are not provided), and the +will still be inaccessible from the requiring module.
+
+(keyword? v) -> boolean?
+
+v: any/c
+
+
+
+(mutable-vector args ...) -> mutable-vector?
+
+
+Macro for creating a new struct, in the form of:
+(struct <struct-name> (fields ...) options ...)
+The options can consist of the following:
Single variable options (those which their presence indicates #true
)
#:mutable
#:transparent
Other options must be presented as key value pairs, and will get stored
+in the struct instance. They will also be bound to the variable
+___<struct-name>-options___
in the same lexical environment where the
+struct was defined. For example:
λ > (struct Applesauce (a b c) #:mutable #:transparent #:unrecognized-option 1234)
+λ > ___Applesauce-options___
+=> #<hashmap {
+ '#:fields: '(a b c),
+ '#:mutable: #false,
+ '#:transparent: #false,
+ '#:unrecognized-option: 1234,
+}>
+
+By default, structs are immutable, which means setter functions will not +be generated. Also by default, structs are not transparent, which means +printing them will result in an opaque struct that does not list the fields
+(log! level arg-list)
+
+Log directly on the specified level the with arguments, as a list
+(log/info! . args)
+
+Log the arguments using the info target, i.e. log on INFO
+(log/warn! . args)
+
+Log the arguments using the warn target, i.e. log on WARN
+(log/debug! . args)
+
+Log the arguments using the debug target, i.e. log on DEBUG
+(log/error! . args)
+
+Log the arguments using the error target, i.e. log on ERROR
+(log/trace! . args)
+
+Log the arguments using the trace target, i.e. log on TRACE
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-get (hash 'a 10 'b 20) 'b) ;; => 20
+
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Contains direct wrappers around the Rust std::time::Instant
and std::time::Duration
modules.
+For example, to measure the time something takes:
(define t (instant/now))
+(displayln "Hello world")
+(displayln (instant/elapsed t))
+
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Get the length of the given string
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Converts a string into a list of characters.
+(string->list string?) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+### **string->lower**
+Creates a new lowercased version of the input string
+
+(string->lower string?) -> string?
+
+#### Examples
+
+```scheme
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Concatenatives all of the inputs to their string representation, separated by spaces.
+(to-string xs ...)
+> (to-string 10) ;; => "10"
+> (to-string "hello" "world") ;; => "hello world"
+
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the +time complexity is O(n/64). Meaning, for small lists this can be constant.
+(list-ref lst index) -> list?
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **to-string**
+Concatenatives all of the inputs to their string representation, separated by spaces.
+
+(to-string xs ...)
+
+* xs : any/c
+
+#### Examples
+```scheme
+> (to-string 10) ;; => "10"
+> (to-string "hello" "world") ;; => "hello world"
+
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Get the length of the given string
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-get (hash 'a 10 'b 20) 'b) ;; => 20
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Checks if the given value can be treated as a pair. +Note - there are no improper lists in steel, so any list with at least one element +is considered a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Lists in Steel have an interface that matches those of classic schemes or lisps. +At face value, they appear to be implemented as cons cells - however, under the hood +they are actually implemented as unrolled linked lists.
+This means that for most practical purposes, interaction with lists is the same. +That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.
+Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.
+(list 10 20 30 40) ;; => '(10 20 30 40)
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+### **steel/time**
+
+
+#### steel/time
+
+Contains direct wrappers around the Rust `std::time::Instant` and `std::time::Duration` modules.
+For example, to measure the time something takes:
+
+```scheme
+(define t (instant/now))
+(displayln "Hello world")
+(displayln (instant/elapsed t))
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Converts a string into a list of characters.
+(string->list string?) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Creates a new lowercased version of the input string
+(string->lower string?) -> string?
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Lists in Steel have an interface that matches those of classic schemes or lisps. +At face value, they appear to be implemented as cons cells - however, under the hood +they are actually implemented as unrolled linked lists.
+This means that for most practical purposes, interaction with lists is the same. +That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.
+Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.
+(list 10 20 30 40) ;; => '(10 20 30 40)
+
+Checks if the given value can be treated as a pair. +Note - there are no improper lists in steel, so any list with at least one element +is considered a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **list-ref**
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the
+time complexity is O(n/64). Meaning, for small lists this can be constant.
+
+(list-ref lst index) -> list?
+
+* lst : list?
+* index : (and/c int? positive?)
+
+#### Examples
+```scheme
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Multiplies the given numbers.
+(* . nums) -> number?
+> (* 5 3) ;; => 15
+> (* 10 3 2) ;; => 60
+> (*) ;; => 1
+
+Adds the given numbers.
+(+ . nums) -> number?
+> (+ 5 3) ;; => 8
+> (+ 10 3 2) ;; => 15
+> (+) ;; => 0
+
+Subtracts the given numbers.
+(- . nums) -> number?
+> (- 5 3) ;; => 2
+> (- 10 3 2) ;; => 5
+> (- -5) ;; => 5
+
+Divides the given numbers.
+(/ . nums) -> number?
+> (/ 10 2) ;; => 5
+> (/ 10 2 2.0) ;; => 2.5
+> (/ 1 3.0) ;; => 0.3333333333333333
+> (/ 1 3) ;; => 1/3
+
+Compares real numbers to check if any number is less than the subsequent.
+(< x . rest) -> bool?
+> (< 1) ;; => #t
+> (< 3 2) ;; => #f
+> (< 2 3) ;; => #t
+> (< 3/2 1.5) ;; => #f
+> (< 2.5 3/2) ;; => #t
+> (< 2 5/2 3) ;; #t
+
+Compares real numbers to check if any number is less than or equal than the subsequent.
+(<= x . rest) -> bool?
+> (<= 1) ;; => #t
+> (<= 3 2) ;; => #f
+> (<= 2 3) ;; => #t
+> (<= 3/2 1.5) ;; => #t
+> (<= 2.5 3/2) ;; => #f
+> (<= 2 6/2 3) ;; #t
+
+Compares real numbers to check if any number is greater than the subsequent.
+(> x . rest) -> bool?
+> (> 1) ;; => #t
+> (> 3 2) ;; => #t
+> (> 1 1) ;; => #f
+> (> 3/2 1.5) ;; => #f
+> (> 3/2 1.4) ;; => #t
+> (> 3 4/2 1) ;; #t
+
+Compares real numbers to check if any number is greater than or equal than the subsequent.
+(>= x . rest) -> bool?
+> (>= 1) ;; => #t
+> (>= 3 2) ;; => #t
+> (>= 2 3) ;; => #f
+> (>= 3/2 1.5) ;; => #t
+> (>= 3/2 1.4) ;; => #t
+> (>= 2 4/2 1) ;; #t
+
+Computes the absolute value of the given number.
+(abs number) -> number?
+> (abs 42) ;; => 42
+> (abs -42) ;; => 42
+> (abs 0) ;; => 0
+
+Appends the given lists together. If provided with no lists, will return the empty list.
+(append lst ...)
+lst : list?
+> (append (list 1 2) (list 3 4)) ;; => '(1 2 3 4)
+> (append) ;; => '()
+
+Applies the given function
with arguments as the contents of the list
.
(apply function lst) -> any?
+> (apply + (list 1 2 3 4)) ;; => 10
+> (apply list (list 1 2 3 4)) ;; => '(1 2 3 4)
+
+Returns #t
if the given value is a byte, meaning an exact
+integer between 0 and 255 inclusive, #f
otherwise.
(byte? 65) ;; => #t
+(byte? 0) ;; => #t
+(byte? 256) ;; => #f
+(byte? 100000) ;; => #f
+(byte? -1) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytes b ...)
+(bytes 65 112 112 108 101)
+
+Converts the bytevector to the equivalent list representation.
+(bytes->list (bytes 0 1 2 3 4 5)) ;; => '(0 1 2 3 4 5)
+
+Decodes a string from a bytevector containing valid UTF-8.
+(bytes->string/utf8 buf [start] [end]) -> string?
+(bytes->string/utf8 (bytes #xe5 #x8d #x83 #xe8 #x91 #x89)) ;; => "千葉"
+
+Append multiple byte vectors into a new bytevector.
+(bytes-append #u8(0 1 2) #u8(3 4 5)) ;; => #u8(#x00 #x01 #x02 #x03 #x04 #x05)
+
+(bytes-append #u8(0) #u8(1) #u8() #u8(2)) ;; => #u8(#x00 #x01 #x02)
+
+Returns the length of the given byte vector
+(bytes-length (bytes 1 2 3 4 5)) ;; => 5
+
+Fetches the byte at the given index within the bytevector. +If the index is out of bounds, this will error.
+(bytes-ref vector index)
+(bytes-ref (bytes 0 1 2 3 4 5) 3) ;; => 4
+(bytes-ref (bytes) 10) ;; error
+
+Sets the byte at the given index to the given byte. Will error +if the index is out of bounds.
+(bytes-set! vector index byte)
+(define my-bytes (bytes 0 1 2 3 4 5))
+(bytes-set! my-bytes 0 100)
+(bytes-ref my-bytes 0) ;; => 100
+
+Returns #t
if this value is a bytevector
(bytes? (bytes 0 1 2)) ;; => #t
+(bytes? (list 10 20 30)) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytevector b ...)
+(bytevector 65 112 112 108 101)
+
+Creates a copy of a bytevector.
+(bytevector-copy vector [start end]) -> bytes?
+(define vec (bytes 1 2 3 4 5))
+
+(bytevector-copy vec) ;; => (bytes 1 2 3 4 5)
+(bytevector-copy vec 1 3) ;; => (bytes 2 3)
+
+Returns canonical path with all components normalized
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Returns the rest of the list. Will raise an error if the list is empty.
+(cdr l) -> list?
+> (cdr (list 10 20 30)) ;; => '(20 30)
+> (cdr (list 10)) ;; => '()
+> (cdr '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (cdr '())
+│ ^^^ cdr expects a non empty list
+
+Rounds the given number up to the nearest integer not less than it.
+(ceiling number) -> integer?
+> (ceiling 42) ;; => 42
+> (ceiling 42.1) ;; => 43
+> (ceiling -42.1) ;; => -42
+
+Change the current working directory
+Returns the Unicode codepoint of a given character.
+(char->integer char?) -> integer?
+Attemps to convert the character into a decimal digit,
+and returns #f
on failure.
Returns #t
if the character is a decimal digit.
Returns the lower case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns the upper case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns #t
if the character is a whitespace character.
Compares characters according to their codepoints, in a "less-than-or-equal" fashion.
+(char<=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "less-than" fashion.
+(char<? char1 char2 ... ) -> bool?
+Checks if all characters are equal.
+Requires that all inputs are characters, and will otherwise raise an error.
+(char=? char1 char2 ...) -> bool?
+Compares characters according to their codepoints, in a "greater-than-or-equal" fashion.
+(char>=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "greater-than" fashion.
+(char>? char1 char2 ... ) -> bool?
+Returns the command line passed to this process, +including the command name as first argument.
+Checks if the given value is a complex number
+(complex? value) -> boolean?
+> (complex? 3+4i) ;; => #t
+> (complex? 42) ;; => #t
+> (complex? "hello") ;; => #f
+
+Returns a newly allocated list whose first element is a
and second element is d
.
(cons a d) -> list?
+> (cons 1 2) ;; => '(1 . 2)
+> (cons 1 '()) ;; => '(1)
+
+Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Returns the number of milliseconds since the Unix epoch as an inexact number.
+(current-inexact-milliseconds) -> inexact?
+Returns the number of milliseconds since the Unix epoch as an integer.
+(current-milliseconds) -> int?
+Returns the number of seconds since the Unix epoch as an integer.
+(current-second) -> int?
+Deletes the directory
+Deletes the file
+Retrieves the denominator of the given rational number.
+(denominator number) -> integer?
+> (denominator 1/2) ;; => 2
+> (denominator 3/4) ;; => 4
+> (denominator 4) ;; => 1
+
+Returns #t
if the value is an disconnected-channel object.
(eof-object? any/c) -> bool?
+Returns a string representation of a duration
+(duration->string dur)
+Returns #t
if the value is an empty-channel object.
(empty-channel-object? any/c) -> bool?
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Returns an EOF object.
+(eof-object) -> eof-object?
+Returns #t
if the value is an EOF object.
(eof-object? any/c) -> bool?
+Returns the message of an error object.
+(error-object-message error?) -> string?
+Converts an exact number to an inexact number.
+(exact->inexact num) -> number?
+> (exact->inexact 10) ;; => 10
+> (exact->inexact 1/2) ;; => 0.5
+> (exact->inexact 1+2i) ;; => 1+2i
+
+Computes the integer square root of the given non-negative integer.
+(exact-integer-sqrt number) -> (integer? integer?)
+> (exact-integer-sqrt 25) ;; => (5 0)
+> (exact-integer-sqrt 35) ;; => (5 10)
+
+Checks if the given value is an exact integer
+(exact-integer? value) -> boolean?
+> (exact-integer? 42) ;; => #t
+> (exact-integer? -42) ;; => #t
+> (exact-integer? 4.0) ;; => #f
+
+Checks if the given value is exact.
+(exact? val) -> boolean?
+> (exact? 42) ;; => #t
+> (exact? 3.14) ;; => #f
+> (exact? "hello") ;; => #f
+
+Returns Euler’s number raised to the power of z.
+(exp z) -> number?
+> (exp 0) ;; => 1
+> (exp 2) ;; => 7.38905609893065
+> (exp 1.5) ;; => 4.4816890703380645
+
+Raises the left operand to the power of the right operand.
+(expt base exponent) -> number?
+> (expt 2 3) ;; => 8
+> (expt 2.0 0.5) ;; => 1.4142135623730951
+> (expt 9 0.5) ;; => 3
+
+Gets the filename for a given path
+Returns #t
if the given number is finite.
(finite? number) -> boolean?
+> (finite? 42) ;; => #t
+> (finite? 0.1) ;; => #t
+> (finite? +inf.0) ;; => #f
+> (finite? -inf.0) ;; => #f
+> (finite? +nan.0) ;; => #f
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Checks if the given value is a floating-point number
+(float? value) -> boolean?
+> (float? 42) ;; => #f
+> (float? 3.14) ;; => #t
+> (float? #t) ;; => #f
+
+Computes the largest integer less than or equal to the given number.
+(floor number) -> number?
+> (floor 3.14) ;; => 3
+> (floor 4.99) ;; => 4
+> (floor -2.5) ;; => -3
+
+Extracts the contents from a port created with open-output-bytevector
.
(get-output-bytevector port?) -> bytes?
+Extracts the string contents from a port created with open-output-string
.
(get-output-string port?) -> string?
+Get the value out of the thread local storage slot.
+Creates an immutable hash table with each given key
mapped to the following val
.
+Each key must have a val, so the total number of arguments must be even.
(hash key val ...) -> hash?
+key : hashable? +val : any/c
+Note: the keys must be hashable.
+> (hash 'a 10 'b 20)",
+r#"=> #<hashmap {
+'a: 10,
+'b: 20,
+}>"#,
+
+Clears the entries out of the existing hashmap. +Will attempt to reuse the existing memory if there are no other references +to the hashmap.
+(hash-clear h) -> hash?
+h: hash?
+> (hash-clear (hash 'a 10 'b 20))
+=> '#hash()
+
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Checks whether the hash map is empty
+(hash-empty? m) -> bool?
+m: hash?
+> (hash-empty? (hash 'a 10)) ;; => #f
+> (hash-emptY? (hash)) ;; => #true
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Returns the keys of the given hash map as a list.
+(hash-keys->list map) -> (listof hashable?)
+
+> (hash-keys->list? (hash 'a 'b 20)) ;; => '(a b)
+
+Returns the keys of the given hash map as an immutable vector
+(hash-keys->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> ['a 'b]",
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-ref (hash 'a 10 'b 20) 'b) ;; => 20
+
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Constructs the union of two hashmaps, keeping the values +in the left map when the keys exist in both maps.
+Will reuse memory where possible.
+(hash-union l r) -> hash?
+> (hash-union (hash 'a 10) (hash 'b 20)) ;; => '#hash((a . 10) (b . 20))
+
+Returns the values of the given hash map as a list
+(hash-values->list? map) -> (listof any/c)?
+map: hash?
+> (hash-values->list? (hash 'a 10 'b 20)),
+=> '(10 20)",
+
+Returns the values of the given hash map as an immutable vector
+(hash-values->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> [10 10]",
+
+Converts an inexact number to an exact number.
+(inexact->exact num) -> number?
+> (inexact->exact 10.0) ;; => 10
+> (inexact->exact 1.5) ;; => 3/2
+> (inexact->exact 1.5+2.5i) ;; => 3/2+5/2i
+
+Checks if the given value is inexact.
+(inexact? val) -> boolean?
+> (inexact? 42) ;; => #f
+> (inexact? 3.14) ;; => #t
+
+Returns #t
if the given number is infinite.
(infinite? number) -> boolean?
+> (infinite? 42) ;; => #f
+> (infinite? -nan.0) ;; => #f
+> (infinite? +inf.0) ;; => #t
+
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Checks if the given value is an integer, an alias for integer?
(int? value) -> boolean?
+> (int? 42) ;; => #t
+> (int? 3.14) ;; => #f
+> (int? "hello") ;; => #f
+
+Returns the character corresponding to a given Unicode codepoint.
+(integer->char integer?) -> char?
+Checks if the given value is an integer, an alias for int?
(integer? value) -> boolean?
+> (integer? 42) ;; => #t
+> (integer? 3.14) ;; => #f
+> (integer? "hello") ;; => #f
+
+Checks if a path is a directory
+Checks if a path is a file
+Returns the last element in the list. Takes time proportional to the length of the list.
+(last l) -> any/c
+> (list (list 1 2 3 4)) ;; => 4
+
+Returns the length of the list.
+(length l) -> int?
+> (length (list 10 20 30)) ;; => 3
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Converts the list of bytes to the equivalent bytevector representation.
+The list must contain only values which satisfy the byte?
predicate,
+otherwise this function will error.
(list->bytes (list 0 1 2 3 4 5)) ;; => (bytes 0 1 2 3 4 5)
+
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the +time complexity is O(n/64). Meaning, for small lists this can be constant.
+(list-ref lst index) -> list?
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Returns the local time in the format given by the input string (using chrono::Local::format
).
(local-time/now! fmt) -> string?
+Lock the given mutex
+Unlock the given mutex
+Computes the natural logarithm of the given number.
+(log number [base]) -> number?
+> (log 10) ;; => 2.302585092994046
+> (log 100 10) ;; => 2
+> (log 27 3) ;; => 3
+
+Computes the magnitude of the given number.
+(magnitude number) -> number?
+> (magnitude 3+4i) ;; => 5
+> (magnitude 5) ;; => 5
+> (magnitude -5) ;; => 5
+
+Creates a bytevector given a length and a default value.
+(make-bytes len default) -> bytes?
+(make-bytes 6 42) ;; => (bytes 42 42 42 42 42)
+
+Creates a string of a given length, filled with an optional character
+(which defaults to #\0
).
(make-string len [char]) -> string?
+Creates a thread local storage slot. These slots are static, and will not be reclaimed.
+When spawning a new thread, the value inside will be shared into that slot, however +future updates to the slot will be local to that thread.
+Construct a new mutex
+Returns #t
if the real number is Nan.
(nan? value) -> boolean?
+(nan? +nan.0) => #t
+(nan? 100000) => #f
+
+Checks if the given real number is negative.
+(negative? num) -> boolean?
+> (negative? 0) ;; => #f
+> (negative? 1) ;; => #f
+> (negative? -1) ;; => #t
+
+Converts the given number to a string.
+Checks if the given value is a number
+(number? value) -> boolean?
+> (number? 42) ;; => #t
+> (number? "hello") ;; => #f
+> (number? 'symbol) ;; => #f
+
+Retrieves the numerator of the given rational number.
+(numerator number) -> number?
+> (numerator 3/4) ;; => 3
+> (numerator 5/2) ;; => 5
+> (numerator -2) ;; => -2
+
+Creates an input port from a bytevector, that will return the bytevector contents.
+(open-input-bytevector bytes?) -> input-port?
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Creates an input port from a string, that will return the string contents.
+(open-input-string string?) -> input-port?
+Creates an output port that accumulates what is written into a bytevector.
+These bytes can be recovered calling get-output-bytevector
.
(open-output-bytevector) -> output-port?
+(define out (open-output-bytevector))
+
+
+(write-byte 30 out)
+(write-byte 250 out)
+
+(get-output-bytevector out) ;; => (bytes 30 250)
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Creates an output port that accumulates what is written into a string.
+This string can be recovered calling get-output-string
.
(open-output-string) -> output-port?
+(define out (open-output-string))
+
+
+(write-char "α" out)
+(write-char "ω" out)
+
+(get-output-string out) ;; => "αω"
+
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Checks if the given value can be treated as a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Gets the parent directory name for a given path
+Gets the extension from a path
+Checks if a path exists
+Peeks the next byte from an input port.
+(peek-byte [port]) -> byte?
+Checks if the given real number is positive.
+(positive? num) -> boolean?
+> (positive? 0) ;; => #f
+> (positive? 1) ;; => #t
+> (positive? -1) ;; => #f
+
+Returns quotient of dividing numerator by denomintator.
+(quotient numerator denominator) -> integer?
+> (quotient 11 2) ;; => 5
+> (quotient 10 2) ;; => 5
+> (quotient -10 2) ;; => -5
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Returns #t if obj is a rational number, #f otherwise. +Rational numbers are numbers that can be expressed as the quotient of two numbers. +For example, 3/4, -5/2, 0.25, and 0 are rational numbers.
+(rational? value) -> bool?
+Examples:
+> (rational? (/ 0.0)) ;; => #f
+> (rational? 3.5) ;; => #t
+> (rational? 6/10) ;; => #t
+> (rational? +nan.0) ;; => #f
+
+Reads a single byte from an input port.
+(read-byte [port]) -> byte?
+Reads the next character from an input port.
+(read-char [port]) -> char?
+Returns the contents of the directory as a list
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Checks if the given value is a real number
+(real? value) -> boolean?
+> (real? 42) ;; => #t
+> (real? 3+4i) ;; => #f
+> (real? "hello") ;; => #f
+
+Blocks until one of the channels passed in is ready to receive. +Returns the index of the channel arguments passed in which is ready.
+Using this directly is not recommended.
+Returns the rest of the list. Will raise an error if the list is empty.
+(rest l) -> list?
+> (rest (list 10 20 30)) ;; => '(20 30)
+> (rest (list 10)) ;; => '()
+> (rest (list 10))
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (rest '())
+│ ^^^^ rest expects a non empty list
+
+Returns a list that has the same elements as lst
, but in reverse order.
+This function takes time proportional to the length of lst
.
(reverse lst) -> list?
+> (reverse (list 1 2 3 4)) ;; '(4 3 2 1)
+
+Rounds the given number to the nearest integer.
+(round number) -> number?
+> (round 3.14) ;; => 3
+> (round 4.6) ;; => 5
+> (round -2.5) ;; => -3
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **set-tls!**
+Set the value in the the thread local storage. Only this thread will see the updates associated
+with this TLS.
+### **split-many**
+Splits a string given a separator pattern into a list of strings.
+
+(split-many str pat) -> (listof string?)
+
+* str : string?
+* pat : string?
+
+#### Examples
+```scheme
+(split-many "foo,bar,baz" ",") ;; => '("foo" "bar" "baz")
+(split-many "foo|bar|" "|") ;; => '("foo" "bar" "")
+(split-many "" "&") ;; => '("")
+
+Splits a string given a separator at most once, yielding +a list with at most 2 elements.
+(split-once str pat) -> string?
+(split-once "foo,bar,baz" ",") ;; => '("foo" "bar,baz")
+(split-once "foo|bar|" "|") ;; => '("foo" "bar|")
+(split-once "" "&") ;; => '("")
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Computes the square root of the given number.
+(sqrt number) -> number?
+> (sqrt 4) ;; => 2
+> (sqrt 2) ;; => 1.4142135623730951
+> (sqrt -1) ;; => 0+1i
+
+Computes the square of the given number.
+(square number) -> number?
+> (square 5) ;; => 25
+> (square -3) ;; => 9
+> (square 2.5) ;; => 6.25
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Constructs a string from the given characters
+Encodes a string as UTF-8 into a bytevector.
+(string->bytes string?) -> bytes?
+(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Deserializes a JSON string into a Steel value.
+(string->jsexpr json) -> any/c
+(string->jsexpr "{\"foo\": [3]}") ;; => '#hash((foo . (3)))
+
+Converts a string into a list of characters.
+(string->list s [start] [end]) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Creates a new lowercased version of the input string
+(string->lower string?) -> string?
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Converts the given string to a number, with an optional radix.
+On failure, it returns #f
(string->number digits [radix]) -> (or/c number? boolean?)
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Alias of string->bytes
.
Returns a vector containing the characters of a given string
+(string->vector string?) -> vector?
+(string->vector "hello") ;; => '#(#\h #\e #\l #\l #\o)
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+Compares strings lexicographically (as in"less-than-or-equal"), +in a case insensitive fashion.
+(string-ci<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than"), +in a case insensitive fashion.
+(string-ci<? s1 s2 ... ) -> bool?
+Compares strings for equality, in a case insensitive fashion.
+Compares strings lexicographically (as in"greater-than-or-equal"), +in a case insensitive fashion.
+(string-ci>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than"), +in a case insensitive fashion.
+(string-ci>? s1 s2 ... ) -> bool?
+Searches a string to check if it contains the second argument.
+(string-contains? string? string?) -> bool?
+(string-contains? "hello" "lo") ;;=> #t
+(string-contains? "hello" "world") ;;=> #f
+
+Joins the given list of strings, with an optional separator.
+(string-join strings [sep]) -> string?
+(string-join '("a" "b" "c")) ;; => "abc"
+(string-join '("one" "two" "three") ", ") ;; => "one, two, three"
+
+Get the length of the given string in UTF-8 bytes.
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+> (string-length "✅") ;; => 3
+> (string-length "🤖") ;; => 4
+
+Extracts the nth character out of a given string.
+(string-ref str n) -> char?
+Replaces all occurrences of a pattern into the given string
+(string-replace str from to) -> string?
+(string-replace "hello world" "o" "@") ;; => "hell@ w@rld"
+
+Compares strings lexicographically (as in"less-than-equal-to").
+(string<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than").
+(string<? s1 s2 ... ) -> bool?
+Compares strings for equality.
+(string=? string1 string2 ...) -> bool?
+Compares strings lexicographically (as in"greater-than-or-equal").
+(string>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than").
+(string>? s1 s2 ... ) -> bool?
+Creates a substring slicing the characters between two indices.
+(substring str start end) -> string?
+(substring "hello" 1 4) ;; => "ell"
+(substring "hello" 10 15) ;; => error
+
+Returns the first n elements of the list l as a new list.
+(take l n) -> list?
+> (take '(1 2 3 4) 2) ;; => '(0 1)
+> (take (range 0 10) 4) ;; => '(0 1 2 3)
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Check if the given thread is finished running.
+Interrupts the thread. Note, this will not interrupt any native code +that is potentially running in the thread, and will attempt to block +at the next bytecode instruction that is running.
+Block until this thread finishes.
+Resume a suspended thread. This does nothing if the thread is already joined.
+Suspend the thread. Note, this will not interrupt any native code that is +potentially running in the thread, and will attempt to block at the next +bytecode instruction that is running.
+Sleeps the thread for a given number of milliseconds.
+(time/sleep-ms ms)
+Concatenates all of the inputs to their string representation, separated by spaces.
+(to-string xs ...)
+> (to-string 10) ;; => "10"
+> (to-string 10 20) ;; => "10 20"
+> (to-string "hello" "world") ;; => "hello world"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Returns a new string with the given pat
repeatedly removed from the end
+of the string
(trim-end-matches string? string?) -> string?
+
+> (trim-end-matches "123foo1bar123123" "123") ;; => "123foo1bar"
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Returns a new string with the given pat
repeatedly removed from the start
+of the string
(trim-start-matches string? string?) -> string?
+
+> (trim-start-matches "123foo1bar123123" "123") ;; => "foo1bar123123"
+
+Alias of bytes->string/utf8
.
Serializes a Steel value into a string.
+(value->jsexpr-string any/c) -> string?
+(value->jsexpr-string `(,(hash "foo" #t))) ;; => "[{\"foo\":true}]"
+
+The void value, returned by many forms with side effects, such as define
.
Writes a single byte to an output port.
+(write-byte b [port])
+Writes the contents of a bytevector into an output port.
+(write-bytes buf [port])
+Checks if the given real number is zero.
+(zero? num) -> boolean?
+> (zero? 0) ;; => #t
+> (zero? 0.0) ;; => #t
+> (zero? 0.1) ;; => #f
+
+Returns #t
if the given value is a byte, meaning an exact
+integer between 0 and 255 inclusive, #f
otherwise.
(byte? 65) ;; => #t
+(byte? 0) ;; => #t
+(byte? 256) ;; => #f
+(byte? 100000) ;; => #f
+(byte? -1) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytes b ...)
+(bytes 65 112 112 108 101)
+
+Converts the bytevector to the equivalent list representation.
+(bytes->list (bytes 0 1 2 3 4 5)) ;; => '(0 1 2 3 4 5)
+
+Decodes a string from a bytevector containing valid UTF-8.
+(bytes->string/utf8 buf [start] [end]) -> string?
+(bytes->string/utf8 (bytes #xe5 #x8d #x83 #xe8 #x91 #x89)) ;; => "千葉"
+
+Append multiple byte vectors into a new bytevector.
+(bytes-append #u8(0 1 2) #u8(3 4 5)) ;; => #u8(#x00 #x01 #x02 #x03 #x04 #x05)
+
+(bytes-append #u8(0) #u8(1) #u8() #u8(2)) ;; => #u8(#x00 #x01 #x02)
+
+Returns the length of the given byte vector
+(bytes-length (bytes 1 2 3 4 5)) ;; => 5
+
+Fetches the byte at the given index within the bytevector. +If the index is out of bounds, this will error.
+(bytes-ref vector index)
+(bytes-ref (bytes 0 1 2 3 4 5) 3) ;; => 4
+(bytes-ref (bytes) 10) ;; error
+
+Sets the byte at the given index to the given byte. Will error +if the index is out of bounds.
+(bytes-set! vector index byte)
+(define my-bytes (bytes 0 1 2 3 4 5))
+(bytes-set! my-bytes 0 100)
+(bytes-ref my-bytes 0) ;; => 100
+
+Returns #t
if this value is a bytevector
(bytes? (bytes 0 1 2)) ;; => #t
+(bytes? (list 10 20 30)) ;; => #f
+
+Returns a new mutable vector with each byte as the given arguments.
+Each argument must satisfy the byte?
predicate, meaning it is an exact
+integer range from 0 - 255 (inclusive)
(bytevector b ...)
+(bytevector 65 112 112 108 101)
+
+Creates a copy of a bytevector.
+(bytevector-copy vector [start end]) -> bytes?
+(define vec (bytes 1 2 3 4 5))
+
+(bytevector-copy vec) ;; => (bytes 1 2 3 4 5)
+(bytevector-copy vec 1 3) ;; => (bytes 2 3)
+
+Converts the list of bytes to the equivalent bytevector representation.
+The list must contain only values which satisfy the byte?
predicate,
+otherwise this function will error.
(list->bytes (list 0 1 2 3 4 5)) ;; => (bytes 0 1 2 3 4 5)
+
+Creates a bytevector given a length and a default value.
+(make-bytes len default) -> bytes?
+(make-bytes 6 42) ;; => (bytes 42 42 42 42 42)
+
+Alias of bytes->string/utf8
.
Miscellaneous constants
+The void value, returned by many forms with side effects, such as define
.
Filesystem functions, mostly just thin wrappers around the std::fs
functions in
+the Rust std library.
Returns canonical path with all components normalized
+Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Change the current working directory
+Deletes the directory
+Deletes the file
+Gets the filename for a given path
+Gets the parent directory name for a given path
+Checks if a path is a directory
+Checks if a path is a file
+Gets the extension from a path
+Checks if a path exists
+Returns the contents of the directory as a list
+Creates an immutable hash table with each given key
mapped to the following val
.
+Each key must have a val, so the total number of arguments must be even.
(hash key val ...) -> hash?
+key : hashable? +val : any/c
+Note: the keys must be hashable.
+> (hash 'a 10 'b 20)",
+r#"=> #<hashmap {
+'a: 10,
+'b: 20,
+}>"#,
+
+Clears the entries out of the existing hashmap. +Will attempt to reuse the existing memory if there are no other references +to the hashmap.
+(hash-clear h) -> hash?
+h: hash?
+> (hash-clear (hash 'a 10 'b 20))
+=> '#hash()
+
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Checks whether the hash map is empty
+(hash-empty? m) -> bool?
+m: hash?
+> (hash-empty? (hash 'a 10)) ;; => #f
+> (hash-emptY? (hash)) ;; => #true
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Returns the keys of the given hash map as a list.
+(hash-keys->list map) -> (listof hashable?)
+
+> (hash-keys->list? (hash 'a 'b 20)) ;; => '(a b)
+
+Returns the keys of the given hash map as an immutable vector
+(hash-keys->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> ['a 'b]",
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-ref (hash 'a 10 'b 20) 'b) ;; => 20
+
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Constructs the union of two hashmaps, keeping the values +in the left map when the keys exist in both maps.
+Will reuse memory where possible.
+(hash-union l r) -> hash?
+> (hash-union (hash 'a 10) (hash 'b 20)) ;; => '#hash((a . 10) (b . 20))
+
+Returns the values of the given hash map as a list
+(hash-values->list? map) -> (listof any/c)?
+map: hash?
+> (hash-values->list? (hash 'a 10 'b 20)),
+=> '(10 20)",
+
+Returns the values of the given hash map as an immutable vector
+(hash-values->vector map) -> (vectorof any/c)?
+map: hash?
+> (hash-keys->vector (hash 'a 10 'b 20)),
+=> [10 10]",
+
+Checks if the given value is a complex number
+(complex? value) -> boolean?
+> (complex? 3+4i) ;; => #t
+> (complex? 42) ;; => #t
+> (complex? "hello") ;; => #f
+
+Returns #t
if the value is an EOF object.
(eof-object? any/c) -> bool?
+Checks if the given value is an exact integer
+(exact-integer? value) -> boolean?
+> (exact-integer? 42) ;; => #t
+> (exact-integer? -42) ;; => #t
+> (exact-integer? 4.0) ;; => #f
+
+Checks if the given value is a floating-point number
+(float? value) -> boolean?
+> (float? 42) ;; => #f
+> (float? 3.14) ;; => #t
+> (float? #t) ;; => #f
+
+Checks if the given value is an integer, an alias for integer?
(int? value) -> boolean?
+> (int? 42) ;; => #t
+> (int? 3.14) ;; => #f
+> (int? "hello") ;; => #f
+
+Checks if the given value is an integer, an alias for int?
(integer? value) -> boolean?
+> (integer? 42) ;; => #t
+> (integer? 3.14) ;; => #f
+> (integer? "hello") ;; => #f
+
+Checks if the given value is a number
+(number? value) -> boolean?
+> (number? 42) ;; => #t
+> (number? "hello") ;; => #f
+> (number? 'symbol) ;; => #f
+
+Returns #t if obj is a rational number, #f otherwise. +Rational numbers are numbers that can be expressed as the quotient of two numbers. +For example, 3/4, -5/2, 0.25, and 0 are rational numbers.
+(rational? value) -> bool?
+Examples:
+> (rational? (/ 0.0)) ;; => #f
+> (rational? 3.5) ;; => #t
+> (rational? 6/10) ;; => #t
+> (rational? +nan.0) ;; => #f
+
+Checks if the given value is a real number
+(real? value) -> boolean?
+> (real? 42) ;; => #t
+> (real? 3+4i) ;; => #f
+> (real? "hello") ;; => #f
+
+Pushes a value to the back of the vector, returning a new vector.
+De/serialization from/to JSON.
+Deserializes a JSON string into a Steel value.
+(string->jsexpr json) -> any/c
+(string->jsexpr "{\"foo\": [3]}") ;; => '#hash((foo . (3)))
+
+Serializes a Steel value into a string.
+(value->jsexpr-string any/c) -> string?
+(value->jsexpr-string `(,(hash "foo" #t))) ;; => "[{\"foo\":true}]"
+
+Lists in Steel have an interface that matches those of classic schemes or lisps. +At face value, they appear to be implemented as cons cells - however, under the hood +they are actually implemented as unrolled linked lists.
+This means that for most practical purposes, interaction with lists is the same. +That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.
+Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.
+(list 10 20 30 40) ;; => '(10 20 30 40)
+
+Appends the given lists together. If provided with no lists, will return the empty list.
+(append lst ...)
+lst : list?
+> (append (list 1 2) (list 3 4)) ;; => '(1 2 3 4)
+> (append) ;; => '()
+
+Applies the given function
with arguments as the contents of the list
.
(apply function lst) -> any?
+> (apply + (list 1 2 3 4)) ;; => 10
+> (apply list (list 1 2 3 4)) ;; => '(1 2 3 4)
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Returns the rest of the list. Will raise an error if the list is empty.
+(cdr l) -> list?
+> (cdr (list 10 20 30)) ;; => '(20 30)
+> (cdr (list 10)) ;; => '()
+> (cdr '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (cdr '())
+│ ^^^ cdr expects a non empty list
+
+Returns a newly allocated list whose first element is a
and second element is d
.
(cons a d) -> list?
+> (cons 1 2) ;; => '(1 . 2)
+> (cons 1 '()) ;; => '(1)
+
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Returns the last element in the list. Takes time proportional to the length of the list.
+(last l) -> any/c
+> (list (list 1 2 3 4)) ;; => 4
+
+Returns the length of the list.
+(length l) -> int?
+> (length (list 10 20 30)) ;; => 3
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the +time complexity is O(n/64). Meaning, for small lists this can be constant.
+(list-ref lst index) -> list?
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Checks if the given value can be treated as a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Returns the rest of the list. Will raise an error if the list is empty.
+(rest l) -> list?
+> (rest (list 10 20 30)) ;; => '(20 30)
+> (rest (list 10)) ;; => '()
+> (rest (list 10))
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (rest '())
+│ ^^^^ rest expects a non empty list
+
+Returns a list that has the same elements as lst
, but in reverse order.
+This function takes time proportional to the length of lst
.
(reverse lst) -> list?
+> (reverse (list 1 2 3 4)) ;; '(4 3 2 1)
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **take**
+Returns the first n elements of the list l as a new list.
+
+(take l n) -> list?
+
+* l : list?
+* n : (and/c positive? int?)
+
+#### Examples
+
+```scheme
+> (take '(1 2 3 4) 2) ;; => '(0 1)
+> (take (range 0 10) 4) ;; => '(0 1 2 3)
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Returns the command line passed to this process, +including the command name as first argument.
+Returns the message of an error object.
+(error-object-message error?) -> string?
+Multiplies the given numbers.
+(* . nums) -> number?
+> (* 5 3) ;; => 15
+> (* 10 3 2) ;; => 60
+> (*) ;; => 1
+
+Adds the given numbers.
+(+ . nums) -> number?
+> (+ 5 3) ;; => 8
+> (+ 10 3 2) ;; => 15
+> (+) ;; => 0
+
+Subtracts the given numbers.
+(- . nums) -> number?
+> (- 5 3) ;; => 2
+> (- 10 3 2) ;; => 5
+> (- -5) ;; => 5
+
+Divides the given numbers.
+(/ . nums) -> number?
+> (/ 10 2) ;; => 5
+> (/ 10 2 2.0) ;; => 2.5
+> (/ 1 3.0) ;; => 0.3333333333333333
+> (/ 1 3) ;; => 1/3
+
+Computes the absolute value of the given number.
+(abs number) -> number?
+> (abs 42) ;; => 42
+> (abs -42) ;; => 42
+> (abs 0) ;; => 0
+
+Rounds the given number up to the nearest integer not less than it.
+(ceiling number) -> integer?
+> (ceiling 42) ;; => 42
+> (ceiling 42.1) ;; => 43
+> (ceiling -42.1) ;; => -42
+
+Retrieves the denominator of the given rational number.
+(denominator number) -> integer?
+> (denominator 1/2) ;; => 2
+> (denominator 3/4) ;; => 4
+> (denominator 4) ;; => 1
+
+Converts an exact number to an inexact number.
+(exact->inexact num) -> number?
+> (exact->inexact 10) ;; => 10
+> (exact->inexact 1/2) ;; => 0.5
+> (exact->inexact 1+2i) ;; => 1+2i
+
+Computes the integer square root of the given non-negative integer.
+(exact-integer-sqrt number) -> (integer? integer?)
+> (exact-integer-sqrt 25) ;; => (5 0)
+> (exact-integer-sqrt 35) ;; => (5 10)
+
+Checks if the given value is exact.
+(exact? val) -> boolean?
+> (exact? 42) ;; => #t
+> (exact? 3.14) ;; => #f
+> (exact? "hello") ;; => #f
+
+Returns Euler’s number raised to the power of z.
+(exp z) -> number?
+> (exp 0) ;; => 1
+> (exp 2) ;; => 7.38905609893065
+> (exp 1.5) ;; => 4.4816890703380645
+
+Raises the left operand to the power of the right operand.
+(expt base exponent) -> number?
+> (expt 2 3) ;; => 8
+> (expt 2.0 0.5) ;; => 1.4142135623730951
+> (expt 9 0.5) ;; => 3
+
+Returns #t
if the given number is finite.
(finite? number) -> boolean?
+> (finite? 42) ;; => #t
+> (finite? 0.1) ;; => #t
+> (finite? +inf.0) ;; => #f
+> (finite? -inf.0) ;; => #f
+> (finite? +nan.0) ;; => #f
+
+Computes the largest integer less than or equal to the given number.
+(floor number) -> number?
+> (floor 3.14) ;; => 3
+> (floor 4.99) ;; => 4
+> (floor -2.5) ;; => -3
+
+Converts an inexact number to an exact number.
+(inexact->exact num) -> number?
+> (inexact->exact 10.0) ;; => 10
+> (inexact->exact 1.5) ;; => 3/2
+> (inexact->exact 1.5+2.5i) ;; => 3/2+5/2i
+
+Checks if the given value is inexact.
+(inexact? val) -> boolean?
+> (inexact? 42) ;; => #f
+> (inexact? 3.14) ;; => #t
+
+Returns #t
if the given number is infinite.
(infinite? number) -> boolean?
+> (infinite? 42) ;; => #f
+> (infinite? -nan.0) ;; => #f
+> (infinite? +inf.0) ;; => #t
+
+Computes the natural logarithm of the given number.
+(log number [base]) -> number?
+> (log 10) ;; => 2.302585092994046
+> (log 100 10) ;; => 2
+> (log 27 3) ;; => 3
+
+Computes the magnitude of the given number.
+(magnitude number) -> number?
+> (magnitude 3+4i) ;; => 5
+> (magnitude 5) ;; => 5
+> (magnitude -5) ;; => 5
+
+Returns #t
if the real number is Nan.
(nan? value) -> boolean?
+(nan? +nan.0) => #t
+(nan? 100000) => #f
+
+Checks if the given real number is negative.
+(negative? num) -> boolean?
+> (negative? 0) ;; => #f
+> (negative? 1) ;; => #f
+> (negative? -1) ;; => #t
+
+Retrieves the numerator of the given rational number.
+(numerator number) -> number?
+> (numerator 3/4) ;; => 3
+> (numerator 5/2) ;; => 5
+> (numerator -2) ;; => -2
+
+Checks if the given real number is positive.
+(positive? num) -> boolean?
+> (positive? 0) ;; => #f
+> (positive? 1) ;; => #t
+> (positive? -1) ;; => #f
+
+Returns quotient of dividing numerator by denomintator.
+(quotient numerator denominator) -> integer?
+> (quotient 11 2) ;; => 5
+> (quotient 10 2) ;; => 5
+> (quotient -10 2) ;; => -5
+
+Rounds the given number to the nearest integer.
+(round number) -> number?
+> (round 3.14) ;; => 3
+> (round 4.6) ;; => 5
+> (round -2.5) ;; => -3
+
+Computes the square root of the given number.
+(sqrt number) -> number?
+> (sqrt 4) ;; => 2
+> (sqrt 2) ;; => 1.4142135623730951
+> (sqrt -1) ;; => 0+1i
+
+Computes the square of the given number.
+(square number) -> number?
+> (square 5) ;; => 25
+> (square -3) ;; => 9
+> (square 2.5) ;; => 6.25
+
+Checks if the given real number is zero.
+(zero? num) -> boolean?
+> (zero? 0) ;; => #t
+> (zero? 0.0) ;; => #t
+> (zero? 0.1) ;; => #f
+
+Real numbers ordering module.
+Compares real numbers to check if any number is less than the subsequent.
+(< x . rest) -> bool?
+> (< 1) ;; => #t
+> (< 3 2) ;; => #f
+> (< 2 3) ;; => #t
+> (< 3/2 1.5) ;; => #f
+> (< 2.5 3/2) ;; => #t
+> (< 2 5/2 3) ;; #t
+
+Compares real numbers to check if any number is less than or equal than the subsequent.
+(<= x . rest) -> bool?
+> (<= 1) ;; => #t
+> (<= 3 2) ;; => #f
+> (<= 2 3) ;; => #t
+> (<= 3/2 1.5) ;; => #t
+> (<= 2.5 3/2) ;; => #f
+> (<= 2 6/2 3) ;; #t
+
+Compares real numbers to check if any number is greater than the subsequent.
+(> x . rest) -> bool?
+> (> 1) ;; => #t
+> (> 3 2) ;; => #t
+> (> 1 1) ;; => #f
+> (> 3/2 1.5) ;; => #f
+> (> 3/2 1.4) ;; => #t
+> (> 3 4/2 1) ;; #t
+
+Compares real numbers to check if any number is greater than or equal than the subsequent.
+(>= x . rest) -> bool?
+> (>= 1) ;; => #t
+> (>= 3 2) ;; => #t
+> (>= 2 3) ;; => #f
+> (>= 3/2 1.5) ;; => #t
+> (>= 3/2 1.4) ;; => #t
+> (>= 2 4/2 1) ;; #t
+
+Returns an EOF object.
+(eof-object) -> eof-object?
+Extracts the contents from a port created with open-output-bytevector
.
(get-output-bytevector port?) -> bytes?
+Extracts the string contents from a port created with open-output-string
.
(get-output-string port?) -> string?
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Creates an input port from a bytevector, that will return the bytevector contents.
+(open-input-bytevector bytes?) -> input-port?
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Creates an input port from a string, that will return the string contents.
+(open-input-string string?) -> input-port?
+Creates an output port that accumulates what is written into a bytevector.
+These bytes can be recovered calling get-output-bytevector
.
(open-output-bytevector) -> output-port?
+(define out (open-output-bytevector))
+
+
+(write-byte 30 out)
+(write-byte 250 out)
+
+(get-output-bytevector out) ;; => (bytes 30 250)
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Creates an output port that accumulates what is written into a string.
+This string can be recovered calling get-output-string
.
(open-output-string) -> output-port?
+(define out (open-output-string))
+
+
+(write-char "α" out)
+(write-char "ω" out)
+
+(get-output-string out) ;; => "αω"
+
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Peeks the next byte from an input port.
+(peek-byte [port]) -> byte?
+Reads a single byte from an input port.
+(read-byte [port]) -> byte?
+Reads the next character from an input port.
+(read-char [port]) -> char?
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Writes a single byte to an output port.
+(write-byte b [port])
+Writes the contents of a bytevector into an output port.
+(write-bytes buf [port])
+Strings in Steel are immutable, fixed length arrays of characters. They are heap allocated, and
+are implemented under the hood as referenced counted Rust Strings
. Rust Strings
are stored
+as UTF-8 encoded bytes.
Returns the Unicode codepoint of a given character.
+(char->integer char?) -> integer?
+Attemps to convert the character into a decimal digit,
+and returns #f
on failure.
Returns #t
if the character is a decimal digit.
Returns the lower case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns the upper case version of a character, if defined by Unicode, +or the same character otherwise.
+Returns #t
if the character is a whitespace character.
Compares characters according to their codepoints, in a "less-than-or-equal" fashion.
+(char<=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "less-than" fashion.
+(char<? char1 char2 ... ) -> bool?
+Checks if all characters are equal.
+Requires that all inputs are characters, and will otherwise raise an error.
+(char=? char1 char2 ...) -> bool?
+Compares characters according to their codepoints, in a "greater-than-or-equal" fashion.
+(char>=? char1 char2 ... ) -> bool?
+Compares characters according to their codepoints, in a "greater-than" fashion.
+(char>? char1 char2 ... ) -> bool?
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Returns the character corresponding to a given Unicode codepoint.
+(integer->char integer?) -> char?
+Creates a string of a given length, filled with an optional character
+(which defaults to #\0
).
(make-string len [char]) -> string?
+Converts the given number to a string.
+Splits a string given a separator pattern into a list of strings.
+(split-many str pat) -> (listof string?)
+(split-many "foo,bar,baz" ",") ;; => '("foo" "bar" "baz")
+(split-many "foo|bar|" "|") ;; => '("foo" "bar" "")
+(split-many "" "&") ;; => '("")
+
+Splits a string given a separator at most once, yielding +a list with at most 2 elements.
+(split-once str pat) -> string?
+(split-once "foo,bar,baz" ",") ;; => '("foo" "bar,baz")
+(split-once "foo|bar|" "|") ;; => '("foo" "bar|")
+(split-once "" "&") ;; => '("")
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Constructs a string from the given characters
+Encodes a string as UTF-8 into a bytevector.
+(string->bytes string?) -> bytes?
+(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Converts a string into a list of characters.
+(string->list s [start] [end]) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Creates a new lowercased version of the input string
+(string->lower string?) -> string?
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Converts the given string to a number, with an optional radix.
+On failure, it returns #f
(string->number digits [radix]) -> (or/c number? boolean?)
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Alias of string->bytes
.
Returns a vector containing the characters of a given string
+(string->vector string?) -> vector?
+(string->vector "hello") ;; => '#(#\h #\e #\l #\l #\o)
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+Compares strings lexicographically (as in"less-than-or-equal"), +in a case insensitive fashion.
+(string-ci<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than"), +in a case insensitive fashion.
+(string-ci<? s1 s2 ... ) -> bool?
+Compares strings for equality, in a case insensitive fashion.
+Compares strings lexicographically (as in"greater-than-or-equal"), +in a case insensitive fashion.
+(string-ci>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than"), +in a case insensitive fashion.
+(string-ci>? s1 s2 ... ) -> bool?
+Searches a string to check if it contains the second argument.
+(string-contains? string? string?) -> bool?
+(string-contains? "hello" "lo") ;;=> #t
+(string-contains? "hello" "world") ;;=> #f
+
+Joins the given list of strings, with an optional separator.
+(string-join strings [sep]) -> string?
+(string-join '("a" "b" "c")) ;; => "abc"
+(string-join '("one" "two" "three") ", ") ;; => "one, two, three"
+
+Get the length of the given string in UTF-8 bytes.
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+> (string-length "✅") ;; => 3
+> (string-length "🤖") ;; => 4
+
+Extracts the nth character out of a given string.
+(string-ref str n) -> char?
+Replaces all occurrences of a pattern into the given string
+(string-replace str from to) -> string?
+(string-replace "hello world" "o" "@") ;; => "hell@ w@rld"
+
+Compares strings lexicographically (as in"less-than-equal-to").
+(string<=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"less-than").
+(string<? s1 s2 ... ) -> bool?
+Compares strings for equality.
+(string=? string1 string2 ...) -> bool?
+Compares strings lexicographically (as in"greater-than-or-equal").
+(string>=? s1 s2 ... ) -> bool?
+Compares strings lexicographically (as in"greater-than").
+(string>? s1 s2 ... ) -> bool?
+Creates a substring slicing the characters between two indices.
+(substring str start end) -> string?
+(substring "hello" 1 4) ;; => "ell"
+(substring "hello" 10 15) ;; => error
+
+Concatenates all of the inputs to their string representation, separated by spaces.
+(to-string xs ...)
+> (to-string 10) ;; => "10"
+> (to-string 10 20) ;; => "10 20"
+> (to-string "hello" "world") ;; => "hello world"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Returns a new string with the given pat
repeatedly removed from the end
+of the string
(trim-end-matches string? string?) -> string?
+
+> (trim-end-matches "123foo1bar123123" "123") ;; => "123foo1bar"
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Returns a new string with the given pat
repeatedly removed from the start
+of the string
(trim-start-matches string? string?) -> string?
+
+> (trim-start-matches "123foo1bar123123" "123") ;; => "foo1bar123123"
+
+Contains direct wrappers around the Rust std::time::Instant
and std::time::Duration
modules.
+For example, to measure the time something takes:
(define t (instant/now))
+(displayln "Hello world")
+(displayln (instant/elapsed t))
+
+Returns the number of milliseconds since the Unix epoch as an inexact number.
+(current-inexact-milliseconds) -> inexact?
+Returns the number of milliseconds since the Unix epoch as an integer.
+(current-milliseconds) -> int?
+Returns the number of seconds since the Unix epoch as an integer.
+(current-second) -> int?
+Returns a string representation of a duration
+(duration->string dur)
+Returns the local time in the format given by the input string (using chrono::Local::format
).
(local-time/now! fmt) -> string?
+Sleeps the thread for a given number of milliseconds.
+(time/sleep-ms ms)
+Steel uses a combination of reference counting and a mark and sweep garbage collector in order to manage memory. Important notes regarding this:
+All immutable values are reference counted, this includes built in data structures such as:
+All primitive types are unboxed, these include:
+#<void>
typeMutable values are transformed into boxes
- which are mutable memory locations managed by the garbage collector.
Mutable vectos are a special form handled by the garbage collector as well.
+Mutable structs, are immutable vectors with boxes in each slot for each mutable memory location.
+Heap allocated boxes are weak references to a reference counted pointer living in the heap. Thus, even memory managed by the garbage collector is also reference counted. This means that unboxing a mutable memory location requires two pointer jumps - first the weak pointer to the heap, then the strong pointer to the underlying value.
+One consequence of using reference counted variables is that there will be a non trivial amount of time spent performing reference count operations on values coming and going from the stack. The steel compiler and vm performs a few optimizations to reduce the reference counting thrash, and also to improve the usage of the functional data structures built in.
+Consider the following:
+
+(define (reverse input output)
+ (if (null? input)
+ output
+ (reverse (cdr input) (cons (car input) output))))
+
+(reverse (list 10 20 30 40) '()) ;; => (list 40 30 20 10)
+
+
+This is a simple function that takes an input list and reverses it. It does so recursively, calling reverse
in tail position, meaning we'll get a tail call optimization and this gets converted into a JUMP
in the VM.
Lists in Steel aren't your classic cons cells - they're implemented more like unrolled linked lists or vlists - meaning they're more like chunks of large contiguous vectors strung together. Copying those on each write to it would be silly, so the compiler analyzes a function and attempts to find the last usage of variable. For every last usage of a variable, the VM doesn't just copy the value from the stack, it actually moves it off of the VM stack - meaning the reference count has the potential to be 1 by the time it hits the last usage of a variable.
+When the reference count is 1, we can perform an in-place mutation of the resulting list - which gives us a nice performance win! So in the above snippet, we see the following bytecode:
+0 SDEF : 0 reverse
+1 PUREFUNC : 21 lambda
+2 PASS : 0
+3 PASS : 256
+4 READLOCAL0 : 0 input
+5 CALLGLOBAL : 155 null?
+6 FUNC : 1 null?
+7 IF : 6
+8 READLOCAL1 : 1 output
+9 JMP : 17
+10 READLOCAL0 : 0 input
+11 CALLGLOBAL : 86 cdr
+12 FUNC : 1 cdr
+13 MOVEREADLOCAL0 : 0 input ;; <- Last usage of input
+14 CALLGLOBAL : 92 car
+15 FUNC : 1 car
+16 MOVEREADLOCAL1 : 1 output ;; <- last usage of output
+17 CALLGLOBAL : 94 cons
+18 FUNC : 2 cons
+19 TCOJMP : 2 reverse
+20 PASS : 0
+21 POPPURE : 2
+22 ECLOSURE : 2
+23 EDEF : 0
+24 BIND : 975 reverse
+25 VOID : 0
+26 POPPURE : 0
+
+Which corresponds to these call sites:
+
+(define (reverse input output)
+ (if (null? input)
+ output
+ (reverse (cdr input) (cons (car input) output))))
+ ^^^^^ ^^^^^^
+
+In this case, the mutation of output
is great - since we can cons
onto the list and this under the hood is just a push onto the underlying vector. However, for input it doesn't mean much; car
is a reference operation and has no optimizations associated with it. The compiler isn't quite smart enough to do this yet, but rewriting this slightly, we can get the optimization we want:
+(define (reverse input output)
+ (if (null? input)
+ output
+ (let ([first-element (car input)])
+ (reverse (cdr input) (cons first-element output)))))
+
+
+Which results in this bytecode:
+0 SDEF : 0 reverse
+1 PUREFUNC : 24 lambda
+2 PASS : 0
+3 PASS : 256
+4 READLOCAL0 : 0 input
+5 CALLGLOBAL : 156 null?
+6 FUNC : 1 null?
+7 IF : 6
+8 READLOCAL1 : 1 output
+9 JMP : 20
+10 BEGINSCOPE : 0
+11 READLOCAL0 : 0 input
+12 CALLGLOBAL : 98 car
+13 FUNC : 1 car
+14 MOVEREADLOCAL0 : 0 input ;; <- Here
+15 CALLGLOBAL : 97 cdr
+16 FUNC : 1 cdr
+17 READLOCAL2 : 2 first-element
+18 MOVEREADLOCAL1 : 1 output ;; <- Here
+19 CALLGLOBAL : 86 cons
+20 FUNC : 2 cons
+21 TCOJMP : 2 reverse
+22 PASS : 0
+23 LETENDSCOPE : 2
+24 POPPURE : 2
+25 ECLOSURE : 2
+26 EDEF : 0
+27 BIND : 975 reverse
+28 VOID : 0
+29 POPPURE : 0
+
+
+cdr
when coupled with a unique list will just move the view of the underlying storage over one element - the element is still there - but we don't see it, and it means we don't have to allocate a new list (since in this new world, cdr
can allocate). But - we get some nice cache locality for this!
The first example takes 123 ms to reverse a list of 100000 - whereas the second example takes only 23 ms! In racket, the equivalent code takes somewhere between 2 and 5 ms.
+Another toy example of this optimization can be seen here, comparing Steel to Racket:
+
+(define values-to-insert
+ (map (lambda (n)
+ (cons (number->string n) n)) (range 0 10000)))
+
+(define (hash-insert-loop-test hmap values)
+ (if (null? values)
+ hmap
+ (let ([p (car values)])
+ (hash-insert-loop-test (hash-insert hmap (car p) (cdr p)) (cdr values)))))
+
+(hash-insert-loop-test (hash) values-to-insert)
+
+This snippet takes about 10-20 ms on my machine, and the equivalent body of code in Racket (replacing hash-insert
with hash-set
) takes about 8 ms. So this is a nice win. Without this optimization for hash maps, the same code took 5 seconds!
Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-get (hash 'a 10 'b 20) 'b) ;; => 20
+
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Contains direct wrappers around the Rust std::time::Instant
and std::time::Duration
modules.
+For example, to measure the time something takes:
(define t (instant/now))
+(displayln "Hello world")
+(displayln (instant/elapsed t))
+
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Get the length of the given string
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Converts a string into a list of characters.
+(string->list string?) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+### **string->lower**
+Creates a new lowercased version of the input string
+
+(string->lower string?) -> string?
+
+#### Examples
+
+```scheme
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Concatenatives all of the inputs to their string representation, separated by spaces.
+(to-string xs ...)
+> (to-string 10) ;; => "10"
+> (to-string "hello" "world") ;; => "hello world"
+
+Takes a port and reads the entire content into a string
+(read-port-to-string port) -> string?
+Checks whether the given map contains the given key. Key must be hashable.
+(hash-contains? map key) -> bool?
+> (hash-contains? (hash 'a 10 'b 20) 'a) ;; => #true
+> (hash-contains? (hash 'a 10 'b 20) 'not-there) ;; => #false
+
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the +time complexity is O(n/64). Meaning, for small lists this can be constant.
+(list-ref lst index) -> list?
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **to-string**
+Concatenatives all of the inputs to their string representation, separated by spaces.
+
+(to-string xs ...)
+
+* xs : any/c
+
+#### Examples
+```scheme
+> (to-string 10) ;; => "10"
+> (to-string "hello" "world") ;; => "hello world"
+
+Checks if a given value is an output port
+(output-port? any/c) -> bool?
+> (define output (open-output-file "foo.txt"))
+> (output-port? output) ;; => #true
+
+Takes a filename path
referring to a file to be created and returns an output port.
(open-output-file string?) -> output-port?
+> (open-output-file "foo-bar.txt") ;; => #<port>
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Get the length of the given string
+(string-length string?) -> int?
+> (string-length "apples") ;; => 6
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Gets the key
from the given map
. Raises an error if the key does not exist. hash-get
is an alias for this.
(hash-ref map key) -> any/c
+> (hash-get (hash 'a 10 'b 20) 'b) ;; => 20
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Returns a list of strings from the original string split on the whitespace
+(split-whitespace string?) -> (listof string?)
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Gets the key
from the given map
. Returns #false if the key does not exist.
(hash-try-get map key) -> (or any/c #false)
+> (hash-try-get (hash 'a 10 'b 20) 'b) ;; => 20
+> (hash-try-get (hash 'a 10 'b 20) 'does-not-exist) ;; => #false
+
+Checks if the given value can be treated as a pair. +Note - there are no improper lists in steel, so any list with at least one element +is considered a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Lists in Steel have an interface that matches those of classic schemes or lisps. +At face value, they appear to be implemented as cons cells - however, under the hood +they are actually implemented as unrolled linked lists.
+This means that for most practical purposes, interaction with lists is the same. +That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.
+Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.
+(list 10 20 30 40) ;; => '(10 20 30 40)
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Returns the number of key value pairs in the map
+(hash-length map) -> (and positive? int?)
+> (hash-length (hash 'a 10 'b 20)) ;; => 2
+
+Creates a new uppercased version of the input string
+(string->upper string?) -> string?
+> (string->upper "lower") ;; => "LOWER"
+
+Returns a new string with the leading and trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo"
+
+Checks if the input string starts with a prefix
+(starts-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Takes a filename path
referring to an existing file and returns an input port. Raises an error
+if the file does not exist
(open-input-file string?) -> input-port?
+> (open-input-file "foo-bar.txt") ;; => #<port>
+> (open-input-file "file-does-not-exist.txt")
+error[E08]: Io
+┌─ :1:2
+│
+1 │ (open-input-file "foo-bar.txt")
+│ ^^^^^^^^^^^^^^^ No such file or directory (os error 2)
+
+Checks if a given value is an input port
+(input-port? any/c) -> bool?
+> (input-port? (stdin)) ;; => #true
+> (input-port? "foo") ;; => #false
+
+Checks if the input string ends with a given suffix
+(ends-with? input pattern) -> bool?
+input : string? +pattern: string?
+> (ends-with? "foobar" "foo") ;; => #false
+> (ends-with? "foobar" "bar") ;; => #true
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+### **steel/time**
+
+
+#### steel/time
+
+Contains direct wrappers around the Rust `std::time::Instant` and `std::time::Duration` modules.
+For example, to measure the time something takes:
+
+```scheme
+(define t (instant/now))
+(displayln "Hello world")
+(displayln (instant/elapsed t))
+
+Returns a new string with the trailing whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => " foo"
+
+Converts a string into a list of characters.
+(string->list string?) -> (listof char?)
+> (string->list "hello") ;; => '(#\h #\e #\l #\l #\o)
+
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Returns a new string with the leading whitespace removed.
+(trim string?) -> string?
+> (trim " foo ") ;; => "foo "
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Returns a new hashmap with the additional key value pair added. Performs a functional update, +so the old hash map is still accessible.
+(hash-insert map key val) -> hash?
+> (hash-insert (hash 'a 10 'b 20) 'c 30)
+
+=> #<hashmap {
+'a: 10,
+'b: 20,
+'c: 30
+}>
+
+Creates a new lowercased version of the input string
+(string->lower string?) -> string?
+> (string->lower "sPonGeBoB tExT") ;; => "spongebob text"
+
+Converts a string into an int. Raises an error if the string cannot be converted to an integer.
+(string->int string?) -> int?
+> (string->int "100") ;; => 10
+> (string->int "not-an-int") ;; error
+
+Converts a string into a symbol.
+(string->symbol string?) -> symbol?
+> (string->symbol "FooBar") ;; => 'FooBar
+
+Lists in Steel have an interface that matches those of classic schemes or lisps. +At face value, they appear to be implemented as cons cells - however, under the hood +they are actually implemented as unrolled linked lists.
+This means that for most practical purposes, interaction with lists is the same. +That being said, there are no improper lists, meaning, pairs are actually just lists of two elements.
+Indexing into a list also takes O(n/64) - which means you'll get constant time indexing on small lists.
+(list 10 20 30 40) ;; => '(10 20 30 40)
+
+Checks if the given value can be treated as a pair. +Note - there are no improper lists in steel, so any list with at least one element +is considered a pair.
+(pair? any/c) -> bool?
+> (pair? '(10 20)) ;; => #true
+> (pair? '(10)) ;; => #true
+> (pair? '()) ;; => #false
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Get the third element of the list. Raises an error if the list does not have an element in the third position.
+(third l) -> any/c
+> (third '(1 2 3)) ;; => 3
+> (third '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (third '())
+│ ^^^^^^ third: index out of bounds - list did not have an element in the second position: []
+
+Checks if the list is empty
+(empty? lst) -> bool?
+> (empty? (list 1 2 3 4 5)) ;; => #false
+> (empty? '()) ;; => #true
+
+Returns a newly allocated list of the elements in the range (n, m]
+(range n m) -> (listof int?)
+> (range 0 10) ;; => '(0 1 2 3 4 5 6 7 8 9)
+
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+Returns a newly allocated list containing the vs as its elements.
+(list v ...) -> list?
+> (list 1 2 3 4 5) ;; => '(1 2 3 4 5)
+> (list (list 1 2) (list 3 4)) ;; => '((1 2) (3 4))
+
+Get the second element of the list. Raises an error if the list does not have an element in the second position.
+(second l) -> any/c
+> (second '(1 2 3)) ;; => 2
+> (second '())
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (second '())
+│ ^^^^^^ second: index out of bounds - list did not have an element in the second position: []
+### **list-ref**
+Returns the value located at the given index. Will raise an error if you try to index out of bounds.
+
+Note: Runs in time proportional to the length of the list, however lists in Steel are implemented in such a fashion that the
+time complexity is O(n/64). Meaning, for small lists this can be constant.
+
+(list-ref lst index) -> list?
+
+* lst : list?
+* index : (and/c int? positive?)
+
+#### Examples
+```scheme
+> (list-ref (list 1 2 3 4) 2) ;; => 3
+> (list-ref (range 0 100) 42) ;; => 42"
+> (list-ref (list 1 2 3 4) 10)
+error[E11]: Generic
+┌─ :1:2
+│
+1 │ (list-ref (list 1 2 3 4) 10)
+│ ^^^^^^^^ out of bounds index in list-ref - list length: 4, index: 10
+
+Inspired by Racket's higher order contracts, Steel
implements* higher order contracts to enable design by contract, made easy with a define/contract
macro for easier ergonomics. Racket makes use of a concept known as blame which seeks to identify the violating party - Steel
does not quite have fully fleshed out blame but that is a work in progress. Here are some examples:
;; Simple flat contracts
+(define/contract (test x y)
+ (->/c even? even? odd?)
+ (+ x y 1))
+
+(test 2 2) ;; => 5
+
+(define/contract (test-violation x y)
+ (->/c even? even? odd?)
+ (+ x y 1))
+
+(test-violation 1 2) ;; contract violation
+
+
+Contracts are implemented as values, so they are bound to functions. This enables the use of contract checking on functions themselves since functions can be passed around:
+;; Higher order contracts, check on application
+(define/contract (higher-order func y)
+ (->/c (->/c even? odd?) even? even?)
+ (+ 1 (func y)))
+
+(higher-order (lambda (x) (+ x 1)) 2) ;; => 4
+
+(define/contract (higher-order-violation func y)
+ (->/c (->/c even? odd?) even? even?)
+ (+ 1 (func y)))
+
+(higher-order-violation (lambda (x) (+ x 2)) 2) ;; contract violation
+
+Contracts on functions do not get checked until they are applied, so a function returning a contracted function won't cause a violation until that function is actually used:
+;; More higher order contracts, get checked on application
+(define/contract (output)
+ (->/c (->/c string? int?))
+ (lambda (x) 10))
+
+(define/contract (accept func)
+ (->/c (->/c string? int?) string?)
+ "cool cool cool")
+
+(accept (output)) ;; => "cool cool cool"
+
+;; different contracts on the argument
+(define/contract (accept-violation func)
+ (->/c (->/c string? string?) string?)
+ (func "applesauce")
+ "cool cool cool")
+
+(accept-violation (output)) ;; contract violation
+
+;; generates a function
+(define/contract (generate-closure)
+ (->/c (->/c string? int?))
+ (lambda (x) 10))
+
+;; calls generate-closure which should result in a contract violation
+(define/contract (accept-violation)
+ (->/c (->/c string? string?))
+ (generate-closure))
+
+((accept-violation) "test") ;; contract violation
+
+Perhaps a more nuanced case:
+(define/contract (output)
+ (->/c (->/c string? int?))
+ (lambda (x) 10.2))
+
+(define/contract (accept)
+ (->/c (->/c string? number?))
+ (output))
+
+
+((accept) "test") ;; contract violation 10.2 satisfies number? but _not_ int?
+
+
+
+(keyword? v) -> boolean?
+
+v: any/c
+
+
+
+(mutable-vector args ...) -> mutable-vector?
+
+
+Macro for creating a new struct, in the form of:
+(struct <struct-name> (fields ...) options ...)
+The options can consist of the following:
Single variable options (those which their presence indicates #true
)
#:mutable
#:transparent
Other options must be presented as key value pairs, and will get stored
+in the struct instance. They will also be bound to the variable
+___<struct-name>-options___
in the same lexical environment where the
+struct was defined. For example:
λ > (struct Applesauce (a b c) #:mutable #:transparent #:unrecognized-option 1234)
+λ > ___Applesauce-options___
+=> #<hashmap {
+ '#:fields: '(a b c),
+ '#:mutable: #false,
+ '#:transparent: #false,
+ '#:unrecognized-option: 1234,
+}>
+
+By default, structs are immutable, which means setter functions will not +be generated. Also by default, structs are not transparent, which means +printing them will result in an opaque struct that does not list the fields
+ +Steel contains a limited form of the syntax-rules
that scheme provides. These macros build on the small primary language constructs that exist. Consider the following:
(define-syntax or
+ (syntax-rules ()
+ [(or) #f]
+ [(or x) x]
+ [(or x y) (let ([z x])
+ (if z z y))]
+ [(or x y ...) (or x (or y ...))]))
+
+(or #f #f #t)
+
+This will actually expand into something like this
+((λ (__z)
+ (if __z __z ((λ (__z) (if __z __z #t)) #f)))
+ #f)
+
+These macros allow for a simple extension of Steel to however you see fit - defining macros in terms of the syntax rules format is fairly straightforward.
+ +In order to support a growing codebase, Steel has module support for projects spanning multiple files. Steel files can provide
values (with contracts attached) and require
modules from other files:
;; main.scm
+(require "provide.scm")
+
+(even->odd 10)
+
+
+;; provide.scm
+(provide
+ (contract/out even->odd (->/c even? odd?))
+ no-contract
+ flat-value)
+
+(define (even->odd x)
+ (+ x 1))
+
+(define (accept-number x) (+ x 10))
+
+(define (no-contract) "cool cool cool")
+(define flat-value 15)
+
+(displayln "Calling even->odd with some bad inputs but its okay")
+(displayln (even->odd 1))
+
+Here we can see if we were to run main
that it would include the contents of provide
, and only provided values would be accessible from main
. The contract is attached at the contract boundary, so inside the provide
module, you can violate the contract, but outside the module the contract will be applied.
A few notes on modules:
+A
requires B
and C
, and B
requires C
, C
will be compiled once and shared between A
and B
.
+(require (prefix-in export. ;; prefix-in will prefix all of the bound identifiers with the given prefix
+ (only-in "export.scm" ;; only-in will only introduce the identifiers listed.
+ thing-should-not-escape
+ Applesauce
+ bananas
+ new-identifier
+ my-fun-contracted-function
+ contract-out-test)))
+
+If no modifiers are given, require
-ing a module will introduce all of the provided values into the top level scope.
Macros defined using define-syntax
can be handled just like normal values:
;; main.scm
+(require "provide.scm")
+
+(foo-bar x) ;; Will expand into (displayln "Hello world")
+
+;; provide.scm
+(provide foo-bar)
+
+(define-syntax foo-bar
+ (syntax-rules ()
+ [(foo-bar x) (displayln "Hello world!")]))
+
+
+The module system will take care to keep the namespaces separate - any macros that expand +into macros that exist in the originating module will be expanded, but those will not be available to +the requiring module. In addition, macros can expand into private values (those that are not provided), and the +will still be inaccessible from the requiring module.
+ +Inspired by clojure's transducers, Steel
has a similar object that is somewhere half way in between transducers and iterators. Consider the following:
+(mapping (lambda (x) (+ x 1))) ;; => <#iterator>
+(filtering even?) ;; => <#iterator>
+(taking 15) ;; => <#iterator>
+
+(compose
+ (mapping add1)
+ (filtering odd?)
+ (taking 15)) ;; => <#iterator>
+
+Each of these expressions emit an <#iterator>
object, which means they're compatible with execute
and transduce
. Execute takes a transducer (i.e. <#iterator>
) and a collection that can be iterated (list
, vector
, or stream
) and applies the transducer.
;; Accepts lists
+(execute (mapping (lambda (x) (+ x 1))) (list 1 2 3 4 5)) ;; => '(2 3 4 5 6)
+
+;; Accepts vectors
+(execute (mapping (lambda (x) (+ x 1))) (vector 1 2 3 4 5)) ;; '#(2 3 4 5 6)
+
+;; Even accepts streams!
+(define (integers n)
+ (stream-cons n (lambda () (integers (+ 1 n)))))
+
+(execute (taking 5) (integers 0)) ;; => '(0 1 2 3 4)
+
+Transduce is just reduce
with more bells and whistles and works similarly:
;; (-> transducer reducing-function initial-value iterable)
+(transduce (mapping (lambda (x) (+ x 1))) + 0 (list 0 1 2 3)) ;; => 10
+
+Compose just combines the iterator functions and lets us avoid intermediate allocation. The composition works left to right - it chains each value through the functions and then accumulates into the output type. See the following:
+(define xf
+ (compose
+ (mapping add1)
+ (filtering odd?)
+ (taking 5)))
+
+(execute xf (range 0 100)) ;; => '(1 3 5 7 9)
+
+By default, execute outputs to the same type that was passed in. In other words, if you execute
a list
, it will return a list
. However, if you so choose, you can pass in a symbol specifying the output type of your choosing like so:
(define xf
+ (compose
+ (mapping add1)
+ (filtering odd?)
+ (taking 5)))
+
+;; Takes a list and returns a vector
+(execute xf (range 0 100) 'vector) ;; => '#(1 3 5 7 9)
+
+;; Takes a vector and returns a list
+(execute xf (vector 0 1 2 3 4 5 6 7 8 9 10) 'list) ;; => '(1 3 5 7 9)
+
+
+ Sometimes you may want to create a Steel module by calling Rust. This can be +useful to:
+Whatever the case, Steel provides facilities to create reusable modules based on
+Rust code. This process involves compiling Rust into a dylib
.
In this guide, we'll make a dylib
that will wrap the sys-info
crate. There
+are roughly 3 steps:
$STEEL_HOME/native
.#%require-dylib
.To start, create a new library using cargo
:
$ cargo new --lib steel-sys-info
+
+This should create a directory structure as follows:
+├── Cargo.toml
+├── src
+│ └── lib.rs
+
+We'll want to make this a cdylib
library for Steel, so we'll perform the following adjustments in Cargo.toml
:
crate-type
to "cdylib"
.steel-core
with the dylibs
feature as a dependency.abi_stable
as a dependency. This is required by some steel-core
+macros.[package]
+name = "steel-sys-info"
+version.workspace = true
+edition = "2021"
+
+[lib]
+name = "steel_sys_info"
+crate-type = ["cdylib"]
+
+[dependencies]
+# I'm running this example based on the `steel-sys-info` library found in the steel repo. If you're
+# running this on your own, use whichever steel version you'd like to target and pin to that.
+steel-core = { workspace = true, features = ["dylibs"] }
+abi_stable = "0.11.1"
+sys-info = "0.9.1"
+
+This means that when we run cargo build
we'll produce a shared library (.so
+file). The shared library can be loaded into other programs, Steel in our case.
For the purposes of this example, we'll create a module that wraps the MemInfo
+struct, and expose the information there. Since we'll be implementing traits
+that are defined inside the steel
crate, we'll need to create a struct to wrap
+the sys_info::MemInfo
struct:
struct MemoryInfo {
+ info: sys_info::MemInfo,
+}
+
+impl MemoryInfo {
+ fn total(&self) -> isize {
+ self.info.total as isize
+ }
+
+ fn avail(&self) -> isize {
+ self.info.avail as isize
+ }
+
+ fn free(&self) -> isize {
+ self.info.free as isize
+ }
+
+ fn buffers(&self) -> isize {
+ self.info.buffers as isize
+ }
+
+ fn cached(&self) -> isize {
+ self.info.cached as isize
+ }
+
+ fn swap_total(&self) -> isize {
+ self.info.swap_total as isize
+ }
+
+ fn swap_free(&self) -> isize {
+ self.info.swap_free as isize
+ }
+}
+Now that we've done that, we can expose this to steel by implementing the
+Custom
type for the struct, and declaring an FFIModule
:
// Using ABI Stable types is very important
+use steel::{
+ declare_module,
+ rvals::Custom,
+ steel_vm::ffi::{FFIModule, RegisterFFIFn},
+};
+
+impl Custom for MemoryInfo {}
+
+declare_module!(create_module);
+
+fn create_module() -> FFIModule {
+ let mut module = FFIModule::new("steel/sys-info");
+
+ module.register_fn("mem-info", || MemoryInfo {
+ info: sys_info::mem_info().unwrap(),
+ });
+
+ module
+ .register_fn("MemoryInfo-total", MemoryInfo::total)
+ .register_fn("MemoryInfo-avail", MemoryInfo::avail)
+ .register_fn("MemoryInfo-free", MemoryInfo::free)
+ .register_fn("MemoryInfo-buffers", MemoryInfo::buffers)
+ .register_fn("MemoryInfo-cached", MemoryInfo::cached)
+ .register_fn("MemoryInfo-swap-total", MemoryInfo::swap_total)
+ .register_fn("MemoryInfo-swap-free", MemoryInfo::swap_free);
+
+ module
+}
+The register_fn
API will perform all of the necessary coercions necessary to
+make this as safe as possible. At the end of the day, this is FFI and we are
+loading shared libraries, so there is some unsafe Rust code, however steel uses
+the underlying abi_stable
library in order to make interactions with the
+shared library as safe as possible.
To install the dylib in a location where the steel
interpreter will find it,
+from the root of the library just run:
$ cargo steel-lib
+
+This will build the crate, and copy the resulting dylib to $STEEL_HOME/native
.
To load the library, use the syntax #%require-dylib
- This operates similary
+to a standard require
, in that all of the modifiers you're used to using work,
+such as only-in
and prefix-in
. However, the argument is no longer the path
+to the library, but rather the name of the library without the extension. By
+default, the library will be named the [lib]
name used in the toml, prefixed
+with lib
.
(#%require-dylib "libsteel_sys_info"
+ (only-in mem-info
+ MemoryInfo-total
+ MemoryInfo-avail
+ MemoryInfo-free
+ MemoryInfo-buffers
+ MemoryInfo-cached
+ MemoryInfo-swap-total
+ MemoryInfo-swap-free))
+
+(provide current-memory-usage memory-usage-as-percentage)
+
+(define (current-memory-usage #:memory-info (memory-info (mem-info)))
+ (- (MemoryInfo-total memory-info) (MemoryInfo-free memory-info) (MemoryInfo-cached memory-info)))
+
+(define (memory-usage-as-percentage #:memory-info (memory-info (mem-info)))
+ (/ (current-memory-usage #:memory-info memory-info) (MemoryInfo-total memory-info)))
+
+
+This can then be installed as a library itself on the machine, and required just
+like any other library, using a cog.scm
file for the manifest.
Steel can be used as a scripting language within a Rust program. You can achieve
+this by creating a Steel Engine
object. The Engine
object allows you to
+execute Scheme code and interact with it from your Rust program. For more
+details about Engine
, see the Engine API.
The Steel Virtual Machine is provided by the steel-core
trait.
[dependencies]
+steel-core = { git="https://github.com/mattwparas/steel.git", branch = "master" }
+
+The following example runs a few expressions in a Steel Engine
and asserts
+that the results are as expected.
use steel::steel_vm::engine::Engine;
+use steel::SteelVal;
+
+fn main() {
+ let mut steel_engine = Engine::new();
+ let answer = steel_engine.run(
+ (r#"
+ (+ 1 2 3 4)
+ (+ 5 6 7 8)
+ "#),
+ );
+ assert_eq!(answer, vec![SteelVal::IntV(10), SteelVal::IntV(26)])
+}
+Creates a new engine. The Engine
is used to run Steel Scheme code. Note that
+the Engine
is not Send
or Sync
. This means it is bound to the current
+thread and cannot be shared or sent across other threads.
let mut steel_engine = Engine::new();
+Runs a Steel expression and returns the result as a Vec<SteelVal>
. If any
+error occurs, then Err(SteelErr)
is returned.
let mut steel_engine = Engine::new();
+assert_eq!(steel_engine.run("(+ 1 1)"), Ok(vec![SteelVal::IntV(2)]));
+assert!(steel_engine.run("(+ 1 undefined-identifier)").is_err());
+Repl functionality is provided by the steel-repl
crate.
[dependencies]
+steel-repl = { git="https://github.com/mattwparas/steel.git", branch = "master" }
+
+run_repl
runs the Steel repl until an IO error is encountered or the user
+exits the repl. The repl may be exited by:
(quit)
Steel Scheme function.ctrl+c
or ctrl+d
within the repl.let steel_engine = Engine::new();
+steel_repl::run_repl(steel_engine).unwrap();
+
+ The Steel Playground allows you to try Steel out directly from your browser. Visit the Steel Playground at:
+https://mattwparas.github.io/steel-playground/dev/
+The Steel Playground's environment is as follows:
+The output prints the results of all expressions. Additional
+information can be printed out with the display
and displayln
+functions.
Bytecode renders the Bytecode that Steel generates from the Steel +code. The Bytecode is a low level representation of the code that is +executed by the Steel interpretter.
+Raw AST exposes the parsed AST. This expands some macros. For example:
+(define (foo bar)
+ (+ bar bar))
+
+(define baz '(1 2 3))
+
+is actually shorthand for
+(define foo
+ (λ (bar)
+ (+ bar bar)))
+
+(define baz (quote 1 2 3))
+
+This is similar to the Raw AST but provides more detailed information.
+ +