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/.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..f5cccbb7e --- /dev/null +++ b/book/404.html @@ -0,0 +1,220 @@ + + +
+ + +This URL is invalid, sorry. Please use the navigation bar or search to continue.
+ +Returns the absolute value of the given input
+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 vectors 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 ...)
+b : byte?
+(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)
+
+Append two byte vectors into a new bytevector.
+(bytes-append (bytes 0 1 2) (bytes 3 4 5)) ;; (bytes 0 1 2 3 4 5)
+
+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)
+vector : bytes? +index: (and exact? int?)
+(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)
+vector : bytes? +index: (and exact? int?) +byte: 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 vectors 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 ...)
+b : byte?
+(bytevector 65 112 112 108 101)
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Checks if two characters are equal
+Requires that the two inputs are both characters, and will otherwise +raise an error.
+Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Deletes the directory
+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 Euler's number raised to the power of z.
+Gets the filename for a given path
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+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 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 a path is a directory
+Checks if a path is a file
+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
+
+Converts the given number to a string
+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)
+
+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 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. +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
+
+Gets the extension from a path
+Checks if a path exists
+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, while
+(rational? value) -> bool?
+Examples:
+(rational? (/ 0.0)) ⇒ #f
+(rational? 3.5) ⇒ #t
+(rational? 6/10) ⇒ #t
+(rational? 6/3) ⇒ #t
+
+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?
+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: []
+### **split-whitespace**
+Returns a list of strings from the original string split on the whitespace
+
+(split-whitespace string?) -> (listof string?)
+
+#### Examples
+
+```scheme
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+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
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Constructs a string from the given characters
+Converts the given string to a bytevector
+(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 string?) -> (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
+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"
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+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
+
+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: []
+
+Concatenatives 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"
+
+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 vectors 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 ...)
+b : byte?
+(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)
+
+Append two byte vectors into a new bytevector.
+(bytes-append (bytes 0 1 2) (bytes 3 4 5)) ;; (bytes 0 1 2 3 4 5)
+
+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)
+vector : bytes? +index: (and exact? int?)
+(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)
+vector : bytes? +index: (and exact? int?) +byte: 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 vectors 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 ...)
+b : byte?
+(bytevector 65 112 112 108 101)
+
+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)
+
+Converts the given string to a bytevector
+(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)
+
+Filesystem functions, mostly just thin wrappers around the std::fs
functions in
+the Rust std library.
Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Deletes the directory
+Gets the filename for a given path
+Returns canonical path with all components normalized
+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]",
+
+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, while
+(rational? value) -> bool?
+Examples:
+(rational? (/ 0.0)) ⇒ #f
+(rational? 3.5) ⇒ #t
+(rational? 6/10) ⇒ #t
+(rational? 6/3) ⇒ #t
+
+Pushes a value to the back of the vector, returning a new vector.
+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)
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+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 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. +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 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 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 absolute value of the given input
+Returns Euler's number raised to the power of z.
+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 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)
+
+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 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 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>
+
+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.
Checks if two characters are equal
+Requires that the two inputs are both characters, and will otherwise +raise an 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
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Converts the given number to a string
+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?
+input : string? +pattern: string?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Constructs a string from the given characters
+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 string?) -> (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
+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"
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+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
+
+Concatenatives 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))
+
+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 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
+ +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
+
+Returns the absolute value of the given input
+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 vectors 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 ...)
+b : byte?
+(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)
+
+Append two byte vectors into a new bytevector.
+(bytes-append (bytes 0 1 2) (bytes 3 4 5)) ;; (bytes 0 1 2 3 4 5)
+
+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)
+vector : bytes? +index: (and exact? int?)
+(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)
+vector : bytes? +index: (and exact? int?) +byte: 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 vectors 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 ...)
+b : byte?
+(bytevector 65 112 112 108 101)
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+Checks if two characters are equal
+Requires that the two inputs are both characters, and will otherwise +raise an error.
+Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Deletes the directory
+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 Euler's number raised to the power of z.
+Gets the filename for a given path
+Returns the first element of the list l.
+(first l) -> any/c
+> (first '(1 2)) ;; => 1
+> (first (cons 2 3)) ;; => 2
+
+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 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 a path is a directory
+Checks if a path is a file
+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
+
+Converts the given number to a string
+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)
+
+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 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. +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
+
+Gets the extension from a path
+Checks if a path exists
+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, while
+(rational? value) -> bool?
+Examples:
+(rational? (/ 0.0)) ⇒ #f
+(rational? 3.5) ⇒ #t
+(rational? 6/10) ⇒ #t
+(rational? 6/3) ⇒ #t
+
+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?
+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: []
+### **split-whitespace**
+Returns a list of strings from the original string split on the whitespace
+
+(split-whitespace string?) -> (listof string?)
+
+#### Examples
+
+```scheme
+(split-whitespace "apples bananas fruits veggies") ;; '("apples" "bananas" "fruits" "veggies")
+
+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
+
+Gets the port handle to stdin
+(stdin) -> input-port?
+> (stdin) ;; => #<port>
+
+Constructs a string from the given characters
+Converts the given string to a bytevector
+(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 string?) -> (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
+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"
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+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
+
+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: []
+
+Concatenatives 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"
+
+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 vectors 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 ...)
+b : byte?
+(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)
+
+Append two byte vectors into a new bytevector.
+(bytes-append (bytes 0 1 2) (bytes 3 4 5)) ;; (bytes 0 1 2 3 4 5)
+
+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)
+vector : bytes? +index: (and exact? int?)
+(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)
+vector : bytes? +index: (and exact? int?) +byte: 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 vectors 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 ...)
+b : byte?
+(bytevector 65 112 112 108 101)
+
+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)
+
+Converts the given string to a bytevector
+(string->bytes "Apple") ;; => (bytes 65 112 112 108 101)
+
+Filesystem functions, mostly just thin wrappers around the std::fs
functions in
+the Rust std library.
Recursively copies the directory from source to destination
+Creates the directory
+Check the current working directory
+Deletes the directory
+Gets the filename for a given path
+Returns canonical path with all components normalized
+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]",
+
+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, while
+(rational? value) -> bool?
+Examples:
+(rational? (/ 0.0)) ⇒ #f
+(rational? 3.5) ⇒ #t
+(rational? 6/10) ⇒ #t
+(rational? 6/3) ⇒ #t
+
+Pushes a value to the back of the vector, returning a new vector.
+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)
+
+Returns the first element of the list l.
+(car l) -> any/c
+> (car '(1 2)) ;; => 1
+> (car (cons 2 3)) ;; => 2
+
+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 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. +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 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 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 absolute value of the given input
+Returns Euler's number raised to the power of z.
+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 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)
+
+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 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 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>
+
+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.
Checks if two characters are equal
+Requires that the two inputs are both characters, and will otherwise +raise an 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
+
+Converts an integer into a string.
+(int->string int?) -> string?
+> (int->string 10) ;; => "10"
+
+Converts the given number to a string
+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?
+input : string? +pattern: string?
+> (starts-with? "foobar" "foo") ;; => #true
+> (starts-with? "foobar" "bar") ;; => #false
+
+Constructs a string from the given characters
+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 string?) -> (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
+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"
+
+Concatenates all of the given strings into one
+(string-append strs...) -> string?
+> (string-append) ;; => ""
+> (string-append "foo" "bar") ;; => "foobar"
+
+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
+
+Concatenatives 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))
+
+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.
+ +