Refactor documentation to get the language reference text out of the
way of more typical readme items.
This commit is contained in:
parent
6ab075c2bb
commit
cbd52de91b
3 changed files with 457 additions and 428 deletions
486
Readme.org
486
Readme.org
|
|
@ -11,7 +11,11 @@ The purpose of Relish is to create a highly portable, easy to integrate language
|
||||||
- Create a usable POSIX shell
|
- Create a usable POSIX shell
|
||||||
- Create usable applications/scripts
|
- Create usable applications/scripts
|
||||||
- To have quality code coverage
|
- To have quality code coverage
|
||||||
- No unsafe code outside of the POSIX module
|
- No unsafe code nessesary
|
||||||
|
|
||||||
|
Note: as of release 0.3.0, the Posix module requires unsafe code in order to interface with libc.
|
||||||
|
Users are still able to compile and use Relish without any of the unsafe code involved by removing
|
||||||
|
the POSIX module. See the Compilation section for more details.
|
||||||
|
|
||||||
** Stretch Goals
|
** Stretch Goals
|
||||||
- Create an interpreter that can be booted on one or more SOCs
|
- Create an interpreter that can be booted on one or more SOCs
|
||||||
|
|
@ -21,397 +25,31 @@ The purpose of Relish is to create a highly portable, easy to integrate language
|
||||||
* Contact
|
* Contact
|
||||||
[[https://matrix.to/#/#relish:matrix.sunnypup.io][Matrix chat: #relish:matrix.sunnypup.io]]
|
[[https://matrix.to/#/#relish:matrix.sunnypup.io][Matrix chat: #relish:matrix.sunnypup.io]]
|
||||||
|
|
||||||
* How to use
|
If you like Relish and want to support me in working on it consider donating:
|
||||||
** Syntax
|
https://ko-fi.com/avaaffine
|
||||||
*** S-Expressions
|
|
||||||
Relish fits within the LISP family of languages alongside venerable languages like Scheme or Common Lisp.
|
* Documentation
|
||||||
Lisps are *HOMOICONIC* which means that the code is data, and that there is a direct correlation between the code as written and the program as stored in memory.
|
** Writing in Relish
|
||||||
This is achieved through *S-EXPRESSIONS*. An S-Expression (or symbolic expression) is a tree of nested lists.
|
Users who are new to Relish, or who are seeking documentation on how to effectively write in Relish should reference [[file:Writing.org][the main language documentation]]. This will go over the following:
|
||||||
Programs in Relish (and most other lisps) are written with S-Expressions, and are then represented in memory as trees of nested linked lists.
|
- How Relish operates under the hood
|
||||||
|
- Relish's syntax
|
||||||
An example:
|
- Relish control flow constructs
|
||||||
#+BEGIN_SRC lisp
|
- Stdlib functions
|
||||||
(top-level element1 "element2" 3 (nested 2 5 2) (peer-nested))
|
- Builting documentation
|
||||||
#+END_SRC
|
- Common patterns in using Relish
|
||||||
|
** Using Relish as your shell
|
||||||
As in memory
|
As of version 0.3.0 Relish implements all the features of an interactive shell.
|
||||||
#+BEGIN_SRC
|
See further documentation in [[file:Shell.org][the shell documentation]]. This material goes over the following:
|
||||||
top-level -> element1 -> "element2" -> 3 -> [] -> [] ->
|
- How to start and view jobs in Relish
|
||||||
| ^-> peer-nested ->
|
- Special forms for modifying the file descriptors of new jobs
|
||||||
\-> nested -> 2 -> 5 -> 2 ->
|
- Piping and other special shell control flow
|
||||||
#+END_SRC
|
- Special snippets used to provide a first class shell experience
|
||||||
|
* Configuration
|
||||||
Each node in memory has type information and potentially a cooresponding entry in a global symbol table.
|
|
||||||
|
|
||||||
*** Data types
|
|
||||||
Relish leverages the following data types:
|
|
||||||
- Strings: delimited by ~'~, ~"~, or ~`~
|
|
||||||
- Integers: up to 128 bit signed integers
|
|
||||||
- Floats: all floats are stored as 64 bit floats
|
|
||||||
- Booleans: ~true~ or ~false~
|
|
||||||
- Symbols: an un-delimited chunk of text containing alphanumerics, ~-~, ~_~, or ~?~
|
|
||||||
|
|
||||||
Symbols and Functions can contain data of any type. there is no restriction on what can be set/passed to what.....
|
|
||||||
However, internally Relish is typed, and many builtin functions will get very picky about what types are passed to them.
|
|
||||||
|
|
||||||
*** Calling a function
|
|
||||||
S-Expressions can represent function calls in addition to trees of data. A function call is a list of data starting with a symbol that is defined to be a function:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(dothing arg1 arg2 arg3)
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Function calls are executed as soon as the tree is evaluated. See the following example:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(add 3 (add 5 2))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
In this example, ~(add 5 2)~ is evaluated first, its result is then passed to ~(add 3 ...)~. In infix form: ~3 + (5 + 2)~.
|
|
||||||
|
|
||||||
** Control flow
|
|
||||||
*** If
|
|
||||||
An *if form* is the most basic form of conditional evaluation offered by Relish.
|
|
||||||
It is a function that takes lazily evaluated arguments: a condition, a then clause, and an else clause.
|
|
||||||
If the condition evaluates to true, the then clause is evaluated and the result returned.
|
|
||||||
Otherwise the else clause is evaluated and the result is returned.
|
|
||||||
If the condition evaluates to neither true nor false (a non-boolean value) a type error is returned.
|
|
||||||
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
;; simple condition
|
|
||||||
(if true
|
|
||||||
(echo "its true!")
|
|
||||||
(echo "its false!"))
|
|
||||||
|
|
||||||
;; more advanced condition, with hypothetical data
|
|
||||||
(if (get-my-flag global-state)
|
|
||||||
(echo "my flag is already on!")
|
|
||||||
(turn-on-my-flag global-state))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** While
|
|
||||||
Another popular control flow structure is the *while loop*.
|
|
||||||
This is implemented as a condition followed by one or more bodies that are lazily evaluated only if the condition is true.
|
|
||||||
Like the *if form*, if the conditional returns a non-boolean value the *while loop* will return an error.
|
|
||||||
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(while (get-my-flag global-state) ;; if false, returns (nothing) immediately
|
|
||||||
(dothing) ;; this is evaluated
|
|
||||||
"simple token" ;; this is also evaluated
|
|
||||||
(toggle-my-flag global-state)) ;; this is also evaluated
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** Let
|
|
||||||
*Let* is one of the most powerful forms Relish offers. The first body in a call to let is a list of lists.
|
|
||||||
Specifically, a list of variable declarations that look like this: ~(name value)~.
|
|
||||||
|
|
||||||
Each successive variable definition can build off of the last one, like this: ~((step1 "hello") (step2 (concat step1 " ")) (step3 (concat step2 "world")))~.
|
|
||||||
In said example, the resulting value of step3 is "hello world". After the variable declaration list, the next form is one or more unevaluated trees of code to be evaluated.
|
|
||||||
Here is an example of a complete let statement using hypothetical data and methods:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
;; Example let statement accepts one incoming connection on a socket and sends one response
|
|
||||||
(let ((conn (accept-conn listen-socket)) ;; start the var decl list, decl first var
|
|
||||||
(hello-pfx "hello from ") ;; start the var decl list, declare second var
|
|
||||||
(hello-msg (concat hello-pfx (get-server-name))) ;; declare third var from the second var
|
|
||||||
(hello-response (make-http-response 200 hello-msg))) ;; declare fourth var from the third, end list
|
|
||||||
(log (concat "response to " (get-dst conn) ": " hello-msg)) ;; evaluates a function call using data from the first and third vars
|
|
||||||
(send-response conn hello-response)) ;; evaluates a function call using data from the first and fourth vars
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Here you can see the usefulness of being able to declare multiple variables in quick succession.
|
|
||||||
Each variable is in scope for the duration of the let statement and then dropped when the statement has concluded.
|
|
||||||
Thus, it is little cost to break complex calculations down into reusable parts.
|
|
||||||
|
|
||||||
*** Circuit
|
|
||||||
*Circuit* is useful to run a sequence of commands in order.
|
|
||||||
A call to *circuit* comprises of one or more forms in a sequence.
|
|
||||||
All forms in the call to *circuit* are expected to evaluate to a boolean.
|
|
||||||
The first form to evaluate to ~false~ halts the sequence, and false is returned.
|
|
||||||
If all forms evaluate to ~true~, ~true~ is returned.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(circuit
|
|
||||||
(load my-shell-command) ;; exit 0 casted to true, also: requires CFG_RELISH_POSIX
|
|
||||||
(get-state-flag global-state)
|
|
||||||
(eq? (some-big-calculation) expected-result))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** Not quite control flow
|
|
||||||
Several other functions use lazy evaluation of their arguments. The below list is non-exhaustive:
|
|
||||||
- toggle
|
|
||||||
- inc
|
|
||||||
- dec
|
|
||||||
|
|
||||||
These functions are mentioned here for their use with control flow.
|
|
||||||
- inc: increment a symbol by one
|
|
||||||
- dec: decrement a symbol by one
|
|
||||||
- toggle: flip a symbol from true to false, or vice versa
|
|
||||||
|
|
||||||
For more information on these functions consult the output of the help function:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
λ (help toggle)
|
|
||||||
NAME: toggle
|
|
||||||
|
|
||||||
ARGS: 1 args of any type
|
|
||||||
|
|
||||||
DOCUMENTATION:
|
|
||||||
|
|
||||||
switches a boolean symbol between true or false.
|
|
||||||
Takes a single argument (a symbol). Looks it up in the variable table.
|
|
||||||
Either sets the symbol to true if it is currently false, or vice versa.
|
|
||||||
|
|
||||||
CURRENT VALUE AND/OR BODY:
|
|
||||||
<builtin>
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** Quote and Eval
|
|
||||||
As stated previously: Lisp, and consequently Relish, is homoiconic. This means that code can be passed around (and modified) as data.
|
|
||||||
This allows us to write self programming programs, or construct entire procedures on the fly. The primary means to do so are with *quote* and *eval*.
|
|
||||||
The *quote* function allows data (code) to be passed around without evaluating it. It is used to pass unevaluated code around as data that can then be evaluated later.
|
|
||||||
To be specific, typing ~(a)~ usually results in a symbol lookup for ~a~, and then possibly even a function call. However, if we *quote* ~a~, we can pass around the symbol itself:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(quote a) ;; returns the symbol a
|
|
||||||
(quote (add 1 2)) ;; returns the following tree: (add 1 2)
|
|
||||||
(q a) ;; returns the symbol a
|
|
||||||
#+END_SRC
|
|
||||||
(note that ~quote~ may be shortened to ~q~)
|
|
||||||
|
|
||||||
We can use this to build structures that evaluate into new data:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(let ((mylist (q (add))) ;; store a list starting with the add function
|
|
||||||
(myiter 0)) ;; store an iterator starting at 0
|
|
||||||
(while (lt? myiter 4) ;; loop until the iterator >= 4
|
|
||||||
(inc myiter) ;; increment the iterator
|
|
||||||
(def mylist '' (cons mylist myiter)) ;; add to the list
|
|
||||||
(echo mylist)) ;; print the current state of the list
|
|
||||||
(echo (eval mylist))) ;; print the eval result
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Notice the final body in the let form: ~(echo (eval mylist))~
|
|
||||||
The above procedure outputs the following:
|
|
||||||
#+BEGIN_EXAMPLE
|
|
||||||
(add 1)
|
|
||||||
(add 1 2)
|
|
||||||
(add 1 2 3)
|
|
||||||
(add 1 2 3 4)
|
|
||||||
10
|
|
||||||
#+END_EXAMPLE
|
|
||||||
|
|
||||||
*** Lambda
|
|
||||||
Another form of homoiconicity is the *anonymous function*.
|
|
||||||
This is a nameless function being passed around as data.
|
|
||||||
It can be bound to a variable, or called directly.
|
|
||||||
An *anonymous function* is created with the ~lambda~ function.
|
|
||||||
|
|
||||||
Here is an example of a lambda function:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(lambda (x y) (add x y))
|
|
||||||
;; | ^ this is the function body
|
|
||||||
;; +-> this is the argument list
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
The result of the lambda call is returned as a piece of data.
|
|
||||||
It can later be called inline or bound to a variable.
|
|
||||||
|
|
||||||
Here is an example of an inline lambda call:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
((lambda (x y) (add x y)) 1 2)
|
|
||||||
#+END_SRC
|
|
||||||
This form (call) evaluates to (returns) ~3~.
|
|
||||||
|
|
||||||
Here is the lambda bound to a variable inside a let statement:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(let ((adder (lambda (x y) (add x y)))) ;; let form contains one local var
|
|
||||||
(adder 1 2)) ;; local var (the lambda 'adder') called here
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Defining variables and functions
|
|
||||||
In Relish, both variables and functions are stored in a table of symbols.
|
|
||||||
All Symbols defined with ~def~ are *GLOBAL*. The only cases when symbols are local is when they are defined as part of *let* forms or as arguments to functions.
|
|
||||||
In order to define a symbol, the following arguments are required:
|
|
||||||
- A name
|
|
||||||
- A docstring (absolutely required)
|
|
||||||
- A list of arguments (only needed to define a function)
|
|
||||||
- A value
|
|
||||||
|
|
||||||
Regarding the *value*: A function may be defined with several trees of code to execute.
|
|
||||||
In this case, the value derived from the final form in the function will be returned.
|
|
||||||
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(def my-iter 'an iterator to use in my while loop' 0) ;; a variable
|
|
||||||
(def plus-one 'adds 1 to a number' (x) (add 1 x)) ;; a function
|
|
||||||
(def multi-func 'example of multi form function'
|
|
||||||
(x y) ;; args
|
|
||||||
(inc my-iter) ;; an intermediate calculation
|
|
||||||
(add x y my-iter)) ;; the final form of the function. X+Y+MYITER is returned
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Make sure to read the *Configuration* section for information on how symbols are linked to environment variables.
|
|
||||||
|
|
||||||
*** Naming conventions
|
|
||||||
- Symbol names are case sensitive
|
|
||||||
- Symbols may contain alphanumeric characters
|
|
||||||
- Symbols may contain one or more of the following: ~- _ ?~
|
|
||||||
- The idiomatic way to name symbols is ~all-single-case-and-hyphenated~
|
|
||||||
|
|
||||||
*** Undefining variables and functions
|
|
||||||
Removing a symbol consists of a call to ~def~ with no additional arguments:
|
|
||||||
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(def my-iter 'an iterator' 0)
|
|
||||||
(inc my-iter) ;; my-iter = 1
|
|
||||||
(def my-iter) ;; removes my-iter
|
|
||||||
(inc my-iter) ;; UNDEFINED SYMBOL ERROR
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
** Builtin functions
|
|
||||||
The following table is up to date as of Relish 0.3.0. For latest information try the following:
|
|
||||||
- Call ~env~ from a fresh shell: ~(env)~
|
|
||||||
This will output all variables and functions defined
|
|
||||||
- Read the [[file:src/stl.rs][std library declaration code]]
|
|
||||||
| *Control Flow* | *Declaration* | *Shell* | *List* | *Math* | *Strings* | *Boolean* | *Userlib* | *Misc* |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| if | lambda | pipe | car | float | strlen | toggle | reduce | call |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| let | q / quote | load-to-string | len | sub | substr? | bool | prepend | help |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| circuit | def | load / l | cons | mul | echo | and | add-path | env |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| while | get-doc | load-with | cdr | inc | split | eq? | set | eval |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | set-doc | cd | reverse | dec | input | not | map | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | set? | fg | dq | div | concat | or | get-paths | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | pop | gte? | string | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | int | | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | mod | | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | exp | | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | lt? | | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | gt? | | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | add | | | | |
|
|
||||||
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
|
||||||
| | | | | lte? | | | | |
|
|
||||||
|
|
||||||
To learn how to use a given function use the ~help~ command. See the Documentation section for more information.
|
|
||||||
|
|
||||||
** Documentation
|
|
||||||
*** Tests
|
|
||||||
Most of the tests evaluate small scripts (single forms) and check their output.
|
|
||||||
Perusing them may yield answers on all the cases a given builtin can handle.
|
|
||||||
[[file:tests][The test directory]]
|
|
||||||
|
|
||||||
*** Help function
|
|
||||||
Relish is self documenting. The *help* function can be used to inspect any variable or function.
|
|
||||||
It will show the name, current value, docstring, arguments, and definition of any builtin or user defined function or variable.
|
|
||||||
#+BEGIN_EXAMPLE
|
|
||||||
> (help my-adder)
|
|
||||||
NAME: my-adder
|
|
||||||
|
|
||||||
ARGS: 2 args of any type
|
|
||||||
|
|
||||||
DOCUMENTATION:
|
|
||||||
|
|
||||||
adds two numbers
|
|
||||||
|
|
||||||
CURRENT VALUE AND/OR BODY:
|
|
||||||
args: x y
|
|
||||||
form: ((add x y))
|
|
||||||
#+END_EXAMPLE
|
|
||||||
#+BEGIN_EXAMPLE
|
|
||||||
> (help CFG_RELISH_ENV)
|
|
||||||
NAME: CFG_RELISH_ENV
|
|
||||||
|
|
||||||
ARGS: (its a variable)
|
|
||||||
|
|
||||||
DOCUMENTATION:
|
|
||||||
|
|
||||||
my env settings
|
|
||||||
|
|
||||||
CURRENT VALUE AND/OR BODY:
|
|
||||||
true
|
|
||||||
#+END_EXAMPLE
|
|
||||||
|
|
||||||
Every single symbol in Relish can be inspected in this way, unless some third party developer purposefully left a docstring blank.
|
|
||||||
|
|
||||||
*** Snippets directory
|
|
||||||
The *snippets directory* may also yield some interesting examples.
|
|
||||||
Within it are several examples that the authors and maintainers wanted to keep around but didnt know where.
|
|
||||||
It is sort of like a lint roller.
|
|
||||||
It also contains considerably subpar implementations of Relish's internals that are kept around for historical reasons.
|
|
||||||
|
|
||||||
*** Userlib
|
|
||||||
The *Userlib* was added as a script containing many valuable functions such as ~set~ and ~prepend~.
|
|
||||||
You can use it by calling it in your shell config
|
|
||||||
(See [[file:snippets/basic_minimal_configuration.rls][The minimal shell configuration example]] for more info).
|
|
||||||
|
|
||||||
** Easy patterns
|
|
||||||
This section contains examples of common composites of control flow that can be used to build more complex or effective applications
|
|
||||||
More ideas may be explored in the [[file:snippets][snippets]] directory of this project.
|
|
||||||
The author encourages any users to contribute their own personal favorites not already in this section either by adding them to the [[file:snippets][snippets]] folder, or to extend the documentation here.
|
|
||||||
|
|
||||||
*** while-let combo
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
;; myiter = (1 (2 3 4 5 6))
|
|
||||||
(def myiter 'iterator over a list' (head (1 2 3 4 5 6)))
|
|
||||||
|
|
||||||
;; iterate over each element in mylist
|
|
||||||
(while (gt? (len (cdr myiter)) 0) ;; while there are more elements to consume
|
|
||||||
(let ((elem (car myiter)) ;; elem = consumed element from myiter
|
|
||||||
(remaining (cdr myiter))) ;; remaining = rest of elements
|
|
||||||
(echo elem) ;; do a thing with the element, could be any operation
|
|
||||||
(def myiter (head remaining)))) ;; consume next element, loop
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
The while-let pattern can be used for many purposes. Above it is used to iterate over elements in a list. It can also be used to receive connections to a socket and write data to them.
|
|
||||||
|
|
||||||
*** let destructuring
|
|
||||||
~let~ is very useful for destructuring complex return types. If you have a function that may return a whole list of values you can then call it from ~let~ to consume the result data.
|
|
||||||
In this example a let form is used to destructure a call to ~head~. ~head~ returns a list consisting of ~(first-element rest-of-list)~ (for more information see ~(help head)~).
|
|
||||||
The ~let~ form starts with the output of ~head~ stored in ~head-struct~ (short for head-structured). The next variables defined are ~first~ and ~rest~ which contain individual elements from the return of the call to ~head~.
|
|
||||||
Finally, the bodies evaluated in the ~let~ form are able to operate on the head and the rest.
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
;; individually access the top of a list
|
|
||||||
(let ((head-struct (head (1 2 3))
|
|
||||||
(first (car head-struct))
|
|
||||||
(rest (cdr head-struct)))
|
|
||||||
(echo "this is 1: " first)
|
|
||||||
(echo "this is 2, 3: " rest))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
*** if-set?
|
|
||||||
One common pattern seen in bash scripts and makefiles is the set-variable-if-not-set pattern.
|
|
||||||
#+BEGIN_SRC shell
|
|
||||||
MYVAR ?= MY_SPECIAL_VALUE
|
|
||||||
#+END_SRC
|
|
||||||
Translated, can be seen below
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(if (set? myvar)
|
|
||||||
() ;; no need to do anything... or add a call here
|
|
||||||
(def myvar "my variable explanation..." "MY_SPECIAL_VALUE"))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Alternatively this combination can be used to process flags in a script or application:
|
|
||||||
#+BEGIN_SRC lisp
|
|
||||||
(if (set? myflag)
|
|
||||||
(process-flag myflag)
|
|
||||||
())
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
|
|
||||||
** Configuration
|
|
||||||
By default Relish will read from ~/.relishrc for configuration, but the default shell will also accept a filename from the RELISH_CFG_FILE environment variable.
|
By default Relish will read from ~/.relishrc for configuration, but the default shell will also accept a filename from the RELISH_CFG_FILE environment variable.
|
||||||
See [[file:snippets/basic_minimal_configuration.rls][the minimal shell configuration example]] for an example of a basic configuration file.
|
See [[file:snippets/basic_minimal_configuration.rls][the minimal shell configuration example]] for an example of a basic configuration file.
|
||||||
Other snippets, including mood-prompt and avas-laptop-prompt demonstrate more complex configurations.
|
Other snippets, including mood-prompt and avas-laptop-prompt demonstrate more complex configurations.
|
||||||
|
|
||||||
*** The configuration file
|
** The configuration file
|
||||||
The configuration file is a script containing arbitrary Relish code.
|
The configuration file is a script containing arbitrary Relish code.
|
||||||
On start, any shell which leverages the configuration code in the config module ([[file:src/run.rs][run.rs]]) will create a clean seperate context, including default configuration values, within which the standard library will be initialized.
|
On start, any shell which leverages the configuration code in the config module ([[file:src/run.rs][run.rs]]) will create a clean seperate context, including default configuration values, within which the standard library will be initialized.
|
||||||
The configuration file is evaluated and run as a standalone script and may include arbitrary executable code.
|
The configuration file is evaluated and run as a standalone script and may include arbitrary executable code.
|
||||||
|
|
@ -424,18 +62,18 @@ Errors during configuration are non-terminal. In such a case any defaults which
|
||||||
- The standard library will then be re-processed and re-added to the symbol table with new configuration.
|
- The standard library will then be re-processed and re-added to the symbol table with new configuration.
|
||||||
- Variables and functions defined during configuration will carry over to the user/script interpreter, allowing the user to load any number of custom functions and variables.
|
- Variables and functions defined during configuration will carry over to the user/script interpreter, allowing the user to load any number of custom functions and variables.
|
||||||
|
|
||||||
*** Configuration Values
|
** Configuration Values
|
||||||
- CFG_RELISH_POSIX (default false): when true, enables POSIX style job control.
|
- CFG_RELISH_POSIX (default false): when true, enables POSIX style job control.
|
||||||
- CFG_RELISH_ENV (default true): when true, interpreter's variable table and environment variable table are kept in sync.
|
- CFG_RELISH_ENV (default true): when true, interpreter's variable table and environment variable table are kept in sync.
|
||||||
- CFG_RELISH_L_PROMPT (default 'λ'): a function that is called with no arguments to output the left hand of the prompt
|
- CFG_RELISH_L_PROMPT (default 'λ'): a function that is called with no arguments to output the left hand of the prompt
|
||||||
- CFG_RELISH_R_PROMPT (default ''): a function that is called with no arguments to output the right hand of the prompt
|
- CFG_RELISH_R_PROMPT (default ''): a function that is called with no arguments to output the right hand of the prompt
|
||||||
- CFG_RELISH_PROMPT_DELIMITER (default '>'): a function that is called with no arguments to output the delimiter separating prompt from user input
|
- CFG_RELISH_PROMPT_DELIMITER (default '>'): a function that is called with no arguments to output the delimiter separating prompt from user input
|
||||||
|
|
||||||
*** Prompt design
|
** Prompt design
|
||||||
For an example of prompt design see [[file:snippets/mood-prompt.rls][the mood prompt]]
|
For an example of prompt design see [[file:snippets/mood-prompt.rls][the mood prompt]]
|
||||||
For a more complex example see [[file:snippets/avas-laptop-prompt.rls][Ava's laptop prompt]]
|
For a more complex example see [[file:snippets/avas-laptop-prompt.rls][Ava's laptop prompt]]
|
||||||
|
|
||||||
*** Further configuration
|
** Further configuration
|
||||||
Further configuration can be done by loading scripts that contain more functions and data to evaluate.
|
Further configuration can be done by loading scripts that contain more functions and data to evaluate.
|
||||||
Variables and functions defined in an external script loaded by your interpreter will persist in the symbol table.
|
Variables and functions defined in an external script loaded by your interpreter will persist in the symbol table.
|
||||||
|
|
||||||
|
|
@ -443,85 +81,80 @@ Variables and functions defined in an external script loaded by your interpreter
|
||||||
(call "my-extra-library-functions.rls")
|
(call "my-extra-library-functions.rls")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
** Using Relish as your shell
|
* Compilation
|
||||||
As of version 0.3.0 Relish implements all the features of an interactive shell.
|
Compiling Relish is as simple as kicking off a build with Cargo.
|
||||||
See further documentation in [[file:Shell.org][the shell documentation]].
|
#+BEGIN_EXAMPLE sh
|
||||||
|
|
||||||
** Compilation
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
cargo build
|
cargo build
|
||||||
#+END_SRC
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
This will produce a binary at file:target/debug/relish which includes all of the features relish has to offer. This provides a REPL with a full interactive shell that also can manage variables in the Unix environment. It is possible to compile a smaller REPL that does not interact with environment variables and does not offer any shell features. Simply pass the ~--no-default-features~ flag to cargo:
|
||||||
|
|
||||||
|
#+BEGIN_EXAMPLE sh
|
||||||
|
cargo build --no-default-features
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
In order to run Relish it is recommended to run the resulting binary at file:target/debug/relish.
|
||||||
|
|
||||||
** Testing
|
** Testing
|
||||||
#+BEGIN_SRC sh
|
Relish has upwards of 120 unit tests. Full functionality of the core AST with lex, parse, and eval routines as well as the symbol table and relevant code can be tested with Cargo. Unit tests are also included for the standard library of data manipulations and special cases around quote/eval and lambda use:
|
||||||
|
#+BEGIN_EXAMPLE sh
|
||||||
cargo test
|
cargo test
|
||||||
#+END_SRC
|
#+END_EXAMPLE
|
||||||
|
|
||||||
** Running (the main shell)
|
Userlib tests can be triggered by loading the userlib as well as its test suite directly from a ~cargo run~ invocation:
|
||||||
#+BEGIN_SRC sh
|
#+BEGIN_EXAMPLE sh
|
||||||
cargo run src/bin/main.rs
|
cargo run snippets/userlib.rls snippets/userlib-tests.rls
|
||||||
#+END_SRC
|
#+END_EXAMPLE
|
||||||
|
|
||||||
* The codebase
|
* The codebase
|
||||||
** The [[file:tests][tests directory]]
|
** The [[file:tests][tests directory]]
|
||||||
Start here if you are new.
|
Start here if you are new.
|
||||||
|
|
||||||
*** [[file:tests/test_eval.rs][Eval tests]]
|
*** [[file:tests/test_eval.rs][Eval tests]]
|
||||||
These are particularly easy to read and write tests.
|
These are particularly easy to read and write tests.
|
||||||
They primarily cover execution paths in the evaluation process.
|
They primarily cover execution paths in the evaluation process.
|
||||||
|
|
||||||
*** [[file:tests/test_func.rs][Func tests]]
|
*** [[file:tests/test_func.rs][Func tests]]
|
||||||
These tests extend the eval tests to cover the co-recursive nature between eval and func calls.
|
These tests extend the eval tests to cover the co-recursive nature between eval and func calls.
|
||||||
|
|
||||||
*** [[file:tests/test_lex.rs][Lex tests]]
|
*** [[file:tests/test_lex.rs][Lex tests]]
|
||||||
These tests verify the handling of syntax.
|
These tests verify the handling of syntax.
|
||||||
|
|
||||||
*** Lib tests: (tests/test_lib*)
|
*** Lib tests: (tests/test_lib*)
|
||||||
These tests are unique per stdlib module and work to prove the functionality of builtin functions in the language.
|
These tests are unique per stdlib module and work to prove the functionality of builtin functions in the language.
|
||||||
|
|
||||||
** [[file:src][Source directory]]
|
** [[file:src][Source directory]]
|
||||||
This directory contains all of the user facing code in relish.
|
This directory contains all of the user facing code in relish.
|
||||||
|
|
||||||
Just a few entries of note:
|
Just a few entries of note:
|
||||||
*** [[file:src/segment.rs][Segment module]]
|
*** [[file:src/segment.rs][Segment module]]
|
||||||
This file lays out the data structures that the interpreter operates on.
|
This file lays out the data structures that the interpreter operates on.
|
||||||
Representation of code trees, traversals, and type annotations all live here.
|
Representation of code trees, traversals, and type annotations all live here.
|
||||||
|
It provides the core representation of data used in Relish, and could provide supplementary refrence material for users seeking a deeper understanding of how their code is stored in memory.
|
||||||
*** [[file:src/lib.rs][lib.rs]]
|
*** [[file:src/lib.rs][lib.rs]]
|
||||||
This defines a library that can be included to provide an interpreter interface within any Rust project.
|
This defines a library that can be included to provide an interpreter interface within any Rust project.
|
||||||
The components defined here can certainly be used to support language development for other LISP (or non LISP) langauges.`
|
The components defined here can certainly be used to support language development for other LISP (or non LISP) langauges. An external project may use or not use any number of these components.
|
||||||
Your project can use or not use any number of these components.
|
|
||||||
|
|
||||||
*** [[file:src/sym.rs][Symbol module]]
|
*** [[file:src/sym.rs][Symbol module]]
|
||||||
This file contains all code related to symbol expansion and function calling.
|
This file contains all code related to symbol expansion and function calling.
|
||||||
The types defined in this file include SymTable, Args, Symbol, and more.
|
The types defined in this file include SymTable, Args, Symbol, and more.
|
||||||
Code to call Lambda functions also exists in here.
|
Code to call Lambda functions also exists in here.
|
||||||
|
|
||||||
*** [[file:src/run.rs][Run module]]
|
*** [[file:src/run.rs][Run module]]
|
||||||
This file contains functions which load and run the configuration file script.
|
This file contains functions which load and run the configuration file script.
|
||||||
For more information see the configuraiton section above in this Readme.
|
For more information see the configuraiton section above in this Readme.
|
||||||
|
|
||||||
*** [[file:src/stl.rs][Standard library module]]
|
*** [[file:src/stl.rs][Standard library module]]
|
||||||
This defines the ~static_stdlib~ function and the ~dynamic_stdlib~ function.
|
This defines the ~static_stdlib~ function and the ~dynamic_stdlib~ function.
|
||||||
The ~static_stdlib~ function loads all symbols in the standard library which do not need further configuration into the symbol table.
|
The ~static_stdlib~ function loads all symbols in the standard library which do not need further configuration into the symbol table.
|
||||||
The ~dyanmic_stdlib~ function loads all symbols in the standard library which *do* need configuration into the symbol table.
|
The ~dynamic_stdlib~ function loads all symbols in the standard library which *do* need configuration into the symbol table.
|
||||||
The ~dynamic_stdlib~ function uses variables saved in the symbol table to configure the functions and variables it loads.
|
The ~dynamic_stdlib~ function uses variables saved in the symbol table to configure the functions and variables it loads.
|
||||||
This file also contains default configuration values.
|
This module also contains definitions for the default configuration values.
|
||||||
Any new addition to the stdlib must make its way here to be included in the main shell (and any other shell using the included stdlib functions).
|
Any new addition to the stdlib must make its way here to be included in the main shell (and any other shell using the included stdlib functions).
|
||||||
You may choose to override these functions if you would like to include your own special functions in your own special interpreter, or if you would like to pare down the stdlib to a lighter subet of what it is.
|
You may choose to override these functions if you would like to include your own special functions in your own special interpreter, or if you would like to pare down the stdlib to a lighter subet of what it is.
|
||||||
|
|
||||||
You can view the code for standard library functions in [[file:src/stl/][the standard library directory]].
|
You can view the code for standard library functions in [[file:src/stl/][the standard library directory]].
|
||||||
|
|
||||||
*** [[file:src/bin/][binary directory]]
|
*** [[file:src/bin/][binary directory]]
|
||||||
This contains any executable target of this project. Notably [[file:src/bin/relish.rs][the main shell]].
|
This contains any executable target of this project. Notably [[file:src/bin/relish.rs][the main shell]].
|
||||||
|
|
||||||
* Current Status / TODO list
|
* Current Status / TODO list
|
||||||
Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer.
|
Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer.
|
||||||
Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered.
|
Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered.
|
||||||
|
|
||||||
|
** DONE Alpha Release
|
||||||
|
(See tag: v0.2.0)
|
||||||
|
** DONE Beta tasks
|
||||||
|
(See tag: v0.3.0)
|
||||||
** TODO v1.0 tasks
|
** TODO v1.0 tasks
|
||||||
- Create an introductory presentation
|
|
||||||
- put a ko-fi in the readme or something
|
|
||||||
- Post to relevant channels
|
- Post to relevant channels
|
||||||
- Version flag
|
- Version flag
|
||||||
- islist type query
|
- islist type query
|
||||||
|
|
@ -539,8 +172,7 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
||||||
- Rename to Flesh
|
- Rename to Flesh
|
||||||
- Make an icon if you feel like it
|
- Make an icon if you feel like it
|
||||||
- Post release to relevant channels
|
- Post release to relevant channels
|
||||||
|
** TODO v1.1 tasks
|
||||||
** TODO v1.1 tasks (Stable)
|
|
||||||
- finish stretch goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
- finish stretch goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
||||||
- Stl boolean assert builtin
|
- Stl boolean assert builtin
|
||||||
- History length configurable (env var?)
|
- History length configurable (env var?)
|
||||||
|
|
@ -554,7 +186,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
||||||
- probably more escapes in the lexer
|
- probably more escapes in the lexer
|
||||||
- a snippet with a bunch of color constants
|
- a snippet with a bunch of color constants
|
||||||
- Search delim configurable
|
- Search delim configurable
|
||||||
|
|
||||||
** TODO v1.2 release tasks
|
** TODO v1.2 release tasks
|
||||||
- Emacs syntax highlighting and/or LSP implementation
|
- Emacs syntax highlighting and/or LSP implementation
|
||||||
- Bindings for the simplest possible UI library?
|
- Bindings for the simplest possible UI library?
|
||||||
|
|
@ -567,6 +198,5 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
||||||
- TCP Listener
|
- TCP Listener
|
||||||
- HTTP Listener
|
- HTTP Listener
|
||||||
- UDP Listener
|
- UDP Listener
|
||||||
|
|
||||||
* Special thanks
|
* Special thanks
|
||||||
Special thanks to [[https://nul.srht.site/]['Underscore Nul']] for consulting with me in the early stages of this project. Meeting my goal of only using safe rust (with the exception of the posix module) would have been a much bigger challenge if not for having someone to experiment on design ideas with.
|
Special thanks to [[https://nul.srht.site/]['Underscore Nul']] for consulting with me in the early stages of this project. Meeting my goal of only using safe rust (with the exception of the posix module) would have been a much bigger challenge if not for having someone to experiment on design ideas with.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#+Title: Relish as a Shell
|
#+Title: Relish as a Shell
|
||||||
#+Author: Ava Hahn
|
#+Author: Ava Hahn
|
||||||
|
|
||||||
|
Note: this document is best read using a dedicated ORG mode editor
|
||||||
|
|
||||||
* Description
|
* Description
|
||||||
This document outlines the ways that Relish can be used as a shell for daily administration or scripting purposes. Readers should have already read through the [[file:Readme.org][general Relish documentation]]. With the exception of the ~circuit~ function, all facilities introduced in this document apply only to relish interpreters compiled with the [[file:/src/stl/posix.rs][POSIX module]] enabled and ~CFG_RELISH_POSIX~ set at load time.
|
This document outlines the ways that Relish can be used as a shell for daily administration or scripting purposes. Readers should have already read through the [[file:Readme.org][general Relish documentation]]. With the exception of the ~circuit~ function, all facilities introduced in this document apply only to relish interpreters compiled with the [[file:/src/stl/posix.rs][POSIX module]] enabled and ~CFG_RELISH_POSIX~ set at load time.
|
||||||
|
|
||||||
|
|
|
||||||
397
Writing.org
Normal file
397
Writing.org
Normal file
|
|
@ -0,0 +1,397 @@
|
||||||
|
#+Title: Relish as a Language
|
||||||
|
#+Author: Ava Hahn
|
||||||
|
|
||||||
|
Note: this document is best read using a dedicated ORG mode editor
|
||||||
|
|
||||||
|
* Description
|
||||||
|
This document offers a guide on how to write beginner or intermediate scripts using the Relish language.
|
||||||
|
Readers should be able to run the Relish repl to follow along with this guide and experiment with the included examples in the REPL.
|
||||||
|
|
||||||
|
* Syntax
|
||||||
|
** Data types
|
||||||
|
Relish leverages the following data types:
|
||||||
|
- Strings: delimited by ~'~, ~"~, or ~`~
|
||||||
|
- Integers: up to 128 bit signed integers
|
||||||
|
- Floats: all floats are stored as 64 bit floats
|
||||||
|
- Booleans: ~true~ or ~false~
|
||||||
|
- Symbols: an un-delimited chunk of text containing alphanumerics, ~-~, ~_~, or ~?~
|
||||||
|
|
||||||
|
Symbols and Functions can contain data of any type. there is no immediate restriction on what can be set/passed to what..... However, internally Relish is typed, and many builtin functions will get picky about what types are passed to them.
|
||||||
|
** S-Expressions
|
||||||
|
Relish, like other LISPs, is *HOMOICONIC* which means that user written code entered at the REPL is data, and that there is a direct correlation between the code as written and the program as stored in memory.
|
||||||
|
This is achieved through *S-EXPRESSIONS*. An S-Expression (or symbolic expression) is simply a list of elements surrounded by parenthesis. Within this list are elements of any data, and potentially nested s-expressions (sometimes referred to as sexprs). Each s-expression represents one statement, or line of code to be evaluated. To evaluate an s-expression also requires that any nested s-expressions are first evaluated.
|
||||||
|
|
||||||
|
Programs in Relish (and most other lisps) are written with S-Expressions, and are then represented in memory as linked lists of heterogenous data. In memory, a linked list may contain addresses to other linked lists.
|
||||||
|
|
||||||
|
An example:
|
||||||
|
#+BEGIN_EXAMPLE lisp
|
||||||
|
(top-level element1 "element2" 3 (nested 2 5 2) (peer-nested))
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
As a tree:
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
top-level -> element1 -> "element2" -> 3 -> [] -> [] ->
|
||||||
|
\ \_> peer-nested ->
|
||||||
|
\_> nested -> 2 -> 5 -> 2 ->
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
As in memory:
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
ADDR 1: top-level, element1, "element2", 3, ADDR 2, ADDR 3
|
||||||
|
ADDR 2: peer-nested
|
||||||
|
ADDR 3: nested, 2, 5, 2
|
||||||
|
#+END_EXAMPLE
|
||||||
|
In order to evaluate the code stored at ADDR 1, first ADDR 2 and ADDR 3 must be evaluated.
|
||||||
|
|
||||||
|
In evaluation, a list is digested and its simplest possible form is returned. A list may represent a function call, for which the result is the simplest possible form. The list may contain variables, for which evaluation must replace with the defined values of those variables. The top level list must wait as the deepest nested lists are evaluated and their simplest possible forms return upwards to then be evaluated as members of the top level s-expression.
|
||||||
|
|
||||||
|
In this document, and in the Relish interpreter, s-expressions may be referred to as 'forms'.
|
||||||
|
|
||||||
|
** Calling a function
|
||||||
|
S-Expressions can represent function calls in addition to trees of data. A function call is a list of data starting with a symbol that is defined to be a function:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(dothing arg1 arg2 arg3)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Function calls are executed as soon as the tree is evaluated. See the following example:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(add 3 (add 5 2))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
In this example, ~(add 5 2)~ is evaluated first, its result is then passed to ~(add 3 ...)~. In infix format the same statement can be written as: ~3 + (5 + 2)~.
|
||||||
|
|
||||||
|
** Control flow
|
||||||
|
*** If
|
||||||
|
An *if form* is the most basic form of conditional evaluation offered by Relish.
|
||||||
|
It is a function that takes lazily evaluated arguments: a condition, a then clause, and an else clause.
|
||||||
|
If the condition evaluates to true, the then clause is evaluated and the result returned. Otherwise the else clause is evaluated and the result is returned. If the condition evaluates to neither true nor false, (a non-boolean value), a type error is returned.
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
;; simple condition
|
||||||
|
(if true
|
||||||
|
(echo "its true!")
|
||||||
|
(echo "its false!"))
|
||||||
|
|
||||||
|
;; more advanced condition, with hypothetical data
|
||||||
|
(if (get-my-flag global-state)
|
||||||
|
(echo "my flag is already on!")
|
||||||
|
(turn-on-my-flag global-state))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** While
|
||||||
|
Another popular control flow structure is the *while loop*.
|
||||||
|
This is implemented as a condition followed by one or more bodies that are lazily evaluated only if the condition is true.
|
||||||
|
Like the *if form*, if the conditional returns a non-boolean value the *while loop* will return an error.
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(while (get-my-flag global-state) ;; if false, returns (nothing) immediately
|
||||||
|
(dothing) ;; this is evaluated
|
||||||
|
"simple token" ;; this is also evaluated
|
||||||
|
(toggle-my-flag global-state)) ;; this is also evaluated
|
||||||
|
;; if (get-my-flag global-state) still evaluates to true
|
||||||
|
;; we go right beack to (dothing) and go over the elements again
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** Let
|
||||||
|
*Let* is one of the most powerful forms Relish offers.
|
||||||
|
|
||||||
|
The first body in a call to let is a list of lists. Specifically, a list of variable declarations that look like this: ~(name value)~. Each successive variable definition can build off of the last one, like this: ~((step1 "hello") (step2 (concat step1 " ")) (step3 (concat step2 "world")))~. The resulting value of step3 is "hello world".
|
||||||
|
|
||||||
|
After the variable declaration list, the next form is one or more unevaluated trees of code to be evaluated. Here is an example of a complete let statement using hypothetical data and methods:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
;; Example let statement accepts one incoming connection on a socket and sends one response
|
||||||
|
(let ((conn (accept-conn listen-socket)) ;; start the var decl list, decl first var
|
||||||
|
(hello-pfx "hello from ") ;; start the var decl list, declare second var
|
||||||
|
(hello-msg (concat hello-pfx (get-server-name))) ;; declare third var from the second var
|
||||||
|
(hello-response (make-http-response 200 hello-msg))) ;; declare fourth var from the third, end list
|
||||||
|
(log (concat "response to " (get-dst conn) ": " hello-msg)) ;; evaluates a function call using data from the first and third vars
|
||||||
|
(send-response conn hello-response)) ;; evaluates a function call using data from the first and fourth vars
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Here the reader can see how useful it is to being able to declare multiple variables in quick succession. Each variable is in scope for the duration of the let statement and then dropped when the statement has concluded. Thus, it is little cost to break complex calculations down into reusable parts.
|
||||||
|
|
||||||
|
*** Circuit
|
||||||
|
*Circuit* is useful to run a sequence of commands in order.
|
||||||
|
A call to *circuit* comprises of one or more forms in a sequence.
|
||||||
|
All forms in the call to *circuit* are expected to evaluate to a boolean.
|
||||||
|
The first form to evaluate to ~false~ halts the sequence, and false is returned.
|
||||||
|
If all forms evaluate to ~true~, ~true~ is returned.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(circuit
|
||||||
|
(load my-shell-command) ;; exit 0 casted to true, also: requires CFG_RELISH_POSIX
|
||||||
|
(get-state-flag global-state)
|
||||||
|
(eq? (some-big-calculation) expected-result))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** Not quite control flow
|
||||||
|
Several other functions use lazy evaluation of their arguments. The below list is non-exhaustive:
|
||||||
|
- toggle
|
||||||
|
- inc
|
||||||
|
- dec
|
||||||
|
|
||||||
|
These functions are mentioned here for their use with control flow.
|
||||||
|
- inc: increment a symbol by one
|
||||||
|
- dec: decrement a symbol by one
|
||||||
|
- toggle: flip a symbol from true to false, or vice versa
|
||||||
|
|
||||||
|
For more information on these functions consult the output of the help function:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
λ (help toggle)
|
||||||
|
NAME: toggle
|
||||||
|
|
||||||
|
ARGS: 1 args of any type
|
||||||
|
|
||||||
|
DOCUMENTATION:
|
||||||
|
|
||||||
|
switches a boolean symbol between true or false.
|
||||||
|
Takes a single argument (a symbol). Looks it up in the variable table.
|
||||||
|
Either sets the symbol to true if it is currently false, or vice versa.
|
||||||
|
|
||||||
|
CURRENT VALUE AND/OR BODY:
|
||||||
|
<builtin>
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
*** Quote and Eval
|
||||||
|
As stated previously: Lisp, and consequently Relish, is homoiconic. This means that code can be passed around (and modified) as data.
|
||||||
|
This allows us to write self programming programs, or construct entire procedures on the fly. The primary means to do so are with *quote* and *eval*.
|
||||||
|
The *quote* function allows data (code) to be passed around without evaluating it. It is used to pass unevaluated code around as data that can then be evaluated later.
|
||||||
|
To be specific, typing ~(a)~ usually results in a symbol lookup for ~a~, and then possibly even a function call. However, if we *quote* ~a~, we can pass around the symbol itself:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(quote a) ;; returns the symbol a
|
||||||
|
(quote (add 1 2)) ;; returns the following tree: (add 1 2)
|
||||||
|
(q a) ;; returns the symbol a
|
||||||
|
#+END_SRC
|
||||||
|
(note that ~quote~ may be shortened to ~q~)
|
||||||
|
|
||||||
|
We can use this to build structures that evaluate into new data:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(let ((mylist (q (add))) ;; store a list starting with the add function
|
||||||
|
(myiter 0)) ;; store an iterator starting at 0
|
||||||
|
(while (lt? myiter 4) ;; loop until the iterator >= 4
|
||||||
|
(inc myiter) ;; increment the iterator
|
||||||
|
(def mylist '' (cons mylist myiter)) ;; add to the list
|
||||||
|
(echo mylist)) ;; print the current state of the list
|
||||||
|
(echo (eval mylist))) ;; print the eval result
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Notice the final body in the let form: ~(echo (eval mylist))~
|
||||||
|
The above procedure outputs the following:
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
(add 1)
|
||||||
|
(add 1 2)
|
||||||
|
(add 1 2 3)
|
||||||
|
(add 1 2 3 4)
|
||||||
|
10
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
*** Lambda
|
||||||
|
Another form of homoiconicity is the *anonymous function*.
|
||||||
|
This is a nameless function being passed around as data.
|
||||||
|
It can be bound to a variable, or called directly.
|
||||||
|
An *anonymous function* is created with the ~lambda~ function.
|
||||||
|
|
||||||
|
Here is an example of a lambda function:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(lambda (x y) (add x y))
|
||||||
|
;; | ^ this is the function body
|
||||||
|
;; +-> this is the argument list
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The result of the lambda call is returned as a piece of data.
|
||||||
|
It can later be called inline or bound to a variable.
|
||||||
|
|
||||||
|
Here is an example of an inline lambda call:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
((lambda (x y) (add x y)) 1 2)
|
||||||
|
#+END_SRC
|
||||||
|
This form (call) evaluates to (returns) ~3~.
|
||||||
|
|
||||||
|
Here is the lambda bound to a variable inside a let statement:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(let ((adder (lambda (x y) (add x y)))) ;; let form contains one local var
|
||||||
|
(adder 1 2)) ;; local var (the lambda 'adder') called here
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
* Defining variables and functions
|
||||||
|
In Relish, both variables and functions are stored in a table of symbols.
|
||||||
|
All Symbols defined with ~def~ are *GLOBAL*. The only cases when symbols are local is when they are defined as part of *let* forms or as arguments to functions.
|
||||||
|
In order to define a symbol, the following inputs are required:
|
||||||
|
- A name
|
||||||
|
- A docstring (absolutely required)
|
||||||
|
- A list of arguments (only needed to define a function)
|
||||||
|
- A value
|
||||||
|
|
||||||
|
Regarding the *value*: A function may be defined with several sexprs to evaluate.
|
||||||
|
In this case, the value derived from the final form in the function will be returned.
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(def my-iter 'an iterator to use in my while loop' 0) ;; a variable
|
||||||
|
(def plus-one 'adds 1 to a number' (x) (add 1 x)) ;; a function
|
||||||
|
(def multi-func 'example of multi form function'
|
||||||
|
(x y) ;; args
|
||||||
|
(inc my-iter) ;; an intermediate calculation
|
||||||
|
(add x y my-iter)) ;; the final form of the function. X+Y+MYITER is returned
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Make sure to read the *Configuration* section for information on how symbols are linked to environment variables.
|
||||||
|
|
||||||
|
** Naming conventions
|
||||||
|
- Symbol names are case sensitive
|
||||||
|
- Symbols may contain alphanumeric characters
|
||||||
|
- Symbols may contain one or more of the following: ~- _ ?~
|
||||||
|
- The idiomatic way to name symbols is ~all-single-case-and-hyphenated~
|
||||||
|
|
||||||
|
** Undefining variables and functions
|
||||||
|
Removing a symbol consists of a call to ~def~ with no additional arguments:
|
||||||
|
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(def my-iter 'an iterator' 0)
|
||||||
|
(inc my-iter) ;; my-iter = 1
|
||||||
|
(def my-iter) ;; removes my-iter
|
||||||
|
(inc my-iter) ;; UNDEFINED SYMBOL ERROR
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
* Builtin functions
|
||||||
|
The following table is up to date as of Relish 0.3.0. For latest information try the following:
|
||||||
|
- Call ~env~ from a fresh shell: ~(env)~
|
||||||
|
This will output all variables and functions defined
|
||||||
|
- Read the [[file:src/stl.rs][std library declaration code]]
|
||||||
|
| *Control Flow* | *Declaration* | *Shell* | *List* | *Math* | *Strings* | *Boolean* | *Userlib* | *Misc* |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| if | lambda | pipe | car | float | strlen | toggle | reduce | call |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| let | q / quote | load-to-string | len | sub | substr? | bool | prepend | help |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| circuit | def | load / l | cons | mul | echo | and | add-path | env |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| while | get-doc | load-with | cdr | inc | split | eq? | set | eval |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | set-doc | cd | reverse | dec | input | not | map | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | set? | fg | dq | div | concat | or | get-paths | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | pop | gte? | string | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | int | | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | mod | | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | exp | | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | lt? | | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | gt? | | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | add | | | | |
|
||||||
|
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------|
|
||||||
|
| | | | | lte? | | | | |
|
||||||
|
|
||||||
|
To learn how to use a given function use the ~help~ command. See the Documentation section for more information.
|
||||||
|
|
||||||
|
* Documentation
|
||||||
|
** Tests
|
||||||
|
Most of the tests evaluate small scripts (single forms) and check their output.
|
||||||
|
Perusing them may yield answers on all the cases a given builtin can handle.
|
||||||
|
[[file:tests][The test directory]]
|
||||||
|
|
||||||
|
** Help function
|
||||||
|
Relish is self documenting. The *help* function can be used to inspect any variable or function.
|
||||||
|
It will show the name, current value, docstring, arguments, and definition of any builtin or user defined function or variable.
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
> (help my-adder)
|
||||||
|
NAME: my-adder
|
||||||
|
|
||||||
|
ARGS: 2 args of any type
|
||||||
|
|
||||||
|
DOCUMENTATION:
|
||||||
|
|
||||||
|
adds two numbers
|
||||||
|
|
||||||
|
CURRENT VALUE AND/OR BODY:
|
||||||
|
args: x y
|
||||||
|
form: ((add x y))
|
||||||
|
#+END_EXAMPLE
|
||||||
|
#+BEGIN_EXAMPLE
|
||||||
|
> (help CFG_RELISH_ENV)
|
||||||
|
NAME: CFG_RELISH_ENV
|
||||||
|
|
||||||
|
ARGS: (its a variable)
|
||||||
|
|
||||||
|
DOCUMENTATION:
|
||||||
|
|
||||||
|
my env settings
|
||||||
|
|
||||||
|
CURRENT VALUE AND/OR BODY:
|
||||||
|
true
|
||||||
|
#+END_EXAMPLE
|
||||||
|
|
||||||
|
Every single symbol in Relish can be inspected in this way, unless some third party developer purposefully left a docstring blank.
|
||||||
|
|
||||||
|
* Snippets directory
|
||||||
|
The *snippets directory* may also yield some interesting examples.
|
||||||
|
Within it are several examples that the authors and maintainers wanted to keep around but didnt know where.
|
||||||
|
It is sort of like a lint roller.
|
||||||
|
It also contains considerably subpar implementations of Relish's internals that are kept around for historical reasons.
|
||||||
|
|
||||||
|
** Userlib
|
||||||
|
The *Userlib* was added as a script containing many valuable functions such as ~set~ and ~prepend~.
|
||||||
|
You can use it by calling it in your shell config
|
||||||
|
(See [[file:snippets/basic_minimal_configuration.rls][The minimal shell configuration example]] for more info).
|
||||||
|
|
||||||
|
* Common patterns
|
||||||
|
This section contains common composites of control flow that may be used to build more complex or effective applications. More ideas may be explored in the [[file:snippets][snippets]] directory of this project.
|
||||||
|
The author encourages any users to contribute their own personal favorites not already in this section either by adding them to the [[file:snippets][snippets]] folder, or to extend the documentation here.
|
||||||
|
|
||||||
|
** while-let combo
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
;; myiter = (1 (2 3 4 5 6))
|
||||||
|
(def myiter 'iterator over a list' (head (1 2 3 4 5 6)))
|
||||||
|
|
||||||
|
;; iterate over each element in mylist
|
||||||
|
(while (gt? (len (cdr myiter)) 0) ;; while there are more elements to consume
|
||||||
|
(let ((elem (car myiter)) ;; elem = consumed element from myiter
|
||||||
|
(remaining (cdr myiter))) ;; remaining = rest of elements
|
||||||
|
(echo elem) ;; do a thing with the element, could be any operation
|
||||||
|
(def myiter (head remaining)))) ;; consume next element, loop
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
The while-let pattern can be used for many purposes. Above it is used to iterate over elements in a list. It can also be used to receive connections to a socket and write data to them.
|
||||||
|
|
||||||
|
** let destructuring
|
||||||
|
~let~ is very useful for destructuring complex return types. If you have a function that may return a whole list of values you can then call it from ~let~ to consume the result data.
|
||||||
|
In this example a let form is used to destructure a call to ~head~. ~head~ returns a list consisting of ~(first-element rest-of-list)~ (for more information see ~(help head)~).
|
||||||
|
The ~let~ form starts with the output of ~head~ stored in ~head-struct~ (short for head-structured). The next variables defined are ~first~ and ~rest~ which contain individual elements from the return of the call to ~head~.
|
||||||
|
Finally, the bodies evaluated in the ~let~ form are able to operate on the head and the rest.
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
;; individually access the top of a list
|
||||||
|
(let ((head-struct (head (1 2 3))
|
||||||
|
(first (car head-struct))
|
||||||
|
(rest (cdr head-struct)))
|
||||||
|
(echo "this is 1: " first)
|
||||||
|
(echo "this is 2, 3: " rest))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
** if-set?
|
||||||
|
One common pattern seen in bash scripts and makefiles is the set-variable-if-not-set pattern.
|
||||||
|
#+BEGIN_SRC shell
|
||||||
|
MYVAR ?= MY_SPECIAL_VALUE
|
||||||
|
#+END_SRC
|
||||||
|
Translated, can be seen below
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(if (set? myvar)
|
||||||
|
() ;; no need to do anything... or add a call here
|
||||||
|
(def myvar "my variable explanation..." "MY_SPECIAL_VALUE"))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Alternatively this combination can be used to process flags in a script or application:
|
||||||
|
#+BEGIN_SRC lisp
|
||||||
|
(if (set? myflag)
|
||||||
|
(process-flag myflag)
|
||||||
|
())
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue