flesh/Writing.org

422 lines
22 KiB
Org Mode
Raw Normal View History

2024-02-06 22:39:08 +00:00
#+Title: Flesh as a Language
#+Author: Ava Affine
Note: this document is best read using a dedicated ORG mode editor
* Description
2024-02-06 22:39:08 +00:00
This document offers a guide on how to write beginner or intermediate scripts using the Flesh language.
Readers should be able to run the Flesh repl to follow along with this guide and experiment with the included examples in the REPL.
* Syntax
** Data types
2024-02-06 22:39:08 +00:00
Flesh leverages the following data types:
2023-05-28 23:22:49 +00:00
- Strings: delimited by one of the following: ~' " `~
- Integers: up to 128 bit signed integers
- Floats: all floats are stored as 64 bit floats
- Booleans: ~true~ or ~false~
2023-05-28 23:22:49 +00:00
- Symbols: an un-delimited chunk of text containing alphanumerics or one of the following: ~- _ ?~
2024-02-06 22:39:08 +00:00
Symbols and Functions can contain data of any type. there is no immediate restriction on what can be set/passed to what..... However, internally Flesh is typed, and many builtin functions will get picky about what data is passed to them.
** S-Expressions
2024-02-06 22:39:08 +00:00
Flesh, 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.
2024-02-06 22:39:08 +00:00
Programs in Flesh (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 -> [] -> [] ->
2024-02-06 22:39:08 +00:00
\ \_> 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.
2024-02-06 22:39:08 +00:00
In this document, and in the Flesh 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
2024-02-06 22:39:08 +00:00
An *if form* is the most basic form of conditional evaluation offered by Flesh.
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
2024-02-06 22:39:08 +00:00
*Let* is one of the most powerful forms Flesh 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 ") ;; 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
2024-02-06 22:39:08 +00:00
(load my-shell-command) ;; exit 0 casted to true, also: requires CFG_FLESH_POSIX
(get-state-flag global-state)
(eq? (some-big-calculation) expected-result))
#+END_SRC
*** Cond
*Cond* is a function defined in the *Userlib* that acts as syntax sugar for nested *if form*s.
Given a list of pairs consisting of a condition and a form to execute *cond* will iterate trough the list evaluating the conditions in order, Upon encountering a condition that evaluates to ~true~ the corresponding form will be evaluated and its result returned, thus halting the loop so no further forms are evaluated.
If no conditions evaluate to true then *cond* won't execute anything
The argument to *cond* must be given using *quote*, otherwise it will be evaluated before being passed to *cond*, thus making it not work.
Example:
#+BEGIN_SRC lisp
(let ((list (1 2 3 4)))
(cond (q
(((gt? (car list) 2) (echo "The first number of this list is greater than 2")) ;; The first condition returns false so this expression won't be evaluated
((gt? (len list) 3) (echo "This list's length is greater than 3" )) ;; Since the second condition returns true this form will be evaluated and its result will be returned
(true (echo "This list is rather unremarkable")))))) ;; This form will be evaluated if none of the previous conditions return true
;; This is what the equivalent if form would look like
(let ((list (1 2 3 4)))
(if (gt? (car list) 2)
(echo "The first number of this list is greater than 2")
(if (gt? (len list) 3)
(echo "This list's length is greater than 3")
(echo "This list is rather unremarkable"))))
#+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
2024-02-06 22:39:08 +00:00
As stated previously: Lisp, and consequently Flesh, 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
2024-02-06 22:39:08 +00:00
In Flesh, 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
2024-02-06 22:39:08 +00:00
The following table is up to date as of Flesh 0.4.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* | *Files* |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| if | lambda | pipe | car | float | strlen | toggle | reduce | call | read-file |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| let | q / quote | load-to-string | len | sub | substr? | bool | prepend | help | append-file |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| circuit | def | load / l | cons | mul | echo | and | add-path | env | write-file |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| while | get-doc | load-with | cdr | inc | split | eq? | set | eval | exists? |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| assert | set-doc | cd | reverse | dec | input | not | map | | |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| exit | set? | fg | dq | div | concat | or | get-paths | | |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
| | | bg | pop | gte? | string | | cond | | |
|----------------+---------------+----------------+---------+--------+-----------+-----------+-----------+--------+-------------|
2023-07-30 12:26:02 -07:00
| | | | list? | 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
2024-02-06 22:39:08 +00:00
Flesh 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
2024-02-06 22:39:08 +00:00
> (help CFG_FLESH_ENV)
NAME: CFG_FLESH_ENV
ARGS: (its a variable)
DOCUMENTATION:
my env settings
CURRENT VALUE AND/OR BODY:
true
#+END_EXAMPLE
2024-02-06 22:39:08 +00:00
Every single symbol in Flesh 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.
2024-02-06 22:39:08 +00:00
It also contains considerably subpar implementations of Flesh'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
2024-02-06 22:39:08 +00:00
(See [[file:snippets/basic_minimal_configuration.f][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