26 | Stable contains a version of LFE that is known to be fully reliable and used in production for years.
27 |
28 |
29 |
30 | To build the latest stable release, make sure you are on the `master` branch:
31 |
32 | ```bash
33 | git checkout master
34 | ```
35 |
36 |
37 |
38 |
39 | Unstable
40 |
41 |
42 | Unstable contains LFE that is under active development, has not been released, or has not had extensive use and testing in production environments.
43 |
44 |
45 |
46 | To build the latest unstable LFE, make sure you are on the `develop` branch:
47 |
48 | ```bash
49 | git checkout develop
50 | ```
51 |
52 | LFE is just a set of Erlang libraries, so like an Erlang project, the source code needs to be compiled to `.beam` files. Running `make` in the `lfe` directory will do that:
53 |
54 | ```bash
55 | make
56 | ```
57 |
--------------------------------------------------------------------------------
/src/introduction/lfe.md:
--------------------------------------------------------------------------------
1 | # About LFE
2 |
3 | LFE, or "Lisp Flavoured Erlang", is a Lisp-2 (thus similar to Common Lisp, and
4 | MACLISP before that) that runs on top of the Erlang VM or "BEAM". It is the
5 | oldest sibling of the many BEAM languages that have been released since its
6 | inception (e.g., Elixir, Joxa, Luerl, Erlog, Haskerl, Clojerl, Hamler, etc.).
7 |
8 | As a Lisp-2, LFE has different namespaces for functions and variables.
9 | Furthermore, LFE has Erlang's notion of arity, so functions with different
10 | arity may share the same name.
11 |
12 | Lisps are great, but the thing that makes LFE so incredible is that it runs
13 | on the Erlang VM and is 100% compatible with Core Erlang.
14 |
--------------------------------------------------------------------------------
/src/macros/README.md:
--------------------------------------------------------------------------------
1 | # Macros
2 |
3 | As LFE code is just lists so it seems natural that there should be some way to evaluate data structures. In this chapter we will look at some ways of doing this.
4 |
--------------------------------------------------------------------------------
/src/macros/backquote.md:
--------------------------------------------------------------------------------
1 | ## The Backquote Macro
2 |
3 | The backquote macro makes it possible to build lists and tuples from templates. Used by itself a backquote is equivalent to a regular quote:
4 |
5 | ```lisp
6 | lfe> `(a b c)
7 | (a b c)
8 | ```
9 |
10 | Like a regular quote, a backquote alone protects its arguments from evaluation. The advantage of backquote is that it is possible to turn on evaluation inside forms which are backquoted using ``,`` (comma or "unquote") and ``,@`` (comma-at or "unquote splice").[^1] When something is prefixed with a comma it will be evaluated. For example:
11 |
12 | ```lisp
13 | lfe> (set (tuple a b) #(1 2))
14 | #(1 2)
15 | lfe> `(a is ,a and b is ,b)
16 | (a is 1 and b is 2)
17 | lfe> `#(a ,a b ,b)
18 | #(a 1 b 2)
19 | ```
20 |
21 | Quoting works with both lists and tuples. The backquote actually expands to an expression which builds the structure the templates describes. For example, the following
22 |
23 | ```lisp
24 | `(a is ,a and b is ,b)
25 | ```
26 |
27 | expands to
28 |
29 | ```lisp
30 | (list 'a 'is a 'and b 'is b)
31 | ```
32 |
33 | and
34 |
35 | ```lisp
36 | `(a . ,a)
37 | ```
38 |
39 | expands to
40 |
41 | ```lisp
42 | (cons 'a a)
43 | ```
44 |
45 | This:
46 |
47 | ```lisp
48 | `#(a ,a b ,b)
49 | ```
50 |
51 | expands to
52 |
53 | ```lisp
54 | (tuple 'a a 'b b)
55 | ```
56 |
57 | They are very useful in macros as we can write a macro definitions which look like the expansions they produce. For example we could define the ``unless`` from the previous section as:
58 |
59 | ```lisp
60 | (defmacro unless
61 | ((cons test body) `(if (not ,test) (progn ,@body))))
62 | ```
63 |
64 | Here we have extended it allow multiple forms in the body. Comma-at is like comma but splices its argument which should be a list. So for example:
65 |
66 | ```lisp
67 | lfe> (macroexpand '(unless (test x) (first-do) (second-do)) $ENV)
68 | (if (not (test x)) (progn (first-do) (second-do)))
69 | ```
70 |
71 | As the backquote macro expands to the expression which would build the template it is also very useful in patterns as we can use a template to describe the pattern. Here is the [Converting Temperature](../sequential/example.md) example rewritten to use backquote in both the patterns and constructors:
72 |
73 | ```lisp
74 | (defun f->c
75 | ((`#(,name #(C ,temp)))
76 | ;; No conversion needed
77 | `#(,name #(C ,temp)))
78 | ((`#(,name #(F ,temp)))
79 | ;; Do the conversion
80 | `#(,name #(C ,(/ (* (- temp 32) 5) 9)))))
81 |
82 | (defun print-temp
83 | ((`#(,name #(C ,temp)))
84 | (lfe_io:format "~-15w ~w C~n" `(,name ,temp))))
85 | ```
86 |
87 | Using the backquote macro also makes it much easier to build expressions which we can evaluate with ``eval``. So if we want to import values into the expression to evaluate we can do it like this:
88 |
89 | ```lisp
90 | lfe> (set expr '(* x y)) ;Expression to evaluate
91 | (* x y)
92 | lfe> (eval `(let ((x 17) (y 42)) ,expr))
93 | 714
94 | ```
95 |
96 | ----
97 |
98 | #### Notes
99 |
100 | [^1] In LFE the backquote is a normal macro and is expanded at the same time as other macros. When they are parsed `` `thing`` becomes ``(backquote thing)``, ``,thing`` becomes ``(comma thing)`` and ``,@thing`` becomes ``(comma-at thing)``.
101 |
--------------------------------------------------------------------------------
/src/macros/eval.md:
--------------------------------------------------------------------------------
1 | ## Eval
2 |
3 | The form ``eval`` takes an LFE data structure and evaluates it as an expression and then returns the value:
4 |
5 | ```lisp
6 | lfe> (eval 15)
7 | 15
8 | lfe> (eval '(+ 1 2 3 4))
9 | 10
10 | lfe> (eval '(list 'a 'b 'c))
11 | (a b c)
12 | ```
13 |
14 | Using ``eval`` is one way way to merge lists and code. However, it is not a very good way:
15 |
16 | - It is inefficient as the input expression is evaluated by the LFE interpreter, ``lfe_eval``. This is much slower than running compiled code.
17 |
18 | - The expression is evaluated without a lexical context. So calling ``eval`` inside a ``let`` does not allow the evaluated expression to refer to variables bound by the ``let``:
19 |
20 | ```lisp
21 | lfe> (set expr '(* x y)) ; Expression to evaluate
22 | (* x y)
23 | lfe> (let ((x 17) (y 42)) (eval expr))
24 | exception error: #(unbound_symb x)
25 |
26 | ```
27 |
28 | Well, this is not quite true. If we "reverse" the code and build a ``let`` expression which imports and binds the variables and then call ``eval`` on it we can access the variables:
29 |
30 | ```lisp
31 | lfe> (eval (list 'let (list (list 'x 17) (list 'y 42)) expr))
32 | 714
33 | ```
34 |
--------------------------------------------------------------------------------
/src/macros/macros.md:
--------------------------------------------------------------------------------
1 | ## Macros
2 |
3 | The most common way to write programs in LFE that write programs is by defining macros. *Macros* work by transformation. When you define a macro you say how a call to it should be translated, the *macro expansion*, which is then done automatically by the compile. The code generated by the macro then becomes an integral part of the program.
4 |
5 | Macros are usually defined with the `defmacro` form. It resembles a `defun` but instead of defining the value a call returns it defines how the call should be expanded. For example a macro `unless` which returns an expression which evaluates the `body` if the `test` is `false`:
6 |
7 | ```lisp
8 | (defmacro unless (test body)
9 | (list 'if (list 'not test) body))
10 | ```
11 |
12 | So if we type into the top-level REPL:
13 |
14 | ```lisp
15 | lfe> (unless (> 3 4) 'yes)
16 | yes
17 | lfe> (unless (is_number 'foo) 'number)
18 | number
19 | ```
20 |
21 | To test a macro and look at its expansion we can use the function `macroexpand` which takes a macro call and generates its expansion[^1]:
22 |
23 | ```lisp
24 | lfe> (macroexpand '(unless (> 3 4) 'yes) $ENV)
25 | (if (not (> 3 4)) 'yes)
26 | ```
27 |
28 | If a macro call expands into another macro call then the compiler or the top-level REPL it will keep expanding until the expansion is no longer a macro. It is the expansion of the macro call which is then inserted into the code. So in the example of `unless` it is the resultant `if` form which is then inserted into the code.
29 |
30 | ----
31 |
32 | #### Notes
33 |
34 | [^1] The extra argument `$ENV` is needed as this is where the REPL keeps its locally defined functions and macros and we need to tell `macroexpand` where to look.
35 |
--------------------------------------------------------------------------------
/src/records/README.md:
--------------------------------------------------------------------------------
1 | # Records
2 |
3 | [forthcoming]
4 |
5 | Ticket: [https://github.com/lfe/tutorial/issues/9](https://github.com/lfe/tutorial/issues/9)
6 |
--------------------------------------------------------------------------------
/src/records/headers.md:
--------------------------------------------------------------------------------
1 | ## Header Files
2 |
3 | [forthcoming]
4 |
5 | Ticket: [https://github.com/lfe/tutorial/issues/9](https://github.com/lfe/tutorial/issues/9)
6 |
--------------------------------------------------------------------------------
/src/records/mods.md:
--------------------------------------------------------------------------------
1 | ## Modularising
2 |
3 | [forthcoming]
4 |
5 | Ticket: [https://github.com/lfe/tutorial/issues/9](https://github.com/lfe/tutorial/issues/9)
6 |
--------------------------------------------------------------------------------
/src/records/records.md:
--------------------------------------------------------------------------------
1 | ## Records
2 |
3 | A record is defined as:
4 |
5 | ```lisp
6 | (defrecord ...)
7 | ```
8 | The ``defrecord`` macro creates a number of new macro for creating, matching and accessing the fields of the record.
9 |
10 | For example:
11 |
12 | ```lisp
13 | (defrecord message-to to-name message)
14 | ```
15 |
16 | The record data is a tuple which is exactly equivalent to:
17 |
18 | ```lisp
19 | #(message-to )
20 | ```
21 |
22 | Creating record is done with a ``make-`` macro, and is best illustrated by an example:
23 |
24 | ```lisp
25 | (make-message-to message "hello" to-name 'fred)
26 | ```
27 |
28 | This will create the tuple:
29 |
30 | ```lisp
31 | #(message-to fred "hello")
32 | ```
33 |
34 | Note that you don't have to worry about the order you assign values to the various parts of the records when you create it. The advantage of using records is that by placing their definitions in header files you can conveniently define interfaces which are easy to change. For example, if you want to add a new field to the record, you will only have to change the code where the new field is used and not at every place the record is referred to. If you leave out a field when creating a record, it will get the value of the atom ``undefined``. (*manual*)
35 |
36 | Pattern matching with records is very similar to creating records. For example inside a function clause, case or receive:
37 |
38 | ```lisp
39 | (match-message-to to-name the-name message the-message)
40 | ```
41 |
42 | will match a ``message-to`` record and extract the ``to-name`` field in to the variable ``the-name`` and the ``message`` field in to the variable ``the-message``. It is equivalent to writing:
43 |
44 | ```lisp
45 | (tuple 'message-to the-name the-message)
46 | ```
47 |
48 | Accessing the fields of a record is done through macros which are created when the record is defined. For example the variable ``my-message`` contains a ``message-to`` record then
49 |
50 | ```lisp
51 | (message-to-message my-message)
52 | ```
53 |
54 | will return the value of the ``message`` field of ``my-message`` and
55 |
56 | ```lisp
57 | (set-message-to-message my-message "goodbye")
58 | ```
59 |
60 | will return a new record where the ``message`` field now has the value ``"goodbye"``.
61 |
--------------------------------------------------------------------------------
/src/redirects/docs.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/redirects/mdbook.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/robust/README.md:
--------------------------------------------------------------------------------
1 | # Robustness
2 |
3 | There are several things which are wrong with the [messenger example](../concurrent/example.md) from the previous chapter. For example if a node where a user is logged on goes down without doing a log off, the user will remain in the server's ``user-list`` but the client will disappear thus making it impossible for the user to log on again as the server thinks the user already logged on.
4 |
5 | Or what happens if the server goes down in the middle of sending a message leaving the sending client hanging for ever in the ``await-result`` function?
6 |
--------------------------------------------------------------------------------
/src/robust/errors.md:
--------------------------------------------------------------------------------
1 | ## Error Handling
2 |
3 | Before we go into details of the supervision and error handling in an LFE system, we need see how LFE processes terminate, or in LFE terminology, *exit*.
4 |
5 | A process which executes ``(exit 'normal)`` or simply runs out of things to do has a *normal* exit.
6 |
7 | A process which encounters a runtime error (e.g. divide by zero, bad match, trying to call a function which doesn't exist etc) exits with an error, i.e. has an *abnormal* exit. A process which executes ``(exit reason)`` where ``reason`` is any LFE term except the atom ``normal``, also has an abnormal exit.
8 |
9 | An LFE process can set up links to other LFE processes. If a process calls ``(link other-pid)`` it sets up a bidirectional link between itself and the process called ``other-pid``. When a process terminates, it sends something called a *signal* to all the processes it has links to.
10 |
11 | The signal carries information about the pid it was sent from and the exit reason.
12 |
13 | The default behaviour of a process which receives a normal exit is to ignore the signal.
14 |
15 | The default behaviour in the two other cases (i.e. abnormal exit) above is to bypass all messages to the receiving process and to kill it and to propagate the same error signal to the killed process' links. In this way you can connect all processes in a transaction together using links and if one of the processes exits abnormally, all the processes in the transaction will be killed. As we often want to create a process and link to it at the same time, there is a special BIF, ``spawn_link`` which does the same as spawn, but also creates a link to the spawned process.
16 |
17 | Now an example of the ping pong example using links to terminate "pong":
18 |
19 | ```lisp
20 | (defmodule tut20
21 | (export (start 1) (ping 2) (pong 0)))
22 |
23 | (defun ping (n pong-pid)
24 | (link pong-pid)
25 | (ping1 n pong-pid))
26 |
27 | (defun ping1
28 | ((0 pong-pid)
29 | (exit 'ping))
30 | ((n pong-pid)
31 | (! pong-pid (tuple 'ping (self)))
32 | (receive
33 | ('pong (lfe_io:format "Ping received pong~n" ())))
34 | (ping1 (- n 1) pong-pid)))
35 |
36 | (defun pong ()
37 | (receive
38 | ((tuple 'ping ping-pid)
39 | (lfe_io:format "Pong received ping~n" ())
40 | (! ping-pid 'pong)
41 | (pong))))
42 |
43 | (defun start (ping-node)
44 | (let ((pong-pid (spawn 'tut20 'pong ())))
45 | (spawn ping-node 'tut20 'ping (list 3 pong-pid))))
46 | ```
47 |
48 | ```lisp
49 | (s1@bill)lfe> (tut20:start 's2@kosken)
50 | Pong received ping
51 | <5627.43.0>
52 | Ping received pong
53 | Pong received ping
54 | Ping received pong
55 | Pong received ping
56 | Ping received pong
57 | ```
58 |
59 | This is a slight modification of the ping pong program where both processes are spawned from the same ``start/1`` function, where the "ping" process can be spawned on a separate node. Note the use of the ``link`` BIF. "Ping" calls ``(exit `ping)`` when it finishes and this will cause an exit signal to be sent to "pong" which will also terminate.
60 |
61 | It is possible to modify the default behaviour of a process so that it does not get killed when it receives abnormal exit signals, but all signals will be turned into normal messages on the format ``#(EXIT from-pid reason)`` and added to the end of the receiving processes message queue. This behaviour is set by:
62 |
63 | ```lisp
64 | (process_flag 'trap_exit 'true)
65 | ```
66 |
67 | There are several other process flags, see *erlang manual*. Changing the default behaviour of a process in this way is usually not done in standard user programs, but is left to the supervisory programs in OTP (but that's another tutorial). However we will modify the ping pong program to illustrate exit trapping.
68 |
69 | ```lisp
70 | (defmodule tut21
71 | (export (start 1) (ping 2) (pong 0)))
72 |
73 | (defun ping (n pong-pid)
74 | (link pong-pid)
75 | (ping1 n pong-pid))
76 |
77 | (defun ping1
78 | ((0 pong-pid)
79 | (exit 'ping))
80 | ((n pong-pid)
81 | (! pong-pid (tuple 'ping (self)))
82 | (receive
83 | ('pong (lfe_io:format "Ping received pong~n" ())))
84 | (ping1 (- n 1) pong-pid)))
85 |
86 | (defun pong ()
87 | (process_flag 'trap_exit 'true)
88 | (pong1))
89 |
90 | (defun pong1 ()
91 | (receive
92 | ((tuple 'ping ping-pid)
93 | (lfe_io:format "Pong received ping~n" ())
94 | (! ping-pid 'pong)
95 | (pong1))
96 | ((tuple 'EXIT from reason)
97 | (lfe_io:format "Pong exiting, got ~p~n"
98 | (list (tuple 'EXIT from reason))))))
99 |
100 | (defun start (ping-node)
101 | (let ((pong-pid (spawn 'tut21 'pong ())))
102 | (spawn ping-node 'tut21 'ping (list 3 pong-pid))))
103 | ```
104 |
105 | ```lisp
106 | (s1@bill)lfe> (tut21:start 's2@kosken)
107 | <5627.44.0>
108 | Pong received ping
109 | Ping received pong
110 | Pong received ping
111 | Ping received pong
112 | Pong received ping
113 | Ping received pong
114 | Pong exiting, got #(EXIT <5627.44.0> ping)
115 | ```
116 |
--------------------------------------------------------------------------------
/src/robust/example.md:
--------------------------------------------------------------------------------
1 | ## Example: Robust Messenger
2 |
3 | Now we return to the messenger program and add changes which make it more robust:
4 |
5 | ```lisp
6 | ;;; Message passing utility.
7 | ;;; User interface:
8 | ;;; (logon name)
9 | ;;; One user at a time can log in from each Erlang node in the
10 | ;;; system messenger: and choose a suitable name. If the name
11 | ;;; is already logged in at another node or if someone else is
12 | ;;; already logged in at the same node, login will be rejected
13 | ;;; with a suitable error message.
14 | ;;; (logoff)
15 | ;;; Logs off anybody at at node
16 | ;;; (message to-name message)
17 | ;;; sends message to to-name. Error messages if the user of this
18 | ;;; function is not logged on or if to-name is not logged on at
19 | ;;; any node.
20 | ;;;
21 | ;;; One node in the network of Erlang nodes runs a server which maintains
22 | ;;; data about the logged on users. The server is registered as "messenger"
23 | ;;; Each node where there is a user logged on runs a client process registered
24 | ;;; as "mess-client"
25 | ;;;
26 | ;;; Protocol between the client processes and the server
27 | ;;; ----------------------------------------------------
28 | ;;;
29 | ;;; To server: (tuple client-pid 'logon user-name)
30 | ;;; Reply #(messenger stop user-exists-at-other-node) stops the client
31 | ;;; Reply #(messenger logged-on) logon was successful
32 | ;;;
33 | ;;; When the client terminates for some reason
34 | ;;; To server: (tuple 'EXIT client-pid reason)
35 | ;;;
36 | ;;; To server: (tuple client-pid 'message-to to-name message) send a message
37 | ;;; Reply: #(messenger stop you-are-not-logged-on) stops the client
38 | ;;; Reply: #(messenger receiver-not-found) no user with this name logged on
39 | ;;; Reply: #(messenger sent) message has been sent (but no guarantee)
40 | ;;;
41 | ;;; To client: (tuple 'message-from name message)
42 | ;;;
43 | ;;; Protocol between the "commands" and the client
44 | ;;; ----------------------------------------------
45 | ;;;
46 | ;;; Started: (messenger:client server-node name)
47 | ;;; To client: logoff
48 | ;;; To client: (tuple 'message-to to-name message)
49 | ;;;
50 | ;;; Configuration: change the server-node() function to return the
51 | ;;; name of the node where the messenger server runs
52 |
53 | (defmodule messenger
54 | (export (start-server 0) (server 0)
55 | (logon 1) (logoff 0) (message 2) (client 2)))
56 |
57 | ;;; Change the function below to return the name of the node where the
58 | ;;; messenger server runs
59 |
60 | (defun server-node () 'messenger@renat)
61 |
62 | ;;; This is the server process for the "messenger"
63 | ;;; the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
64 |
65 | (defun server ()
66 | (process_flag 'trap_exit 'true)
67 | (server ()))
68 |
69 | (defun server (user-list)
70 | (receive
71 | ((tuple from 'logon name)
72 | (let ((new-user-list (server-logon from name user-list)))
73 | (server new-user-list)))
74 | ((tuple 'EXIT from _)
75 | (let ((new-user-list (server-logoff from user-list)))
76 | (server new-user-list)))
77 | ((tuple from 'message-to to message)
78 | (server-transfer from to message user-list)
79 | (lfe_io:format "list is now: ~p~n" (list user-list))
80 | (server user-list))))
81 |
82 | ;;; Start the server
83 |
84 | (defun start-server ()
85 | (register 'messenger (spawn 'messenger 'server '())))
86 |
87 | ;;; Server adds a new user to the user list
88 |
89 | (defun server-logon (from name user-list)
90 | ;; Check if logged on anywhere else
91 | (if (lists:keymember name 2 user-list)
92 | (progn ;Reject logon
93 | (! from #(messenger stop user-exists-at-other-node))
94 | user-list)
95 | (progn ;Add user to the list
96 | (! from #(messenger logged-on))
97 | (link from)
98 | (cons (tuple from name) user-list))))
99 |
100 | ;;; Server deletes a user from the user list
101 |
102 | (defun server-logoff (pid user-list)
103 | (lists:keydelete pid 1 user-list))
104 |
105 | ;;; Server transfers a message between user
106 |
107 | (defun server-transfer (from-pid to-name message user-list)
108 | ;; Check that the user is logged on and who he is
109 | (case (lists:keyfind from-pid 1 user-list)
110 | ((tuple from-pid from-name)
111 | (server-transfer from-pid from-name to-name message user-list))
112 | ('false
113 | (! from-pid #(messenger stop you-are-not-logged-on)))))
114 |
115 | ;;; If the user exists, send the message
116 |
117 | (defun server-transfer (from-pid from-name to-name message user-list)
118 | ;; Find the receiver and send the message
119 | (case (lists:keyfind to-name 2 user-list)
120 | ((tuple to-pid to-name)
121 | (! to-pid (tuple 'message-from from-name message))
122 | (! from-pid #(messenger sent)))
123 | ('false
124 | (! from-pid #(messenger receiver-not-found)))))
125 |
126 | ;;; User Commands
127 |
128 | (defun logon (name)
129 | (case (whereis 'mess-client)
130 | ('undefined
131 | (let ((client (spawn 'messenger 'client (list (server-node) name))))
132 | (register 'mess-client client)))
133 | (_ 'already-logged-on)))
134 |
135 | (defun logoff ()
136 | (! 'mess-client 'logoff))
137 |
138 | (defun message (to-name message)
139 | (case (whereis 'mess-client) ;Test if the client is running
140 | ('undefined
141 | 'not-logged-on)
142 | (_ (! 'mess-client (tuple 'message-to to-name message))
143 | 'ok)))
144 |
145 | ;;; The client process which runs on each server node
146 |
147 | (defun client (server-node name)
148 | (! (tuple 'messenger server-node) (tuple (self) 'logon name))
149 | (await-result)
150 | (client server-node))
151 |
152 | (defun client (server-node)
153 | (receive
154 | ('logoff
155 | (exit 'normal))
156 | ((tuple 'message-to to-name message)
157 | (! (tuple 'messenger server-node)
158 | (tuple (self) 'message-to to-name message))
159 | (await-result))
160 | ((tuple 'message-from from-name message)
161 | (lfe_io:format "Message from ~p: ~p~n" (list from-name message))))
162 | (client server-node))
163 |
164 | ;;; Wait for a response from the server
165 |
166 | (defun await-result ()
167 | (receive
168 | ((tuple 'messenger 'stop why) ;Stop the client
169 | (lfe_io:format "~p~n" (list why))
170 | (exit 'normal))
171 | ((tuple 'messenger what) ;Normal response
172 | (lfe_io:format "~p~n" (list what)))
173 | (after 5000
174 | (lfe_io:format "No response from server~n" ())
175 | (exit 'timeout))))
176 | ```
177 |
178 | We have added the following changes:
179 |
180 | The messenger server traps exits. If it receives an exit signal, ``#(EXIT from reason)`` this means that a client process has terminated or is unreachable because:
181 |
182 | - the user has logged off (we have removed the "logoff" message),
183 | - the network connection to the client is broken,
184 | - the node on which the client process resides has gone down, or
185 | - the client processes has done some illegal operation.
186 |
187 | If we receive an exit signal as above, we delete the tuple, ``#(from name)`` from the servers ``user-list`` using the ``server-logoff`` function. If the node on which the server runs goes down, an exit signal (automatically generated by the system), will be sent to all of the client processes: ``#(EXIT messenger-pid noconnection)`` causing all the client processes to terminate.
188 |
189 | We have also introduced a timeout of five seconds in the ``await-result`` function. I.e. if the server does not reply within five seconds (5000 ms), the client terminates. This is really only needed in the logon sequence before the client and server are linked.
190 |
191 | An interesting case is if the client was to terminate before the server links to it. This is taken care of because linking to a non-existent process causes an exit signal, ``#(EXIT from noproc)``, to be automatically generated as if the process terminated immediately after the link operation.
192 |
--------------------------------------------------------------------------------
/src/robust/timeouts.md:
--------------------------------------------------------------------------------
1 | ## Timeouts
2 |
3 | Before improving the messenger program we will look into some general principles, using the ping pong program as an example. Recall that when "ping" finishes, it tells "pong" that it has done so by sending the atom ``finished`` as a message to "pong" so that "pong" could also finish. Another way to let "pong" finish, is to make "pong" exit if it does not receive a message from ping within a certain time, this can be done by adding a *timeout* to pong as shown in the following example:
4 |
5 | ```lisp
6 | (defmodule tut19
7 | (export (start-ping 1) (start-pong 0) (ping 2) (pong 0)))
8 |
9 | (defun ping
10 | ((0 pong-node)
11 | (lfe_io:format "Ping finished~n" ()))
12 | ((n pong-node)
13 | (! (tuple 'pong pong-node) (tuple 'ping (self)))
14 | (receive
15 | ('pong (lfe_io:format "Ping received pong~n" ())))
16 | (ping (- n 1) pong-node)))
17 |
18 | (defun pong ()
19 | (receive
20 | ((tuple 'ping ping-pid)
21 | (lfe_io:format "Pong received ping~n" ())
22 | (! ping-pid 'pong)
23 | (pong))
24 | (after 5000
25 | (lfe_io:format "Pong timed out~n" ()))))
26 |
27 | (defun start-pong ()
28 | (register 'pong (spawn 'tut19 'pong ())))
29 |
30 | (defun start-ping (pong-node)
31 | (spawn 'tut19 'ping (list 3 pong-node)))
32 | ```
33 |
34 | After we have compiled this and copied the ``tut19.beam`` file to the necessary directories:
35 |
36 | On (pong@kosken):
37 |
38 | ```lisp
39 | (pong@kosken)lfe> (tut19:start-pong)
40 | true
41 | Pong received ping
42 | Pong received ping
43 | Pong received ping
44 | Pong timed out
45 | ```
46 |
47 | On (ping@gollum):
48 |
49 | ```lisp
50 | (ping@renat)lfe> (tut19:start-ping 'pong@kosken)
51 | <0.40.0>
52 | Ping received pong
53 | Ping received pong
54 | Ping received pong
55 | Ping finished
56 | ```
57 |
58 | The timeout is set in:
59 |
60 | ```lisp
61 | (defun pong ()
62 | (receive
63 | ((tuple 'ping ping-pid)
64 | (lfe_io:format "Pong received ping~n" ())
65 | (! ping-pid 'pong)
66 | (pong))
67 | (after 5000
68 | (lfe_io:format "Pong timed out~n" ()))))
69 | ```
70 |
71 | We start the timeout ``(after 5000)`` when we enter ``receive``. The timeout is cancelled if ``#(ping ping-pid)`` is received. If ``#(ping ping-pid)`` is not received, the actions following the timeout will be done after 5000 milliseconds. ``after`` must be last in the ``receive``, i.e. preceded by all other message reception specifications in the ``receive``. Of course we could also call a function which returned an integer for the timeout:
72 |
73 | ```lisp
74 | (after (pong-timeout)
75 | ```
76 |
77 | In general, there are better ways than using timeouts to supervise parts of a distributed Erlang system. Timeouts are usually appropriate to supervise external events, for example if you have expected a message from some external system within a specified time. For example, we could use a timeout to log a user out of the messenger system if they have not accessed it, for example, in ten minutes.
78 |
--------------------------------------------------------------------------------
/src/sequential/README.md:
--------------------------------------------------------------------------------
1 | # Sequential Programming
2 |
3 | In this chapter we will look at the basics of sequential programming in LFE.
4 |
--------------------------------------------------------------------------------
/src/sequential/atoms.md:
--------------------------------------------------------------------------------
1 | ## Atoms
2 |
3 | Atoms are another data type in LFE. They are words, for example `charles`, `centimetre`, `inch` and `ok`. Atoms are similar to symbols in Lisp except that are simply names, nothing else. They are not like variables which can have a value.
4 |
5 | Enter the next program (file: `tut4.lfe`) which could be useful for converting from inches to centimetres and vice versa:
6 |
7 | ```lisp
8 | (defmodule tut4
9 | (export (convert 2)))
10 |
11 | (defun convert
12 | ((m 'inch) (/ m 2.54))
13 | ((n 'centimetre) (* n 2.54)))
14 | ```
15 |
16 | Compile and test:
17 |
18 | ```lisp
19 | lfe> (c "tut4.lfe")
20 | #(module tut4)
21 | lfe> (tut4:convert 3 'inch)
22 | 1.1811023622047243
23 | lfe> (tut4:convert 7 'centimetre)
24 | 17.78
25 | ```
26 |
27 | Notice that atoms and variables look the same so we have to tell LFE when we want it to be an atom. We do this by *quoting* the atom with a `'`, for example `'inch` and `'centimetre`. We have to do this both when we use it as argument in a function definition and when we use it when calling a function, otherwise LFE will assume that it is a variable.
28 |
29 | Also notice that we have introduced decimals (floating point numbers) without any explanation, but I guess you can cope with that.
30 |
31 | See what happens if I enter something other than centimetre or inch in the convert function:
32 |
33 | ```lisp
34 | lfe> (tut4:convert 3 'miles)
35 | exception error: function_clause
36 | in (: tut4 convert 3 miles)
37 | ```
38 |
39 | The two parts of the convert function are called its *clauses*. Here we see that `miles` is not part of either of the clauses. The LFE system can't **match** either of the clauses so we get an error message `function_clause`. The shell formats the error message nicely, but to see the actual error tuple we can do:
40 |
41 | ```lisp
42 | lfe> (catch (tut4:convert 3 'miles))
43 | #(EXIT
44 | #(function_clause
45 | (#(tut2 convert (3 miles) (#(file "./tut2.lfe") #(line 4)))
46 | #(lfe_eval eval_expr 2 (#(file "src/lfe_eval.erl") #(line 160)))
47 | #(lfe_shell eval_form_1 2 (#(file "src/lfe_shell.erl") #(line 268)))
48 | #(lists foldl 3 (#(file "lists.erl") #(line 1261)))
49 | #(lfe_shell server_loop 1 (#(file "src/lfe_shell.erl") #(line 101))))))
50 | ```
51 |
--------------------------------------------------------------------------------
/src/sequential/bifs.md:
--------------------------------------------------------------------------------
1 | ## Built-in Functions
2 |
3 | [forthcoming]
4 |
5 | Ticket: [https://github.com/lfe/tutorial/issues/7](https://github.com/lfe/tutorial/issues/7)
6 |
--------------------------------------------------------------------------------
/src/sequential/conds.md:
--------------------------------------------------------------------------------
1 | ## Conditionals
2 |
3 | In the module `tut13.lfe`, we saw our first conditional, the `(if ...)` form. We're going to spend the rest of this section discussing `if`, `cond`, `case`, as well as the use of guards and pattern matching to form conditional code branches.
4 |
5 | ### The `if` Form
6 |
7 | In the previous section, we wrote the function `find-max-min/3` to work out the maximum and minimum temperature. This work was delegated to two helper functions:
8 | * `compare-max/2`
9 | * `compare-min/2`
10 |
11 | In both of those functions, we introduced the new `if` form. If works as follows:
12 |
13 | ```lisp
14 | (if
15 |
16 | )
17 | ```
18 |
19 | where `` is executed if `` evaluates to `true` and `` is executed if `` evaluates to `false`. If you have used other programming languages, then this will be quite familiar to you. If you have not, if should remind you a bit of the logic we looked at when discussing guards.
20 |
21 | We can see it in action with the following LFE session in the REPL:
22 |
23 | ```lisp
24 | lfe> (if (=:= 1 1) "They are equal!" "They are *not* equal!")
25 | "They are equal!"
26 | lfe> (if (=:= 2 1) "They are equal!" "They are *not* equal!")
27 | "They are *not* equal!"
28 | ```
29 |
30 | Or -- you will be more familiar with this -- our code from the last section:
31 |
32 | ```lisp
33 | (if (< temp1 temp2)
34 | city1
35 | city2)
36 | ```
37 | where, if `temp1` is less than `temp2`, the value stored in `city1` is returned.
38 |
39 | So the `if` form works for two conditions. What about 3? 10? 100? Well, for the situations were we want to check multiple conditions, we'll need the `cond` form.
40 |
41 | ### The `cond` Form
42 |
43 | ```lisp
44 | (cond ()
45 | ()
46 | ()
47 | ...
48 | ())
49 | ```
50 |
51 | A given expression is only executed if its accompanying predicate evaluates to `true`. The `cond` returns the value of the expression for the first predicate that evaluates to `true`. Using `cond`, our temperature test would look like this:
52 |
53 | ```lisp
54 | (cond ((< temp1 temp2) city1)
55 | ((>= temp1 temp2) city2))
56 | ```
57 |
58 | Here's an example which takes advantage of `cond` supporting more than two logic branches:
59 |
60 | ```lisp
61 | (cond ((> x 0) x)
62 | ((=:= x 0) 0)
63 | ((< x 0) (- x)))
64 | ```
65 |
66 | Note that each predicate is an expression with it's own parentheses around it; on its left is the opening parenthesis for that particular branch of the `cond`.
67 |
68 | Often times when using `cond` one needs a "default" or "fall-through" option to be used when no other condition is met. Since it's the last one, and we need it to evaluate to `true` we simply set the last condition to `true` when we need a default. Here's a rather silly example:
69 |
70 | ```lisp
71 | (cond ((lists:member x '(1 2 3)) "First three")
72 | ((=:= x 4) "Is four")
73 | ((>= x 5) "More than four")
74 | ('true "You chose poorly"))
75 | ```
76 |
77 | Any number that is negative will be caught by the last condition.
78 |
79 | In case you're wondering, yes: `cond` works with patterns as well. Let's take a look.
80 |
81 | ### The Extended `cond` Form
82 |
83 | When we talked about `cond` above, we only discussed the form as any Lisper would be familiar. However, LFE has extended `cond` with additional capabilities provided via pattern matching. LFE's `cond` has the following general form when this is taken into consideration:
84 |
85 | ```lisp
86 | (cond ()
87 | ()
88 | ()
89 | ...
90 | ())
91 | ```
92 |
93 | where each `` could be either as it is in the regular `cond`, `` or it could be `(?= [] )` -- the latter being the extended form (with an optional guard). When using the extended form, instead of evaluating a predicate for its Boolean result, the data passed to the `cond` is matched against the defined patterns: if the pattern match succeeds, then the associated expression is evaluated. Here's an example:
94 |
95 | ```lisp
96 | (cond ((?= (cons head '()) x)
97 | "Only one element")
98 | ((?= (list 1 2) x)
99 | "Two element list")
100 | ((?= (list a _) (when (is_atom a)) x)
101 | "List starts with an atom")
102 | ((?= (cons _ (cons a _)) (when (is_tuple a)) x)
103 | "Second element is a tuple")
104 | ('true "Anything goes"))
105 | ```
106 |
107 | That form is not that often used, but it can be very practical.
108 |
109 |
110 | ### The `case` Form
111 |
112 | The `case` form is useful for situations where you want to check for multiple possible values of the same expression. Without guards, the general form for `case` is the following:
113 |
114 | ```lisp
115 | (case
116 | ()
117 | ()
118 | ...
119 | ())
120 | ```
121 |
122 | So we could rewrite the code for the non-extended `cond` above with the following `case`:
123 |
124 | ```lisp
125 | (case x
126 | ((cons head '())
127 | "Only one element")
128 | ((list 1 2)
129 | "Two element list")
130 | ((list 'a _)
131 | "List starts with 'a'")
132 | (_ "Anything goes"))
133 | ```
134 |
135 | The following will happen with the `case` defined above:
136 | * Any 1-element list will be matched by the first clause.
137 | * A 2-element list of `1` and `2` (in that order) will match the second clause.
138 | * Any 2-element list whose first element is the atom `a` will match the third clause.
139 | * Anything *not* matching the first three clauses will be matched by the fourth.
140 |
141 | With guards, the case has the following general form:
142 |
143 | ```lisp
144 | (case
145 | ( [] )
146 | ( [] )
147 | ...
148 | ( [] ))
149 | ```
150 |
151 | Let's update the previous example with a couple of guards:
152 |
153 | ```lisp
154 | (case x
155 | ((cons head '())
156 | "Only one element")
157 | ((list 1 2)
158 | "Two element list")
159 | ((list a _) (when (is_atom a))
160 | "List starts with an atom")
161 | ((cons _ (cons a _)) (when (is_tuple a))
162 | "Second element is a tuple")
163 | (_ "Anything goes"))
164 | ```
165 |
166 | This changes the logic of the previous example in the following ways:
167 | * Any list whose first element is an atom will match the third clause.
168 | * Any list whose second element is a tuple will match the fourth clause.
169 | * Anything *not* matching the first four clauses will be matched by the fifth.
170 |
171 | ### Function Heads as Conditionals
172 |
173 | Another very common way to express conditional logic in LFE is through the use of pattern matching in function heads. This has the capacity to make code *very* concise while also remaining clear to read -- thus its prevalent use.
174 |
175 | As we've seen, a regular LFE function takes the following form (where the arguments are optional):
176 |
177 | ```lisp
178 | (defun ([ ... ])
179 | )
180 | ```
181 |
182 | When pattern matching in the function head, the form is as follows:
183 |
184 | ```lisp
185 | (defun
186 | (() []
187 | )
188 | (() []
189 | )
190 | ...
191 | (() []
192 | ))
193 | ```
194 |
195 | Note that simple patterns with no expressions are just regular function arguments. In other words ``, ``, etc., may be either a full pattern or they may be simple function arguments. The guards are optional.
196 |
197 | Let's try this out by rewriting the silly `case` example above to use a function with pattern-matching in the function heads:
198 |
199 | ```lisp
200 | (defun check-val
201 | (((cons head '()))
202 | "Only one element")
203 | (((list 1 2))
204 | "Two element list")
205 | (((list a _)) (when (is_atom a))
206 | "List starts with an atom")
207 | (((cons _ (cons a _))) (when (is_tuple a))
208 | "Second element is a tuple")
209 | ((_) "Anything goes"))
210 | ```
211 |
212 | If you run that in the REPL, you can test it out with the following:
213 |
214 | ```lisp
215 | lfe> (check-val '(1))
216 | "Only one element"
217 | lfe> (check-val '(a 1))
218 | "List starts with an atom"
219 | lfe> (check-val '(1 #(b 2)))
220 | "Second element is a tuple"
221 | lfe> (check-val 42)
222 | "Anything goes"
223 | ```
224 |
225 | And there you have LFE function definitions with much of the power of `if`, `cond`, and `case`!
226 |
227 | Let's use some of these forms in actual code now ...
228 |
229 | ### Example: Inches and Centimetres
230 |
231 | [forthcoming]
232 |
233 | [tutorial #14]
234 |
235 |
236 | ### Example: Leap Years
237 |
238 | [forthcoming]
239 |
240 | [tutorial #15]
241 |
--------------------------------------------------------------------------------
/src/sequential/example.md:
--------------------------------------------------------------------------------
1 | ## Example: Converting Temperature
2 |
3 | Now for a larger example to consolidate what we have learnt so far. Assume we have a list of temperature readings from a number of cities in the world. Some of them are in Celsius (Centigrade) and some in Fahrenheit (as in the previous list). First let's convert them all to Celsius, then let's print out the data neatly. Save the following code to ``tut8.lfe``:
4 |
5 | ```lisp
6 | (defmodule tut8
7 | (export (format-temps 1)))
8 |
9 | ;; Only this function is exported
10 | (defun format-temps
11 | ((())
12 | ;; No output for an empty list
13 | 'ok)
14 | (((cons city rest))
15 | (print-temp (f->c city))
16 | (format-temps rest)))
17 |
18 | (defun f->c
19 | (((tuple name (tuple 'C temp)))
20 | ;; No conversion needed
21 | (tuple name (tuple 'C temp)))
22 | (((tuple name (tuple 'F temp)))
23 | ;; Do the conversion
24 | (tuple name (tuple 'C (/ (* (- temp 32) 5) 9)))))
25 |
26 | (defun print-temp
27 | (((tuple name (tuple 'C temp)))
28 | (lfe_io:format "~-15w ~w C~n" (list name temp))))
29 | ```
30 |
31 | ```lisp
32 | lfe> (c "tut8.lfe")
33 | #(module tut8)
34 | lfe> (tut8:format-temps
35 | '(#(Moscow #(C 10))
36 | #(Cape-Town #(F 70))
37 | #(Stockholm #(C -4))
38 | #(Paris #(F 28))
39 | #(London #(F 36)))))
40 | Moscow 10 C
41 | Cape-Town 21.11111111111111 C
42 | Stockholm -4 C
43 | Paris -2.2222222222222223 C
44 | London 2.2222222222222223 C
45 | ok
46 | ```
47 |
48 | Before we look at how this program works, notice that we have added a few comments to the code. A comment starts with a ``;`` character and goes on to the end of the line. [^1] Note as well that the ``(export (format-temps 1))`` line only includes the function ``format-temps/1``, the other functions are local functions, i.e. they are not visible from outside the module ``temp-convert``.
49 |
50 | When we call ``format-temps/1`` the first time, the ``city`` variable gets the value ``#(Moscow #(C-10))`` and the remainder of the list is assigned to the ``rest`` variable. Next, the ``f->c/1`` function is called inside the ``print-temp/1`` function, with ``f->c/1`` getting passed ``#(Moscow #(C-10))``.
51 |
52 | Note that when we see function calls nested like ``(print-temp (f->c ...))`` -- in other words when one function call is passed as the argument to another function -- we execute (evaluate) them from the inside out. We first evaluate ``(f->c city)`` which gives the value ``#(Moscow #(C 10))`` as the temperature is already in Celsius and then we evaluate ``(print-temp #(Moscow #(C 10)))``. Note that the ``f->c/1`` function works in a similar way to the ``convert-length/1`` function we wrote in a previous section.
53 |
54 | Next, ``print-temp/1`` simply calls ``lfe_io:format/2`` in a similar way to what has been described above. Note that ``~-15w`` says to print the "term" with a field length (width) of 15 and left justify it.[^2]
55 |
56 | Now we call ``(format-temps rest)`` with the remainder of the list as an argument. This way of doing things is similar to the loop constructs in other languages. (Yes, this is recursion, but don't let that worry you). So the same ``format-temps/1`` function is called again, this time ``city`` gets the value ``#(Cape-Town #(F 70))`` and we repeat the same procedure as before. We go on doing this until the list becomes empty, i.e. ``()``, which causes the first clause ``(format-temps '())`` to match. This simply "returns" or "results in" the atom ``ok``, so the program ends.
57 |
58 | ----
59 |
60 | #### Notes
61 |
62 | [^1] In LFE, the convention is that a comment starting with a single ``;`` is reserved for comments at the end of a line of code; lines which start with a comment use two ``;;``, as above. There are also conventions for ``;;;`` and ``;;;;`` -- to learn more about these, see [Program Development Using LFE - Rules and Conventions](http://docs.lfe.io/prog-rules/1.html) and [LFE Style Guide](http://docs.lfe.io/style-guide/1.html).
63 |
64 | [^2] LFE's ``lfe_io:format`` differs from the Erlang standard library ``io:format`` in that ``lfe_io`` displays its results using LFE-formatted data structures in its Lisp syntax; ``io`` uses the standard Erlang syntax. You may use either from LFE. ``lfe_io`` takes the same formatting parameters as ``io``, so there should be no surprises if you're coming from Erlang. For more information, be sure to read the [io:format documentation](http://www.erlang.org/doc/man/io.html#fwrite-1).
65 |
--------------------------------------------------------------------------------
/src/sequential/hofs.md:
--------------------------------------------------------------------------------
1 | ## Higher Order Functions
2 |
3 | [forthcoming]
4 |
5 | ### Refactor Temperature Conversion Example
6 |
7 | [tutorial #16]
8 |
9 | [tutorial #17]
10 |
11 | Ticket: [https://github.com/lfe/tutorial/issues/8](https://github.com/lfe/tutorial/issues/8)
12 |
--------------------------------------------------------------------------------
/src/sequential/lists.md:
--------------------------------------------------------------------------------
1 | ## Lists
2 |
3 | Whereas tuples group things together, we also want to be able to represent lists of things. Lists in LFE are surrounded by "(" and ")". For example a list of the temperatures of various cities in the world could be:
4 |
5 | ```lisp
6 | (#(Moscow #(C -10))
7 | #(Cape-Town #(F 70)) #(Stockholm #(C -4))
8 | #(Paris #(F 28)) #(London #(F 36)))
9 | ```
10 |
11 | Note that this list was so long that it didn't fit on one line. This doesn't matter, LFE allows line breaks at all "sensible places" but not, for example, in the middle of atoms, integers etc.
12 |
13 | A very useful way of looking at parts of lists is by using the constructor ``cons``. It can also be used as a pattern to match against lists. This is best explained by an example using the shell.
14 |
15 | ```lisp
16 | lfe> (set (cons first rest) '(1 2 3 4 5))
17 | (1 2 3 4 5)
18 | lfe> first
19 | 1
20 | lfe> rest
21 | (2 3 4 5)
22 | ```
23 |
24 | We see here that ``set`` also allows you to define variables using patterns. Using ``cons`` we could separate the first element of the list from the rest of list (``first`` got the value 1 and ``rest`` the value (2 3 4 5)). We also see here that when we want to give a literal list we need to *quote* it with ``'``. This stops LFE trying to evaluate the list as a function call in the same way as quoting an atom stops LFE trying to evaluate the atom as a variable.
25 |
26 | Another example:
27 |
28 | ```lisp
29 | lfe> (set (cons e1 (cons e2 r)) '(1 2 3 4 5 6 7))
30 | (1 2 3 4 5 6 7)
31 | lfe> e1
32 | 1
33 | lfe> e2
34 | 2
35 | lfe> r
36 | (3 4 5 6 7)
37 | ```
38 |
39 | We see here nesting ``cons`` to get the first two elements from the list. Of course if we try to get more elements from the list than there are elements in the list we will get an error. Note also the special case of the list with no elements ().
40 |
41 | ```lisp
42 | lfe> (set (cons a (cons b c)) '(1 2))
43 | (1 2)
44 | lfe> a
45 | 1
46 | lfe> b
47 | 2
48 | lfe> c
49 | ()
50 | ```
51 |
52 | In all examples above we have been using new variables names, not reusing old ones. While ``set`` does allow you to rebind variables normally a variable can only be given a value once in its context (scope).
53 |
54 | The following example shows how we find the length of a list:
55 |
56 | ```lisp
57 | (defmodule tut6
58 | (export (list-length 1)))
59 |
60 | (defun list-length
61 | ((()) 0)
62 | (((cons first rest))
63 | (+ 1 (list-length rest))))
64 | ```
65 |
66 | Compile (file ``tut4.lfe``) and test:
67 |
68 | ```lisp
69 | lfe> (c "tut6.lfe")
70 | #(module tut6)
71 | lfe> (tut6:list-length '(1 2 3 4 5 6 7))
72 | 7
73 | ```
74 |
75 | Explanation:
76 |
77 | ```lisp
78 | (defun list-length
79 | ((()) 0)
80 | ```
81 |
82 | The length of an empty list is obviously 0.
83 |
84 | ```lisp
85 | (((cons first rest))
86 | (+ 1 (list-length rest))))
87 | ```
88 |
89 | The length of a list with the first element ``first`` and the remaining elements ``rest`` is 1 + the length of ``rest``.
90 |
91 | (Advanced readers only: This is not tail recursive, there is a better way to write this function).
92 |
93 | In general we can say we use tuples where we would use "records" or "structs" in other languages and we use lists when we want to represent things which have varying sizes, (i.e. where we would use linked lists in other languages).
94 |
95 | LFE does not have a string data type, instead strings can be represented by lists of Unicode characters. So the list ``(97 98 99)`` is equivalent to "abc". Note that we don't have to quote strings as we do lists. The LFE repl is "clever" and guesses the what sort of list we mean and outputs it in what it thinks is the most appropriate form, for example:
96 |
97 | ```lisp
98 | lfe> '(97 98 99)
99 | "abc"
100 | lfe> "abc"
101 | "abc"
102 | lfe> '"abc"
103 | "abc"
104 | ```
105 |
106 | Lists can also be surrounded by "[" and "]" instead of parentheses. They are equivalent but must match, for example:
107 |
108 | ```lisp
109 | lfe> '(a b c)
110 | (a b c)
111 | lfe> '[a b c]
112 | (a b c)
113 | lfe> '(a b c]
114 | 1: illegal ']'
115 | ```
116 |
117 | This can be used to make list structures easier to read. For example, it is often used in function definitions for the list of arguments when there are multiple clauses:
118 |
119 | ```lisp
120 | (defun list-length
121 | ([()] 0)
122 | ([(cons first rest)]
123 | (+ 1 (list-length rest))))
124 | ```
125 |
--------------------------------------------------------------------------------
/src/sequential/matching.md:
--------------------------------------------------------------------------------
1 | ## Matching and Guards and Scope of Variables
2 |
3 | In the previous section we wrote a little example program for converting temperatures. In creating programs like that with special data structures (in our case, a list of cities and their temperatures), it's often useful to create utility functions which make working with our data more convenient. We will explore that below and use these functions to introduce some new concepts.
4 |
5 | ### A Utility Function
6 |
7 | In this case, it could be useful to find the maximum and minimum temperature in our data. We can add support for this by creating the necessary code little bit at a time. Let's start with creating functions for finding the maximum value of the elements of a property list:
8 |
9 | ```lisp
10 | (defmodule tut9
11 | (export (list-max 1)))
12 |
13 | (defun list-max
14 | (((cons head tail))
15 | (list-max tail head)))
16 |
17 | (defun list-max
18 | (('() results)
19 | results)
20 | (((cons head tail) result-so-far) (when (> head result-so-far))
21 | (list-max tail head))
22 | (((cons head tail) result-so-far)
23 | (list-max tail result-so-far)))
24 | ```
25 |
26 | Then, in the LFE REPL:
27 |
28 | ```lisp
29 | lfe> (c "tut9.lfe")
30 | #(module tut9)
31 | lfe> (tut9:list-max '(1 2 3 4 5 6 7 4 3 2 1))
32 | 7
33 | ```
34 |
35 | ### Pattern Matching
36 |
37 | Before we talk about pattern matching, let's clarify why we have two different functions with the same name. Note that the two ``list-max`` functions above each take a different number of arguments (parameters). Or, another way of saying it: they have different *arity*. In LFE, functions having the same name but differing in arity are actually *different functions*. Where we need to distinguish between these functions we write ``/``, where ```` is an integer representing the number of arguments that function takes. For our example above, we would write ``list-max/1`` and ``list-max/2``.
38 |
39 | The next thing we should explain is the arguments for the ``list-max/2`` function, since that probably looks pretty strange the first time you see it. If you look closely, you will see that there are three clauses in ``list-max/2`` and each clause stats with the function arguments for that clause; in order, they are:
40 |
41 | * an empty list and ``results``
42 | * a ``cons`` and ``results-so-far`` with something called a *guard* (more on that soon)
43 | * a ``cons`` and ``results-so-far`` just by itself
44 |
45 | What each of these are doing is what is called *pattern matching* in LFE: if the arguments passed to ``list-max/2`` match the first pattern, the first clause gets executed and the others don't. If the first one does not match, the second one is tried, and so on.
46 |
47 | So what is being "matched" here? Well, the first clause will match if it's first argument is an empty list. The second and third will match if the first element is a list. The second has something more, though: let's take a closer look.
48 |
49 | ### Guards
50 |
51 | In the second clause of ``list-max/2`` we see a new form: ``(when ...)`` which contains a comparison operation. The special form ``when`` is a something we can use with LFE patterns to limit a match. In this case we use it to say: "only use this function clause if ``head`` is greater than ``result-so-far``. We call tests of this type a *guard*. If the guard isn't true (usually referred to as "if the guard fails"), then we try the next part of the function.
52 |
53 | ### Stepping Through the Function
54 |
55 | Now that we know how there can be two functions with the same name and how the arguments for ``list-max/2`` are working, let's step through the functions above. They represent an example of walking through a list and "carrying" a value as we do so, in this case ``result-so-far`` is the variable that carries a value as we walk. ``list-max/1`` simply assumes that the max value of the list is the head of the list and calls ``list-max/2`` with the rest of the list and the value of the head of the list, in the above this would be ``(list-max '(2 3 4 5 6 7 4 3 2 1) 1)``. If we tried to use ``list-max/1`` with an empty list or tried to use it with something which isn't a list at all, we would cause an error. The LFE philosophy is not to handle errors of this type in the function they occur, but to do so elsewhere. More about this later.
56 |
57 | In ``list-max/2`` we walk down the list and use ``head`` instead of ``result-so-far`` when ``head`` is greater than ``result-so-far``. In this function, if ``head`` *isn't* greater than ``result-so-far`` then it must be smaller or equal to it, and the next clause is executed under this condition.
58 |
59 | To change the above program to one which works out the minimum value of the element in a list, all we would need to do is to write ``<`` instead of ``>`` in the guard ... but it would be wise to change the name of the function to ``list-min`` :-).
60 |
61 | ### Scope and ``let``
62 |
63 | In a function, the arguments that are passed to it are *in scope* or "accessible" for all of that function. In another function which has been passed its own arguments, only those are in scope; arguments from other functions are not available for use.
64 |
65 | In the case of functions which are pattern matching on the arguments, like our ``list-max/2``, we have three clauses, each with their own arguments and each with their own scope. The parentheses at the beginning of each clause mark this scope. In that case, the ``results`` variable is only available in the first clause; it is not in scope for the second two clauses.
66 |
67 | But passing arguments to functions and pattern matching function arguments are not the only ways in which you can bind a value to a variable. There is a special form in LFE (and other Lisps) called ``let``. Let's[^1] take a look, shall we? Here's another way we could have written ``list-max/2``:
68 |
69 | ```lisp
70 | (defun list-max
71 | (('() results)
72 | results)
73 | (((cons head tail) result-so-far) (when (> head result-so-far))
74 | (let ((new-result-so-far head))
75 | (list-max tail new-result-so-far)))
76 | (((cons head tail) result-so-far)
77 | (list-max tail result-so-far)))
78 | ```
79 |
80 | We only made a change to the second clause: we assigned the value of ``head`` to the variable ``new-result-so-far``. This assignment didn't involve any computation or new values, it was essentially just a "renaming" mostly for the sake of demonstration, and arguably to make it more clear the purpose of the value stored in ``head``.
81 |
82 |
83 | ----
84 |
85 | #### Notes
86 |
87 | [^1] We are not going to apologise for that pun.
88 |
--------------------------------------------------------------------------------
/src/sequential/modfunc.md:
--------------------------------------------------------------------------------
1 | ## Modules and Functions
2 |
3 | ### Creating a Simple Module
4 |
5 | A programming language isn't much use if you can only run code from a REPL. So next we will write a small LFE program in a file on the file system. In the same directory that you started the LFE REPL, create a new file called `tut1.lfe` (the filename is **important**: be sure you type it just as we have) using your favourite text editor.
6 |
7 | Here's the code to enter:
8 |
9 | ```lisp
10 | (defmodule tut1
11 | (export all))
12 |
13 | (defun double (x)
14 | (* 2 x))
15 | ```
16 |
17 | It's not hard to guess that this "program" doubles the value of numbers. We'll get back to the first two lines later. Let's compile the program. This can be done in the LFE REPL as shown below:
18 |
19 | ```lisp
20 | lfe> (c "tut1.lfe")
21 | #(module tut1)
22 | lfe>
23 | ```
24 |
25 | The `#(module tut1)` tells you that the compilation was successful. If it said "error" instead, you have made some mistake in the text you entered and there will also be error messages to give you some idea as to what has gone wrong so you can change what you have written and try again.
26 |
27 | Now lets run the program.
28 |
29 | ```lisp
30 | lfe> (tut1:double 108)
31 | 216
32 | lfe>
33 | ```
34 |
35 | As expected, `108` doubled is 216.
36 |
37 | Now let's get back to the first two lines. LFE programs are written in files. Each file contains what we call an *LFE module*. The first line of code in the module tells LFE that we're defining a module and giving it a name:
38 |
39 | ```lisp
40 | (defmodule tut1
41 | ```
42 | The name of our module is `tut1` and the file which is used to store the module must have the same name as the module but with the `.lfe` extension. In our case the file name is `tut1.lfe`.
43 |
44 | In LFE, whenever we use a function that has been defined in another module, we use the syntax, `(module:function argument1 argument2 ...)`. So
45 |
46 | ```lisp
47 | lfe> (tut1:double 108)
48 | ```
49 |
50 | means "call the function `double` in the module `tut1` with the argument of `108`.
51 |
52 | The second line tells LFE which functions we will be exporting -- in this case, all of them (which is only *one* ...):
53 |
54 | ```lisp
55 | (export all))
56 | ```
57 |
58 | If we wanted to be explicit about which functions were to be exported, we would have written:
59 |
60 | ```lisp
61 | (defmodule tut1
62 | (export (double 1)))
63 | ```
64 |
65 | That says "in the module `tut1`, please make available the function called `double` which takes one argument" (`x` in our example). By "make available" we mean that this function can be called from outside the module `tut1`.
66 |
67 | ### A More Complicated Example
68 |
69 | Now for a more complicated example, the factorial of a number (e.g. factorial of 4 is 4 * 3 * 2 * 1). Enter the following code in a file called `tut2.lfe`.
70 |
71 | ```lisp
72 | (defmodule tut2
73 | (export (fac 1)))
74 |
75 | (defun fac
76 | ((1) 1)
77 | ((n) (* n (fac (- n 1)))))
78 | ```
79 |
80 | Compile the file
81 |
82 | ```lisp
83 | lfe> (c "tut2.lfe")
84 | #(module tut2)
85 | ```
86 |
87 | And now calculate the factorial of 4.
88 |
89 | ```lisp
90 | lfe> (tut2:fac 4)
91 | 24
92 | ```
93 |
94 | The function `fac` contains two parts. The first part:
95 |
96 | ```lisp
97 | ((1) 1)
98 | ```
99 |
100 | says that the factorial of 1 is 1. Note that this part is a separate list in the function definition where the first element is a *list* of the arguments to the function and the rest is the body of the function. The second part:
101 |
102 | ```lisp
103 | ((n) (* n (fac (- n 1)))))
104 | ```
105 |
106 | says that the factorial of n is n multiplied by the factorial of n - 1. After this part which is the last part we end the function definition with the closing `)`.
107 |
108 | A function can have many arguments. Let's expand the module `tut2` with the rather stupid function to multiply two numbers:
109 |
110 | ```lisp
111 | (defmodule tut3
112 | (export (fac 1) (mult 2)))
113 |
114 | (defun fac
115 | ((1) 1)
116 | ((n) (* n (fac (- n 1)))))
117 |
118 | (defun mult (x y)
119 | (* x y))
120 |
121 | ```
122 |
123 | Note that we have also had to expand the `(export` line with the information that there is another function `mult` with two arguments. Compile the file:
124 |
125 | ```lisp
126 | lfe> (c "tut3.lfe")
127 | #(module tut3)
128 | ```
129 | and try it out:
130 |
131 | ```lisp
132 | lfe> (tut3:mult 3 4)
133 | 12
134 | ```
135 |
136 | In the example above the numbers are integers and the arguments in the functions in the code, `n`, `x`, `y` are called variables. Examples of variables could be `number`, `shoe-size`, `age` etc.
137 |
138 | Note that when a function has only one part and all the arguments are variables then we can use the shorter form we saw in `double` and `mult`. This means that we could also have written `mult` as:
139 |
140 | ```lisp
141 | (defun mult
142 | ((x y) (* x y)))
143 | ```
144 |
--------------------------------------------------------------------------------
/src/sequential/morelists.md:
--------------------------------------------------------------------------------
1 | ## More About Lists
2 |
3 | ### The ``cons`` Form
4 | Remember how we used ``cons`` to "extract" head and tail values of a list when matching them? In the REPL, we would do it like so:
5 |
6 | ```lisp
7 | lfe> (set (cons head tail) (list 'Paris 'London 'Rome))
8 | (Paris London Rome)
9 | lfe> head
10 | Paris
11 | lfe> tail
12 | (London Rome)
13 | ```
14 | Well, that's not how ``cons`` started life[^1]; it's original use was in "cons"tructing lists, not taking them apart. Here is some classic usage:
15 |
16 | ```lisp
17 | lfe> (cons 'Madrid tail)
18 | (Madrid London Rome)
19 | ```
20 |
21 | Let's look at a more involved example where we use ``cons``es to reverse the order of a list:
22 |
23 | ```lisp
24 | (defmodule tut10
25 | (export all))
26 |
27 | (defun reverse (list)
28 | (reverse list '()))
29 |
30 | (defun reverse
31 | (((cons head tail) reversed-list)
32 | (reverse tail (cons head reversed-list)))
33 | (('() reversed-list)
34 | reversed-list))
35 | ```
36 |
37 | Then, in the REPL:
38 |
39 | ```lisp
40 | lfe> (c "tut10.lfe")
41 | #(module tut10)
42 | lfe> (tut10:reverse (list 1 2 3))
43 | (3 2 1)
44 | ```
45 |
46 | Consider how ``reversed-list`` is built: it starts as ``'()``, we then successively take off the heads of the list that was provided and add these heads to the the ``reversed-list`` variable, as detailed by the following:
47 |
48 | ```lisp
49 | (reverse (cons 1 '(2 3)) '()) => (reverse '(2 3) (cons 1 '()))
50 | (reverse (cons 2 '(3)) '(1)) => (reverse '(3) (cons 2 '(1)))
51 | (reverse (cons 3 '()) (2 1)) => (reverse '() (cons 3 '(2 1)))
52 | (reverse '() '(3 2 1)) => '(3 2 1)
53 | ```
54 |
55 | The Erlang module ``lists`` contains a lot of functions for manipulating lists, for example for reversing them -- our work above was done for demonstration and pedagogical purposes. For serious applications, one should prefer functions in the Erlang standard library.[^2]
56 |
57 | ### Processing Lists
58 |
59 | Now lets get back to the cities and temperatures, but take a more structured approach this time. First let's convert the whole list to Celsius as follows:
60 |
61 | ```lisp
62 | (defmodule tut11
63 | (export (format-temps 1)))
64 |
65 | (defun format-temps (cities)
66 | (->c cities))
67 |
68 | (defun ->c
69 | (((cons (tuple name (tuple 'F temp)) tail))
70 | (let ((converted (tuple name (tuple 'C (/ (* (- temp 32) 5) 9)))))
71 | (cons converted (->c tail))))
72 | (((cons city tail))
73 | (cons city (->c tail)))
74 | (('())
75 | '()))
76 | ```
77 | Now let's test this new function:
78 |
79 | ```lisp
80 | lfe> (c "tut11.lfe")
81 | #(module tut11)
82 | lfe> (tut11:format-temps
83 | '(#(Moscow #(C 10))
84 | #(Cape-Town #(F 70))
85 | #(Stockholm #(C -4))
86 | #(Paris #(F 28))
87 | #(London #(F 36)))))
88 | (#(Moscow #(C 10))
89 | #(Cape-Town #(C 21.11111111111111))
90 | #(Stockholm #(C -4))
91 | #(Paris #(C -2.2222222222222223))
92 | #(London #(C 2.2222222222222223)))
93 | ```
94 |
95 | Let's look at this, bit-by-bit. In the first function:
96 |
97 | ```lisp
98 | (defun format-temps (cities)
99 | (->c cities))
100 | ```
101 |
102 | we see that ``format-temps/1`` calls ``->c/1``. ``->c/1`` takes off the head of the List ``cities`` and converts it to Celsius if needed. The ``cons`` function is used to add the (maybe) newly converted city to the converted rest of the list:
103 |
104 | ```lisp
105 | (cons converted (->c tail))
106 | ```
107 | or
108 |
109 | ```lisp
110 | (cons city (->c tail))
111 | ```
112 |
113 | We go on doing this until we get to the end of the list (i.e. the list is empty):
114 |
115 | ```lisp
116 | (('())
117 | '())
118 | ```
119 |
120 | Now that we have converted the list, we should add a function to print it:
121 |
122 | ```lisp
123 | (defmodule tut12
124 | (export (format-temps 1)))
125 |
126 | (defun format-temps (cities)
127 | (print-temps (->c cities)))
128 |
129 | (defun ->c
130 | (((cons (tuple name (tuple 'F temp)) tail))
131 | (let ((converted (tuple name (tuple 'C (/ (* (- temp 32) 5) 9)))))
132 | (cons converted (->c tail))))
133 | (((cons city tail))
134 | (cons city (->c tail)))
135 | (('())
136 | '()))
137 |
138 | (defun print-temps
139 | (((cons (tuple name (tuple 'C temp)) tail))
140 | (io:format "~-15w ~w c~n" (list name temp))
141 | (print-temps tail))
142 | (('())
143 | 'ok))
144 | ```
145 |
146 | Let's take a look:
147 |
148 | ```lisp
149 | lfe> (c "tut12.lfe")
150 | #(module tut12)
151 | lfe> (tut12:format-temps
152 | '(#(Moscow #(C 10))
153 | #(Cape-Town #(F 70))
154 | #(Stockholm #(C -4))
155 | #(Paris #(F 28))
156 | #(London #(F 36)))))
157 | 'Moscow' 10 c
158 | 'Cape-Town' 21.11111111111111 c
159 | 'Stockholm' -4 c
160 | 'Paris' -2.2222222222222223 c
161 | 'London' 2.2222222222222223 c
162 | ok
163 | ```
164 |
165 | ### Utility Functions Revisited
166 |
167 | Remember a few sections back when we created the utility function for finding the maximum value in a list? Let's put that into action now: we want to add a function which finds the cities with the maximum and minimum temperatures:
168 |
169 | ```lisp
170 | (defun find-max-min
171 | (((cons city tail))
172 | (find-max-min tail city city)))
173 |
174 | (defun find-max-min
175 | (((cons head tail) max-city min-city)
176 | (find-max-min tail
177 | (compare-max head max-city)
178 | (compare-min head min-city)))
179 | (('() max-city min-city)
180 | (tuple max-city min-city)))
181 |
182 | (defun compare-max
183 | (((= (tuple name1 (tuple 'C temp1)) city1)
184 | (= (tuple name2 (tuple 'C temp2)) city2))
185 | (if (> temp1 temp2)
186 | city1
187 | city2)))
188 |
189 | (defun compare-min
190 | (((= (tuple name1 (tuple 'C temp1)) city1)
191 | (= (tuple name2 (tuple 'C temp2)) city2))
192 | (if (< temp1 temp2)
193 | city1
194 | city2)))
195 | ```
196 |
197 |
198 | ### The Complete Example
199 |
200 | ```lisp
201 | (defmodule tut13
202 | (export (format-temps 1)))
203 |
204 | (defun format-temps (cities)
205 | (let* ((converted (->c cities)))
206 | (print-temps converted)
207 | (print-max-min (find-max-min converted))))
208 |
209 | (defun ->c
210 | (((cons (tuple name (tuple 'F temp)) tail))
211 | (let ((converted (tuple name (tuple 'C (/ (* (- temp 32) 5) 9)))))
212 | (cons converted (->c tail))))
213 | (((cons city tail))
214 | (cons city (->c tail)))
215 | (('())
216 | '()))
217 |
218 | (defun print-temps
219 | (((cons (tuple name (tuple 'C temp)) tail))
220 | (io:format "~-15w ~w c~n" (list name temp))
221 | (print-temps tail))
222 | (('())
223 | 'ok))
224 |
225 | (defun find-max-min
226 | (((cons city tail))
227 | (find-max-min tail city city)))
228 |
229 | (defun find-max-min
230 | (((cons head tail) max-city min-city)
231 | (find-max-min tail
232 | (compare-max head max-city)
233 | (compare-min head min-city)))
234 | (('() max-city min-city)
235 | (tuple max-city min-city)))
236 |
237 | (defun compare-max
238 | (((= (tuple name1 (tuple 'C temp1)) city1)
239 | (= (tuple name2 (tuple 'C temp2)) city2))
240 | (if (> temp1 temp2)
241 | city1
242 | city2)))
243 |
244 | (defun compare-min
245 | (((= (tuple name1 (tuple 'C temp1)) city1)
246 | (= (tuple name2 (tuple 'C temp2)) city2))
247 | (if (< temp1 temp2)
248 | city1
249 | city2)))
250 |
251 | (defun print-max-min
252 | (((tuple (tuple max-name (tuple 'C max-temp))
253 | (tuple min-name (tuple 'C min-temp))))
254 | (io:format "Max temperature was ~w c in ~w~n" (list max-temp max-name))
255 | (io:format "Min temperature was ~w c in ~w~n" (list min-temp min-name))))
256 | ```
257 |
258 | Let's try it out:
259 |
260 | ```lisp
261 | lfe> (c "tut13.lfe")
262 | #(module tut13)
263 | lfe> (tut13:format-temps
264 | '(#(Moscow #(C 10))
265 | #(Cape-Town #(F 70))
266 | #(Stockholm #(C -4))
267 | #(Paris #(F 28))
268 | #(London #(F 36)))))
269 | 'Moscow' 10 c
270 | 'Cape-Town' 21.11111111111111 c
271 | 'Stockholm' -4 c
272 | 'Paris' -2.2222222222222223 c
273 | 'London' 2.2222222222222223 c
274 | Max temperature was 21.11111111111111 c in 'Cape-Town'
275 | Min temperature was -4 c in 'Stockholm'
276 | ok
277 | ```
278 |
279 | As you may have noticed, that program isn't the most *efficient* way of doing this, since we walk through the list of cities four times. But it is better to first strive for clarity and correctness and to make programs efficient only if really needed.
280 |
281 | ----
282 |
283 | #### Notes
284 |
285 | [^1] Way back in the prehistoric times when large, building-size computers still roamed the earth and the languages which ran on them were tiny and furry, Lisp came along with only a handful of forms: ``cons`` was one of them, and it was used to construct lists, one cell at a time.
286 |
287 | [^2] More information about this ``lists`` module is available [here](http://www.erlang.org/doc/man/lists.html).
288 |
--------------------------------------------------------------------------------
/src/sequential/output.md:
--------------------------------------------------------------------------------
1 | ## Writing Output to a Terminal
2 |
3 | It's nice to be able to do formatted output in these example, so the next example shows a simple way to use to use the ``lfe_io:format`` function. Of course, just like all other exported functions, you can test the ``lfe_io:format`` function in the repl:
4 |
5 | ```lisp
6 | lfe> (lfe_io:format "hello world~n" ())
7 | hello world
8 | ok
9 | lfe> (lfe_io:format "this outputs one LFE term: ~w~n" '(hello))
10 | this outputs one LFE term: hello
11 | ok
12 | lfe> (lfe_io:format "this outputs two LFE terms: ~w~w~n" '(hello world))
13 | this outputs two LFE terms: helloworld
14 | ok
15 | lfe> (lfe_io:format "this outputs two LFE terms: ~w ~w~n" '(hello world))
16 | this outputs two LFE terms: hello world
17 | ok
18 | ```
19 |
20 | The function ``format/2`` (i.e. ``format`` with two arguments) takes two lists. The first one is nearly always a list written as a string between " ". This list is printed out as it stands, except that each ~w is replaced by a term taken in order from the second list. Each ~n is replaced by a new line. The ``lfe_io:format/2`` function itself returns the atom ``ok`` if everything goes as planned. Like other functions in LFE, it crashes if an error occurs. This is not a fault in LFE, it is a deliberate policy. LFE has sophisticated mechanisms to handle errors which we will show later. As an exercise, try to make ``lfe_io:format`` crash, it shouldn't be difficult. But notice that although ``lfe_io:format`` crashes, the Erlang shell itself does not crash.
21 |
22 | ```lisp
23 | lfe> (lfe_io:format "this outputs one LFE term: ~w~n" 'hello)
24 | exception error: badarg
25 | in (: lfe_io fwrite1 "this outputs one LFE term: ~w~n" hello)
26 | in (lfe_io format 3)
27 | ```
28 |
--------------------------------------------------------------------------------
/src/sequential/propmaps.md:
--------------------------------------------------------------------------------
1 | ## Property Lists and Maps
2 |
3 | ### Property Lists
4 |
5 | Property lists in Erlang and LFE are a simple way to create key-value pairs (we actually saw them in the last section, but didn't mention it). They have a very simple structure: a list of tuples, where the key is the first element of each tuple and is an atom. Very often you will see "options" for functions provided as property lists (this is similar to how other programming languages use keywords in function arguments).
6 |
7 | Property lists can be created with just the basic data structures of LFE:
8 |
9 | ```lisp
10 | lfe> (set options (list (tuple 'debug 'true)
11 | (tuple 'default 42)))
12 | (#(debug true) #(default 42))
13 | ```
14 |
15 | Or, more commonly, using quoted literals:
16 |
17 | ```lisp
18 | lfe> (set options '(#(debug true) #(default 42)))
19 | (#(debug true) #(default 42))
20 | ```
21 |
22 | There are convenience functions provided in the `proplists` module. In the last example, we define a default value to be used in the event that the given key is not found in the proplist:
23 |
24 | ```lisp
25 | lfe> (proplists:get_value 'default options)
26 | 42
27 | lfe> (proplists:get_value 'poetry options)
28 | undefined
29 | lfe> (proplists:get_value 'poetry options "Vogon")
30 | "Vogon"
31 | ```
32 |
33 | Be sure to read the [module documentation](http://www.erlang.org/doc/man/proplists.html) for more information. Here's an example of our options in action:
34 |
35 | ```lisp
36 | (defmodule tut61
37 | (export (div 2) (div 3)))
38 |
39 | (defun div (a b)
40 | (div a b '()))
41 |
42 | (defun div (a b opts)
43 | (let ((debug (proplists:get_value 'debug opts 'false))
44 | (ratio? (proplists:get_value 'ratio opts 'false)))
45 | (if (and debug ratio?)
46 | (io:format "Returning as ratio ...~n"))
47 | (if ratio?
48 | (++ (integer_to_list 1) "/" (integer_to_list 2))
49 | (/ a b))))
50 | ```
51 |
52 | Let's try our function without and then with various options:
53 |
54 | ```lisp
55 | lfe> (c "tut61.lfe")
56 | #(module tut61)
57 | lfe> (tut61:div 1 2)
58 | 0.5
59 | lfe> (tut61:div 1 2 '(#(ratio true)))
60 | "1/2"
61 | lfe> (tut61:div 1 2 '(#(ratio true) #(debug true)))
62 | Returning as ratio ...
63 | "1/2"
64 | ```
65 |
66 | ### Maps
67 |
68 | As with property lists, maps are a set of key to value associations. You may create an association from "key" to value 42 in one of two ways: using the LFE core form `map` or entering a map literal:
69 |
70 | ```lisp
71 | lfe> (map "key" 42)
72 | #M("key" 42)
73 | lfe> #M("key" 42)
74 | #M("key" 42)
75 | ```
76 |
77 | We will jump straight into the deep end with an example using some interesting features. The following example shows how we calculate alpha blending using maps to reference colour and alpha channels. Save this code as the file `tut7.lfe` in the directory from which you have run the LFE REPL:
78 |
79 | ```lisp
80 | (defmodule tut7
81 | (export (new 4) (blend 2)))
82 |
83 | (defmacro channel? (val)
84 | `(andalso (is_float ,val) (>= ,val 0.0) (=< ,val 1.0)))
85 |
86 | (defmacro all-channels? (r g b a)
87 | `(andalso (channel? ,r)
88 | (channel? ,g)
89 | (channel? ,b)
90 | (channel? ,a)))
91 |
92 | (defun new
93 | ((r g b a) (when (all-channels? r g b a))
94 | (map 'red r 'green g 'blue b 'alpha a)))
95 |
96 | (defun blend (src dst)
97 | (blend src dst (alpha src dst)))
98 |
99 | (defun blend
100 | ((src dst alpha) (when (> alpha 0.0))
101 | (map-update dst
102 | 'red (/ (red src dst) alpha)
103 | 'green (/ (green src dst) alpha)
104 | 'blue (/ (blue src dst) alpha)
105 | 'alpha alpha))
106 | ((_ dst _)
107 | (map-update dst 'red 0 'green 0 'blue 0 'alpha 0)))
108 |
109 | (defun alpha
110 | (((map 'alpha src-alpha) (map 'alpha dst-alpha))
111 | (+ src-alpha (* dst-alpha (- 1.0 src-alpha)))))
112 |
113 | (defun red
114 | (((map 'red src-val 'alpha src-alpha)
115 | (map 'red dst-val 'alpha dst-alpha))
116 | (+ (* src-val src-alpha)
117 | (* dst-val dst-alpha (- 1.0 src-alpha)))))
118 |
119 | (defun green
120 | (((map 'green src-val 'alpha src-alpha)
121 | (map 'green dst-val 'alpha dst-alpha))
122 | (+ (* src-val src-alpha)
123 | (* dst-val dst-alpha (- 1.0 src-alpha)))))
124 |
125 | (defun blue
126 | (((map 'blue src-val 'alpha src-alpha)
127 | (map 'blue dst-val 'alpha dst-alpha))
128 | (+ (* src-val src-alpha)
129 | (* dst-val dst-alpha (- 1.0 src-alpha)))))
130 | ```
131 |
132 | Now let's try it out, first compiling it:
133 |
134 | ```lisp
135 | lfe> (c "tut7.lfe")
136 | #(module tut7)
137 | lfe> (set colour-1 (tut7:new 0.3 0.4 0.5 1.0))
138 | #M(alpha 1.0 blue 0.5 green 0.4 red 0.3)
139 | lfe> (set colour-2 (tut7:new 1.0 0.8 0.1 0.3))
140 | #M(alpha 0.3 blue 0.1 green 0.8 red 1.0)
141 | lfe> (tut7:blend colour-1 colour-2)
142 | #M(alpha 1.0 blue 0.5 green 0.4 red 0.3)
143 | lfe> (tut7:blend colour-2 colour-1)
144 | #M(alpha 1.0 blue 0.38 green 0.52 red 0.51)
145 | ```
146 |
147 | This example warrants some explanation.
148 |
149 | First we define a couple macros to help with our guard tests. This is only here for convenience and to reduce syntax cluttering. Guards can be only composed of a limited set of functions, so we needed to use macros that would compile down to just the functions allowed in guards. A full treatment of Lisp macros is beyond the scope of this tutorial, but there is a lot of good material available online for learning macros, including [Paul Graham's book "On Lisp."](http://www.paulgraham.com/onlisptext.html)
150 |
151 | ```lisp
152 | (defun new
153 | ((r g b a) (when (all-channels? r g b a))
154 | (map 'red r 'green g 'blue b 'alpha a)))
155 | ```
156 |
157 | The function `new/4` [^1] creates a new map term with and lets the keys `red`, `green`, `blue` and `alpha` be associated with an initial value. In this case we only allow for float values between and including 0.0 and 1.0 as ensured by the `all-channels?` and `channel?` macros.
158 |
159 | By calling `blend/2` on any colour term created by `new/4` we can calculate the resulting colour as determined by the two maps terms.
160 |
161 | The first thing `blend/2` does is to calculate the resulting alpha channel.
162 |
163 | ```lisp
164 | (defun alpha
165 | (((map 'alpha src-alpha) (map 'alpha dst-alpha))
166 | (+ src-alpha (* dst-alpha (- 1.0 src-alpha)))))
167 | ```
168 |
169 | We fetch the value associated with key `alpha` for both arguments using the `(map 'alpha )` pattern. Any other keys in the map are ignored, only the key `alpha` is required and checked for.
170 |
171 | This is also the case for functions `red/2`, `blue/2` and `green/2`.
172 |
173 | ```lisp
174 | (defun red
175 | (((map 'red src-val 'alpha src-alpha)
176 | (map 'red dst-val 'alpha dst-alpha))
177 | (+ (* src-val src-alpha)
178 | (* dst-val dst-alpha (- 1.0 src-alpha)))))
179 | ```
180 |
181 | The difference here is that we check for two keys in each map argument. The other keys are ignored.
182 |
183 | Finally we return the resulting colour in `blend/3`.
184 |
185 | ```lisp
186 | (defun blend
187 | ((src dst alpha) (when (> alpha 0.0))
188 | (map-update dst
189 | 'red (/ (red src dst) alpha)
190 | 'green (/ (green src dst) alpha)
191 | 'blue (/ (blue src dst) alpha)
192 | 'alpha alpha))
193 | ```
194 |
195 | We update the `dst` map with new channel values. The syntax for updating an existing key with a new value is done with `map-update` form.
196 |
197 | ----
198 |
199 | #### Notes
200 |
201 | [^1] Note the use of the slash and number after the function name. We will be discussing this more in a future section, though before we get there you will see this again. Until we get to the full explanation, just know that the number represents the arity of a given function and this helps us be explicit about which function we mean.
202 |
--------------------------------------------------------------------------------
/src/sequential/repl.md:
--------------------------------------------------------------------------------
1 | ## The LFE REPL
2 |
3 | Most operating systems have a command interpreter or shell -- Unix and Linux have many, while Windows has the Command Prompt. Likewise, Erlang has a shell where you can directly write bits of Erlang code and evaluate (run) them to see what happens. LFE has more than a shell: it's a full REPL (*read-eval-print loop*) like other Lisps, and it can do more than the Erlang shell can (including defining proper functions and Lisp macros).
4 |
5 | ### Starting the LFE REPL
6 |
7 | In your system terminal window where you changed directory to the clone of the LFE repository, you can start the LFE REPL by typing the following:
8 |
9 | ```bash
10 | ./bin/lfe
11 | ```
12 |
13 | At which point you will see output something like this:
14 | ```
15 | Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
16 |
17 | ..-~.~_~---..
18 | ( \\ ) | A Lisp-2+ on the Erlang VM
19 | |`-.._/_\\_.-': | Type (help) for usage info.
20 | | g |_ \ |
21 | | n | | | Docs: http://docs.lfe.io/
22 | | a / / | Source: http://github.com/lfe/lfe
23 | \ l |_/ |
24 | \ r / | LFE v2.1.2 (abort with ^G)
25 | `-E___.-'
26 |
27 | lfe>
28 | ```
29 |
30 | ### Interactive LFE Code
31 |
32 | Now let's multiply two numbers in the REPL by typing ``(* 2 21)`` at the ``> `` prompt:
33 |
34 | ```lisp
35 | lfe> (* 2 21)
36 | ```
37 | Lisp stands for "**LIS**t **P**rocessor" because nearly everything in Lisp is really just a list of things -- including the code itself. The lists in Lisps are created with parentheses, just like the expression above. As you can see, the multiplication operator goes first, followed by its arguments -- this is called *prefix notation*, due to the operator coming first.
38 |
39 | In order to tell the REPL that you want it to evaluate your LFE code, you need to his the ```` or ```` key.
40 |
41 | ```lisp
42 | 42
43 | lfe>
44 | ```
45 |
46 | It has correctly given you the answer: 42.
47 |
48 |
49 | Now let's try a more complex calculation:
50 |
51 | ```lisp
52 | lfe> (* 2 (+ 1 2 3 4 5 6))
53 | ```
54 | This expression has one nested inside the other. The first one to be executed is the inner-most, in this case, the addition operation. Just like we saw before, the operator comes first (the "addition" operator, in this case) and then all of the numbers to be added.
55 |
56 | Hit ```` to get your answer:
57 |
58 | ```lisp
59 | 42
60 | lfe>
61 | ```
62 |
63 | ### Defining Variables and Functions
64 |
65 | The LFE REPL allows you do set variables and define functions. Let's define a variable called ``multiplier``:
66 |
67 | ```lisp
68 | lfe> (set multiplier 2)
69 | 2
70 | lfe>
71 | ```
72 |
73 | When we set the value for that variable, the REPL provided feedback on the expression entered, showing us the value. Now we can use it just like the number for which it stands:
74 |
75 | ```lisp
76 | lfe> (* multiplier (+ 1 2 3 4 5 6))
77 | 42
78 | lfe>
79 | ```
80 |
81 | The ``set`` form lets you define a variable; the ``defun`` form lets you define a function. Enter this in the REPL:
82 |
83 | ```lisp
84 | lfe> (defun double (x)
85 | (* 2 x))
86 | ```
87 |
88 | Now try it:
89 |
90 | ```lisp
91 | lfe> (double 21)
92 | 42
93 | lfe>
94 | ```
95 |
96 | As we can see, this function multiplies any given number by ``2``.
97 |
98 |
99 | ### Leaving the REPL
100 |
101 | To exit the REPL and shutdown the underlying Erlang system which started when you executed ``./bin/lfe``, simply exit:
102 |
103 | ```lisp
104 | lfe> (exit)
105 | ok
106 | ```
107 | At which point you will be presented with your regular system terminal prompt.
108 |
109 | There are two other ways in which you may leave the REPL:
110 | * Hitting ``^c`` twice in a row, or
111 | * Hitting ``^g`` then ``q``
112 |
--------------------------------------------------------------------------------
/src/sequential/stdlib.md:
--------------------------------------------------------------------------------
1 | ## The Erlang Standard Library
2 |
3 | ### ``man`` Pages and Online Docs
4 | Erlang has a lot of standard modules to help you do things which are directly callable from LFE. For example, the module ``io`` contains a lot of functions to help you perform various acts of formatted input/output. Depending upon your Erlang installation, you may have man pages available. From your operating system shell, you can found out by typing ``erl -man `` like so:
5 |
6 | ```bash
7 | erl -man io
8 | ```
9 |
10 | If you have man pages installed, that command would give output along these lines:
11 |
12 | ```
13 | ERLANG MODULE DEFINITION io(3)
14 |
15 | MODULE
16 | io - Standard I/O Server Interface Functions
17 |
18 | DESCRIPTION
19 | This module provides an interface to standard Erlang IO
20 | servers. The output functions all return ok if they are suc-
21 | ...
22 | ```
23 |
24 | If your installation of Erlang doesn't have man pages, you can always find what you're looking for on the documentation web site. Here is the online `man` page for the [io module](http://erlang.org/doc/man/io.html).
25 |
26 | ### Module and Function Tab-Completion in the REPL
27 |
28 | From the LFE REPL, you have some other nice options for standard library discovery. Start up LFE to take a look:
29 |
30 | ```
31 | ./bin/lfe
32 | Erlang/OTP 23 [erts-11.0.2] [source] [64-bit] [smp:12:12] ...
33 |
34 | lfe>
35 | ```
36 |
37 | Now, at the prompt, hit your ```` key. You should see something like this:
38 |
39 | ```
40 | application application_controller application_master
41 | beam_lib binary c
42 | code code_server edlin
43 | edlin_expand epp erl_distribution
44 | erl_eval erl_parse erl_prim_loader
45 | erl_scan erlang error_handler
46 | error_logger error_logger_tty_h erts_internal
47 | ets file file_io_server
48 | file_server filename gb_sets
49 | gb_trees gen gen_event
50 | gen_server global global_group
51 | group heart hipe_unified_loader
52 | inet inet_config inet_db
53 | inet_parse inet_udp init
54 | io io_lib io_lib_format
55 | kernel kernel_config lfe_env
56 | lfe_eval lfe_init lfe_io
57 | lfe_shell lists net_kernel
58 | orddict os otp_ring0
59 | prim_eval prim_file prim_inet
60 | prim_zip proc_lib proplists
61 | ram_file rpc standard_error
62 | supervisor supervisor_bridge sys
63 | unicode user_drv user_sup
64 | zlib
65 | ```
66 |
67 | These are all the modules available to you by default in the LFE REPL. Now type ``(g`` and hit ````:
68 |
69 | ```lisp
70 | lfe> (g
71 | ```
72 | ```
73 | gb_sets gb_trees gen gen_event
74 | gen_server global global_group group
75 | ```
76 | Let's keep going! Continue typing a full module, and then hit ```` again:
77 |
78 | ```lisp
79 | lfe> (gb_trees:
80 | ```
81 | ```
82 | add/2 add_element/2 balance/1 del_element/2
83 | delete/2 delete_any/2 difference/2 empty/0
84 | filter/2 fold/3 from_list/1 from_ordset/1
85 | insert/2 intersection/1 intersection/2 is_disjoint/2
86 | is_element/2 is_empty/1 is_member/2 is_set/1
87 | is_subset/2 iterator/1 largest/1 module_info/0
88 | module_info/1 new/0 next/1 singleton/1
89 | size/1 smallest/1 subtract/2 take_largest/1
90 | take_smallest/1 to_list/1 union/1 union/2
91 | ```
92 |
93 | Now you can see all the *functions* that are available in the module you have selected. This is a great feature, allowing for easy use as well as exploration and discovery.
94 |
--------------------------------------------------------------------------------
/src/sequential/tuples.md:
--------------------------------------------------------------------------------
1 | ## Tuples
2 |
3 | Now the `tut4` program is hardly good programming style. Consider:
4 |
5 | ```lisp
6 | (tut4:convert 3 'inch)
7 | ```
8 |
9 | Does this mean that 3 is in inches? or that 3 is in centimetres and we want to convert it to inches? So LFE has a way to group things together to make things more understandable. We call these tuples. Tuples are constructed and matched using `(tuple ...)`, with literal tuples being written with `#( ... )`.
10 |
11 | So we can write `#(inch 3)` to denote 3 inches and `#(centimeter 5)` to denote 5 centimetres. Now let's write a new program which converts centimetres to inches and vice versa (file `tut5.lfe`).
12 |
13 | ```lisp
14 | (defmodule tut5
15 | (export (convert-length 1)))
16 |
17 | (defun convert-length
18 | (((tuple 'centimeter x)) (tuple 'inch (/ x 2.54)))
19 | (((tuple 'inch y)) (tuple 'centimeter (* y 2.54))))
20 | ```
21 |
22 | Compile and test:
23 |
24 | ```lisp
25 | (c "tut5.lfe")
26 | #(module tut5)
27 | lfe> (tut5:convert-length #(inch 5))
28 | #(centimeter 12.7)
29 | lfe> (tut5:convert-length (tut5:convert-length #(inch 5)))
30 | #(inch 5.0)
31 | ```
32 |
33 | Note that in the last call we convert 5 inches to centimetres and back again and reassuringly get back to the original value. I.e the argument to a function can be the result of another function. Pause for a moment and consider how that line (above) works. The argument we have given the function `#(inch 5)` is first matched against the first head clause of `convert-length` i.e. in `((tuple 'centimeter x))` where it can be seen that the pattern `(tuple 'centimeter x)` does not match `#(inch 5)` (the *head* is the first bit in the clause with a list of argument patterns). This having failed, we try the head of the next clause i.e. `((tuple 'inch y))`, this pattern matches `#(inch 5)` and `y` gets the value 5.
34 |
35 | We have shown tuples with two parts above, but tuples can have as many parts as we want and contain any valid LFE **term**. For example, to represent the temperature of various cities of the world we could write
36 |
37 | ```lisp
38 | #(Moscow #(C -10))
39 | #(Cape-Town #(F 70))
40 | #(Paris #(F 28))
41 | ```
42 |
43 | Tuples have a fixed number of things in them. We call each thing in a tuple an *element*. So in the tuple `#(Moscow #(C -10))`, element 1 is `Moscow` and element 2 is `#(C -10)`. We have chosen `C` meaning Celsius (or Centigrade) and `F` meaning Fahrenheit.
44 |
--------------------------------------------------------------------------------
/src/title.md:
--------------------------------------------------------------------------------
1 | # The LFE Tutorial
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | *Adapted from the Erlang "Getting Started" tutorial*
10 |
11 |
12 |
13 |
14 |
15 | Original Erlang version by the Erlang/OTP Team
16 |
17 | LFE translation by Robert Virding & Duncan McGreggor
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | [![publisher logo][publisher-img]][publisher-site]
31 |
32 |
33 |
34 | [publisher-img]: images/cnbb-pub-logo-1.6.png
35 | [publisher-site]: http://cnbb.pub/
36 |
--------------------------------------------------------------------------------
/theme/LICENSE:
--------------------------------------------------------------------------------
1 | Attribution-ShareAlike 4.0 International
2 |
3 | =======================================================================
4 |
5 | Creative Commons Corporation ("Creative Commons") is not a law firm and
6 | does not provide legal services or legal advice. Distribution of
7 | Creative Commons public licenses does not create a lawyer-client or
8 | other relationship. Creative Commons makes its licenses and related
9 | information available on an "as-is" basis. Creative Commons gives no
10 | warranties regarding its licenses, any material licensed under their
11 | terms and conditions, or any related information. Creative Commons
12 | disclaims all liability for damages resulting from their use to the
13 | fullest extent possible.
14 |
15 | Using Creative Commons Public Licenses
16 |
17 | Creative Commons public licenses provide a standard set of terms and
18 | conditions that creators and other rights holders may use to share
19 | original works of authorship and other material subject to copyright
20 | and certain other rights specified in the public license below. The
21 | following considerations are for informational purposes only, are not
22 | exhaustive, and do not form part of our licenses.
23 |
24 | Considerations for licensors: Our public licenses are
25 | intended for use by those authorized to give the public
26 | permission to use material in ways otherwise restricted by
27 | copyright and certain other rights. Our licenses are
28 | irrevocable. Licensors should read and understand the terms
29 | and conditions of the license they choose before applying it.
30 | Licensors should also secure all rights necessary before
31 | applying our licenses so that the public can reuse the
32 | material as expected. Licensors should clearly mark any
33 | material not subject to the license. This includes other CC-
34 | licensed material, or material used under an exception or
35 | limitation to copyright. More considerations for licensors:
36 | wiki.creativecommons.org/Considerations_for_licensors
37 |
38 | Considerations for the public: By using one of our public
39 | licenses, a licensor grants the public permission to use the
40 | licensed material under specified terms and conditions. If
41 | the licensor's permission is not necessary for any reason--for
42 | example, because of any applicable exception or limitation to
43 | copyright--then that use is not regulated by the license. Our
44 | licenses grant only permissions under copyright and certain
45 | other rights that a licensor has authority to grant. Use of
46 | the licensed material may still be restricted for other
47 | reasons, including because others have copyright or other
48 | rights in the material. A licensor may make special requests,
49 | such as asking that all changes be marked or described.
50 | Although not required by our licenses, you are encouraged to
51 | respect those requests where reasonable. More considerations
52 | for the public:
53 | wiki.creativecommons.org/Considerations_for_licensees
54 |
55 | =======================================================================
56 |
57 | Creative Commons Attribution-ShareAlike 4.0 International Public
58 | License
59 |
60 | By exercising the Licensed Rights (defined below), You accept and agree
61 | to be bound by the terms and conditions of this Creative Commons
62 | Attribution-ShareAlike 4.0 International Public License ("Public
63 | License"). To the extent this Public License may be interpreted as a
64 | contract, You are granted the Licensed Rights in consideration of Your
65 | acceptance of these terms and conditions, and the Licensor grants You
66 | such rights in consideration of benefits the Licensor receives from
67 | making the Licensed Material available under these terms and
68 | conditions.
69 |
70 |
71 | Section 1 -- Definitions.
72 |
73 | a. Adapted Material means material subject to Copyright and Similar
74 | Rights that is derived from or based upon the Licensed Material
75 | and in which the Licensed Material is translated, altered,
76 | arranged, transformed, or otherwise modified in a manner requiring
77 | permission under the Copyright and Similar Rights held by the
78 | Licensor. For purposes of this Public License, where the Licensed
79 | Material is a musical work, performance, or sound recording,
80 | Adapted Material is always produced where the Licensed Material is
81 | synched in timed relation with a moving image.
82 |
83 | b. Adapter's License means the license You apply to Your Copyright
84 | and Similar Rights in Your contributions to Adapted Material in
85 | accordance with the terms and conditions of this Public License.
86 |
87 | c. BY-SA Compatible License means a license listed at
88 | creativecommons.org/compatiblelicenses, approved by Creative
89 | Commons as essentially the equivalent of this Public License.
90 |
91 | d. Copyright and Similar Rights means copyright and/or similar rights
92 | closely related to copyright including, without limitation,
93 | performance, broadcast, sound recording, and Sui Generis Database
94 | Rights, without regard to how the rights are labeled or
95 | categorized. For purposes of this Public License, the rights
96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
97 | Rights.
98 |
99 | e. Effective Technological Measures means those measures that, in the
100 | absence of proper authority, may not be circumvented under laws
101 | fulfilling obligations under Article 11 of the WIPO Copyright
102 | Treaty adopted on December 20, 1996, and/or similar international
103 | agreements.
104 |
105 | f. Exceptions and Limitations means fair use, fair dealing, and/or
106 | any other exception or limitation to Copyright and Similar Rights
107 | that applies to Your use of the Licensed Material.
108 |
109 | g. License Elements means the license attributes listed in the name
110 | of a Creative Commons Public License. The License Elements of this
111 | Public License are Attribution and ShareAlike.
112 |
113 | h. Licensed Material means the artistic or literary work, database,
114 | or other material to which the Licensor applied this Public
115 | License.
116 |
117 | i. Licensed Rights means the rights granted to You subject to the
118 | terms and conditions of this Public License, which are limited to
119 | all Copyright and Similar Rights that apply to Your use of the
120 | Licensed Material and that the Licensor has authority to license.
121 |
122 | j. Licensor means the individual(s) or entity(ies) granting rights
123 | under this Public License.
124 |
125 | k. Share means to provide material to the public by any means or
126 | process that requires permission under the Licensed Rights, such
127 | as reproduction, public display, public performance, distribution,
128 | dissemination, communication, or importation, and to make material
129 | available to the public including in ways that members of the
130 | public may access the material from a place and at a time
131 | individually chosen by them.
132 |
133 | l. Sui Generis Database Rights means rights other than copyright
134 | resulting from Directive 96/9/EC of the European Parliament and of
135 | the Council of 11 March 1996 on the legal protection of databases,
136 | as amended and/or succeeded, as well as other essentially
137 | equivalent rights anywhere in the world.
138 |
139 | m. You means the individual or entity exercising the Licensed Rights
140 | under this Public License. Your has a corresponding meaning.
141 |
142 |
143 | Section 2 -- Scope.
144 |
145 | a. License grant.
146 |
147 | 1. Subject to the terms and conditions of this Public License,
148 | the Licensor hereby grants You a worldwide, royalty-free,
149 | non-sublicensable, non-exclusive, irrevocable license to
150 | exercise the Licensed Rights in the Licensed Material to:
151 |
152 | a. reproduce and Share the Licensed Material, in whole or
153 | in part; and
154 |
155 | b. produce, reproduce, and Share Adapted Material.
156 |
157 | 2. Exceptions and Limitations. For the avoidance of doubt, where
158 | Exceptions and Limitations apply to Your use, this Public
159 | License does not apply, and You do not need to comply with
160 | its terms and conditions.
161 |
162 | 3. Term. The term of this Public License is specified in Section
163 | 6(a).
164 |
165 | 4. Media and formats; technical modifications allowed. The
166 | Licensor authorizes You to exercise the Licensed Rights in
167 | all media and formats whether now known or hereafter created,
168 | and to make technical modifications necessary to do so. The
169 | Licensor waives and/or agrees not to assert any right or
170 | authority to forbid You from making technical modifications
171 | necessary to exercise the Licensed Rights, including
172 | technical modifications necessary to circumvent Effective
173 | Technological Measures. For purposes of this Public License,
174 | simply making modifications authorized by this Section 2(a)
175 | (4) never produces Adapted Material.
176 |
177 | 5. Downstream recipients.
178 |
179 | a. Offer from the Licensor -- Licensed Material. Every
180 | recipient of the Licensed Material automatically
181 | receives an offer from the Licensor to exercise the
182 | Licensed Rights under the terms and conditions of this
183 | Public License.
184 |
185 | b. Additional offer from the Licensor -- Adapted Material.
186 | Every recipient of Adapted Material from You
187 | automatically receives an offer from the Licensor to
188 | exercise the Licensed Rights in the Adapted Material
189 | under the conditions of the Adapter's License You apply.
190 |
191 | c. No downstream restrictions. You may not offer or impose
192 | any additional or different terms or conditions on, or
193 | apply any Effective Technological Measures to, the
194 | Licensed Material if doing so restricts exercise of the
195 | Licensed Rights by any recipient of the Licensed
196 | Material.
197 |
198 | 6. No endorsement. Nothing in this Public License constitutes or
199 | may be construed as permission to assert or imply that You
200 | are, or that Your use of the Licensed Material is, connected
201 | with, or sponsored, endorsed, or granted official status by,
202 | the Licensor or others designated to receive attribution as
203 | provided in Section 3(a)(1)(A)(i).
204 |
205 | b. Other rights.
206 |
207 | 1. Moral rights, such as the right of integrity, are not
208 | licensed under this Public License, nor are publicity,
209 | privacy, and/or other similar personality rights; however, to
210 | the extent possible, the Licensor waives and/or agrees not to
211 | assert any such rights held by the Licensor to the limited
212 | extent necessary to allow You to exercise the Licensed
213 | Rights, but not otherwise.
214 |
215 | 2. Patent and trademark rights are not licensed under this
216 | Public License.
217 |
218 | 3. To the extent possible, the Licensor waives any right to
219 | collect royalties from You for the exercise of the Licensed
220 | Rights, whether directly or through a collecting society
221 | under any voluntary or waivable statutory or compulsory
222 | licensing scheme. In all other cases the Licensor expressly
223 | reserves any right to collect such royalties.
224 |
225 |
226 | Section 3 -- License Conditions.
227 |
228 | Your exercise of the Licensed Rights is expressly made subject to the
229 | following conditions.
230 |
231 | a. Attribution.
232 |
233 | 1. If You Share the Licensed Material (including in modified
234 | form), You must:
235 |
236 | a. retain the following if it is supplied by the Licensor
237 | with the Licensed Material:
238 |
239 | i. identification of the creator(s) of the Licensed
240 | Material and any others designated to receive
241 | attribution, in any reasonable manner requested by
242 | the Licensor (including by pseudonym if
243 | designated);
244 |
245 | ii. a copyright notice;
246 |
247 | iii. a notice that refers to this Public License;
248 |
249 | iv. a notice that refers to the disclaimer of
250 | warranties;
251 |
252 | v. a URI or hyperlink to the Licensed Material to the
253 | extent reasonably practicable;
254 |
255 | b. indicate if You modified the Licensed Material and
256 | retain an indication of any previous modifications; and
257 |
258 | c. indicate the Licensed Material is licensed under this
259 | Public License, and include the text of, or the URI or
260 | hyperlink to, this Public License.
261 |
262 | 2. You may satisfy the conditions in Section 3(a)(1) in any
263 | reasonable manner based on the medium, means, and context in
264 | which You Share the Licensed Material. For example, it may be
265 | reasonable to satisfy the conditions by providing a URI or
266 | hyperlink to a resource that includes the required
267 | information.
268 |
269 | 3. If requested by the Licensor, You must remove any of the
270 | information required by Section 3(a)(1)(A) to the extent
271 | reasonably practicable.
272 |
273 | b. ShareAlike.
274 |
275 | In addition to the conditions in Section 3(a), if You Share
276 | Adapted Material You produce, the following conditions also apply.
277 |
278 | 1. The Adapter's License You apply must be a Creative Commons
279 | license with the same License Elements, this version or
280 | later, or a BY-SA Compatible License.
281 |
282 | 2. You must include the text of, or the URI or hyperlink to, the
283 | Adapter's License You apply. You may satisfy this condition
284 | in any reasonable manner based on the medium, means, and
285 | context in which You Share Adapted Material.
286 |
287 | 3. You may not offer or impose any additional or different terms
288 | or conditions on, or apply any Effective Technological
289 | Measures to, Adapted Material that restrict exercise of the
290 | rights granted under the Adapter's License You apply.
291 |
292 |
293 | Section 4 -- Sui Generis Database Rights.
294 |
295 | Where the Licensed Rights include Sui Generis Database Rights that
296 | apply to Your use of the Licensed Material:
297 |
298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
299 | to extract, reuse, reproduce, and Share all or a substantial
300 | portion of the contents of the database;
301 |
302 | b. if You include all or a substantial portion of the database
303 | contents in a database in which You have Sui Generis Database
304 | Rights, then the database in which You have Sui Generis Database
305 | Rights (but not its individual contents) is Adapted Material,
306 |
307 | including for purposes of Section 3(b); and
308 | c. You must comply with the conditions in Section 3(a) if You Share
309 | all or a substantial portion of the contents of the database.
310 |
311 | For the avoidance of doubt, this Section 4 supplements and does not
312 | replace Your obligations under this Public License where the Licensed
313 | Rights include other Copyright and Similar Rights.
314 |
315 |
316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
317 |
318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
328 |
329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
338 |
339 | c. The disclaimer of warranties and limitation of liability provided
340 | above shall be interpreted in a manner that, to the extent
341 | possible, most closely approximates an absolute disclaimer and
342 | waiver of all liability.
343 |
344 |
345 | Section 6 -- Term and Termination.
346 |
347 | a. This Public License applies for the term of the Copyright and
348 | Similar Rights licensed here. However, if You fail to comply with
349 | this Public License, then Your rights under this Public License
350 | terminate automatically.
351 |
352 | b. Where Your right to use the Licensed Material has terminated under
353 | Section 6(a), it reinstates:
354 |
355 | 1. automatically as of the date the violation is cured, provided
356 | it is cured within 30 days of Your discovery of the
357 | violation; or
358 |
359 | 2. upon express reinstatement by the Licensor.
360 |
361 | For the avoidance of doubt, this Section 6(b) does not affect any
362 | right the Licensor may have to seek remedies for Your violations
363 | of this Public License.
364 |
365 | c. For the avoidance of doubt, the Licensor may also offer the
366 | Licensed Material under separate terms or conditions or stop
367 | distributing the Licensed Material at any time; however, doing so
368 | will not terminate this Public License.
369 |
370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
371 | License.
372 |
373 |
374 | Section 7 -- Other Terms and Conditions.
375 |
376 | a. The Licensor shall not be bound by any additional or different
377 | terms or conditions communicated by You unless expressly agreed.
378 |
379 | b. Any arrangements, understandings, or agreements regarding the
380 | Licensed Material not stated herein are separate from and
381 | independent of the terms and conditions of this Public License.
382 |
383 |
384 | Section 8 -- Interpretation.
385 |
386 | a. For the avoidance of doubt, this Public License does not, and
387 | shall not be interpreted to, reduce, limit, restrict, or impose
388 | conditions on any use of the Licensed Material that could lawfully
389 | be made without permission under this Public License.
390 |
391 | b. To the extent possible, if any provision of this Public License is
392 | deemed unenforceable, it shall be automatically reformed to the
393 | minimum extent necessary to make it enforceable. If the provision
394 | cannot be reformed, it shall be severed from this Public License
395 | without affecting the enforceability of the remaining terms and
396 | conditions.
397 |
398 | c. No term or condition of this Public License will be waived and no
399 | failure to comply consented to unless expressly agreed to by the
400 | Licensor.
401 |
402 | d. Nothing in this Public License constitutes or may be interpreted
403 | as a limitation upon, or waiver of, any privileges and immunities
404 | that apply to the Licensor or You, including from the legal
405 | processes of any jurisdiction or authority.
406 |
407 |
408 | =======================================================================
409 |
410 | Creative Commons is not a party to its public
411 | licenses. Notwithstanding, Creative Commons may elect to apply one of
412 | its public licenses to material it publishes and in those instances
413 | will be considered the “Licensor.” The text of the Creative Commons
414 | public licenses is dedicated to the public domain under the CC0 Public
415 | Domain Dedication. Except for the limited purpose of indicating that
416 | material is shared under a Creative Commons public license or as
417 | otherwise permitted by the Creative Commons policies published at
418 | creativecommons.org/policies, Creative Commons does not authorize the
419 | use of the trademark "Creative Commons" or any other trademark or logo
420 | of Creative Commons without its prior written consent including,
421 | without limitation, in connection with any unauthorized modifications
422 | to any of its public licenses or any other arrangements,
423 | understandings, or agreements concerning use of licensed material. For
424 | the avoidance of doubt, this paragraph does not form part of the
425 | public licenses.
426 |
427 | Creative Commons may be contacted at creativecommons.org.
428 |
429 |
--------------------------------------------------------------------------------
/theme/README.md:
--------------------------------------------------------------------------------
1 | # The LFE `mdbook` Theme
2 |
3 | This repo is intended to be used as a git submodule in LFE book repos, so that all books may be maintained with the same theme from a single point.
4 |
5 | When cloned/added as a git submodule, this repo's local directory in the book repo should be named `theme` and should be at the top-level, in the same parent directory as the book `src` directory.
6 |
--------------------------------------------------------------------------------
/theme/css/chrome.css:
--------------------------------------------------------------------------------
1 | /* CSS for UI elements (a.k.a. chrome) */
2 |
3 | @import 'variables.css';
4 |
5 | ::-webkit-scrollbar {
6 | background: var(--bg);
7 | }
8 | ::-webkit-scrollbar-thumb {
9 | background: var(--scrollbar);
10 | }
11 | html {
12 | scrollbar-color: var(--scrollbar) var(--bg);
13 | }
14 | #searchresults a,
15 | .content a:link,
16 | a:visited,
17 | a > .hljs {
18 | color: var(--links);
19 | }
20 |
21 | /* Menu Bar */
22 |
23 | #menu-bar,
24 | #menu-bar-hover-placeholder {
25 | z-index: 101;
26 | margin: auto calc(0px - var(--page-padding));
27 | }
28 | #menu-bar {
29 | position: relative;
30 | display: flex;
31 | flex-wrap: wrap;
32 | background-color: var(--bg);
33 | border-bottom-color: var(--bg);
34 | border-bottom-width: 1px;
35 | border-bottom-style: solid;
36 | }
37 | #menu-bar.sticky,
38 | .js #menu-bar-hover-placeholder:hover + #menu-bar,
39 | .js #menu-bar:hover,
40 | .js.sidebar-visible #menu-bar {
41 | position: -webkit-sticky;
42 | position: sticky;
43 | top: 0 !important;
44 | }
45 | #menu-bar-hover-placeholder {
46 | position: sticky;
47 | position: -webkit-sticky;
48 | top: 0;
49 | height: var(--menu-bar-height);
50 | }
51 | #menu-bar.bordered {
52 | border-bottom-color: var(--table-border-color);
53 | }
54 | #menu-bar i, #menu-bar .icon-button {
55 | position: relative;
56 | padding: 0 8px;
57 | z-index: 10;
58 | line-height: var(--menu-bar-height);
59 | cursor: pointer;
60 | transition: color 0.5s;
61 | }
62 | @media only screen and (max-width: 420px) {
63 | #menu-bar i, #menu-bar .icon-button {
64 | padding: 0 5px;
65 | }
66 | }
67 |
68 | .icon-button {
69 | border: none;
70 | background: none;
71 | padding: 0;
72 | color: inherit;
73 | }
74 | .icon-button i {
75 | margin: 0;
76 | }
77 |
78 | .right-buttons {
79 | margin: 0 15px;
80 | }
81 | .right-buttons a {
82 | text-decoration: none;
83 | }
84 |
85 | .left-buttons {
86 | display: flex;
87 | margin: 0 5px;
88 | }
89 | .no-js .left-buttons {
90 | display: none;
91 | }
92 |
93 | .menu-title {
94 | display: inline-block;
95 | font-weight: 200;
96 | font-size: 2rem;
97 | line-height: var(--menu-bar-height);
98 | text-align: center;
99 | margin: 0;
100 | flex: 1;
101 | white-space: nowrap;
102 | overflow: hidden;
103 | text-overflow: ellipsis;
104 | }
105 | .js .menu-title {
106 | cursor: pointer;
107 | }
108 |
109 | .menu-bar,
110 | .menu-bar:visited,
111 | .nav-chapters,
112 | .nav-chapters:visited,
113 | .mobile-nav-chapters,
114 | .mobile-nav-chapters:visited,
115 | .menu-bar .icon-button,
116 | .menu-bar a i {
117 | color: var(--icons);
118 | }
119 |
120 | .menu-bar i:hover,
121 | .menu-bar .icon-button:hover,
122 | .nav-chapters:hover,
123 | .mobile-nav-chapters i:hover {
124 | color: var(--icons-hover);
125 | }
126 |
127 | /* Nav Icons */
128 |
129 | .nav-chapters {
130 | font-size: 2.5em;
131 | text-align: center;
132 | text-decoration: none;
133 |
134 | position: fixed;
135 | top: 0;
136 | bottom: 0;
137 | margin: 0;
138 | max-width: 150px;
139 | min-width: 90px;
140 |
141 | display: flex;
142 | justify-content: center;
143 | align-content: center;
144 | flex-direction: column;
145 |
146 | transition: color 0.5s, background-color 0.5s;
147 | }
148 |
149 | .nav-chapters:hover {
150 | text-decoration: none;
151 | background-color: var(--theme-hover);
152 | transition: background-color 0.15s, color 0.15s;
153 | }
154 |
155 | .nav-wrapper {
156 | margin-top: 50px;
157 | display: none;
158 | }
159 |
160 | .mobile-nav-chapters {
161 | font-size: 2.5em;
162 | text-align: center;
163 | text-decoration: none;
164 | width: 90px;
165 | border-radius: 5px;
166 | background-color: var(--sidebar-bg);
167 | }
168 |
169 | .previous {
170 | float: left;
171 | }
172 |
173 | .next {
174 | float: right;
175 | right: var(--page-padding);
176 | }
177 |
178 | @media only screen and (max-width: 1080px) {
179 | .nav-wide-wrapper { display: none; }
180 | .nav-wrapper { display: block; }
181 | }
182 |
183 | @media only screen and (max-width: 1380px) {
184 | .sidebar-visible .nav-wide-wrapper { display: none; }
185 | .sidebar-visible .nav-wrapper { display: block; }
186 | }
187 |
188 | /* Inline code */
189 |
190 | :not(pre) > .hljs {
191 | display: inline;
192 | padding: 0.1em 0.3em;
193 | border-radius: 3px;
194 | }
195 |
196 | :not(pre):not(a) > .hljs {
197 | color: var(--inline-code-color);
198 | overflow-x: initial;
199 | }
200 |
201 | a:hover > .hljs {
202 | text-decoration: underline;
203 | }
204 |
205 | pre {
206 | position: relative;
207 | }
208 | pre > .buttons {
209 | position: absolute;
210 | z-index: 100;
211 | right: 5px;
212 | top: 5px;
213 |
214 | color: var(--sidebar-fg);
215 | cursor: pointer;
216 | }
217 | pre > .buttons :hover {
218 | color: var(--sidebar-active);
219 | }
220 | pre > .buttons i {
221 | margin-left: 8px;
222 | }
223 | pre > .buttons button {
224 | color: inherit;
225 | background: transparent;
226 | border: none;
227 | cursor: inherit;
228 | }
229 | pre > .result {
230 | margin-top: 10px;
231 | }
232 |
233 | /* Search */
234 |
235 | #searchresults a {
236 | text-decoration: none;
237 | }
238 |
239 | mark {
240 | border-radius: 2px;
241 | padding: 0 3px 1px 3px;
242 | margin: 0 -3px -1px -3px;
243 | background-color: var(--search-mark-bg);
244 | transition: background-color 300ms linear;
245 | cursor: pointer;
246 | }
247 |
248 | mark.fade-out {
249 | background-color: rgba(0,0,0,0) !important;
250 | cursor: auto;
251 | }
252 |
253 | .searchbar-outer {
254 | margin-left: auto;
255 | margin-right: auto;
256 | max-width: var(--content-max-width);
257 | }
258 |
259 | #searchbar {
260 | width: 100%;
261 | margin: 5px auto 0px auto;
262 | padding: 10px 16px;
263 | transition: box-shadow 300ms ease-in-out;
264 | border: 1px solid var(--searchbar-border-color);
265 | border-radius: 3px;
266 | background-color: var(--searchbar-bg);
267 | color: var(--searchbar-fg);
268 | }
269 | #searchbar:focus,
270 | #searchbar.active {
271 | box-shadow: 0 0 3px var(--searchbar-shadow-color);
272 | }
273 |
274 | .searchresults-header {
275 | font-weight: bold;
276 | font-size: 1em;
277 | padding: 18px 0 0 5px;
278 | color: var(--searchresults-header-fg);
279 | }
280 |
281 | .searchresults-outer {
282 | margin-left: auto;
283 | margin-right: auto;
284 | max-width: var(--content-max-width);
285 | border-bottom: 1px dashed var(--searchresults-border-color);
286 | }
287 |
288 | ul#searchresults {
289 | list-style: none;
290 | padding-left: 20px;
291 | }
292 | ul#searchresults li {
293 | margin: 10px 0px;
294 | padding: 2px;
295 | border-radius: 2px;
296 | }
297 | ul#searchresults li.focus {
298 | background-color: var(--searchresults-li-bg);
299 | }
300 | ul#searchresults span.teaser {
301 | display: block;
302 | clear: both;
303 | margin: 5px 0 0 20px;
304 | font-size: 0.8em;
305 | }
306 | ul#searchresults span.teaser em {
307 | font-weight: bold;
308 | font-style: normal;
309 | }
310 |
311 | /* Sidebar */
312 |
313 | .sidebar {
314 | position: fixed;
315 | left: 0;
316 | top: 0;
317 | bottom: 0;
318 | width: var(--sidebar-width);
319 | font-size: 0.875em;
320 | box-sizing: border-box;
321 | -webkit-overflow-scrolling: touch;
322 | overscroll-behavior-y: contain;
323 | background-color: var(--sidebar-bg);
324 | color: var(--sidebar-fg);
325 | }
326 | .sidebar-resizing {
327 | -moz-user-select: none;
328 | -webkit-user-select: none;
329 | -ms-user-select: none;
330 | user-select: none;
331 | }
332 | .js:not(.sidebar-resizing) .sidebar {
333 | transition: transform 0.3s; /* Animation: slide away */
334 | }
335 | .sidebar code {
336 | line-height: 2em;
337 | }
338 | .sidebar .sidebar-scrollbox {
339 | overflow-y: auto;
340 | position: absolute;
341 | top: 0;
342 | bottom: 0;
343 | left: 0;
344 | right: 0;
345 | padding: 10px 10px;
346 | }
347 | .sidebar .sidebar-resize-handle {
348 | position: absolute;
349 | cursor: col-resize;
350 | width: 0;
351 | right: 0;
352 | top: 0;
353 | bottom: 0;
354 | }
355 | .js .sidebar .sidebar-resize-handle {
356 | cursor: col-resize;
357 | width: 5px;
358 | }
359 | .sidebar-hidden .sidebar {
360 | transform: translateX(calc(0px - var(--sidebar-width)));
361 | }
362 | .sidebar::-webkit-scrollbar {
363 | background: var(--sidebar-bg);
364 | }
365 | .sidebar::-webkit-scrollbar-thumb {
366 | background: var(--scrollbar);
367 | }
368 |
369 | .sidebar-visible .page-wrapper {
370 | transform: translateX(var(--sidebar-width));
371 | }
372 | @media only screen and (min-width: 620px) {
373 | .sidebar-visible .page-wrapper {
374 | transform: none;
375 | margin-left: var(--sidebar-width);
376 | }
377 | }
378 |
379 | .chapter {
380 | list-style: none outside none;
381 | padding-left: 0;
382 | line-height: 2.2em;
383 | }
384 |
385 | .chapter ol {
386 | width: 100%;
387 | }
388 |
389 | .chapter li {
390 | display: flex;
391 | color: var(--sidebar-non-existant);
392 | }
393 | .chapter li a {
394 | display: block;
395 | padding: 0;
396 | text-decoration: none;
397 | color: var(--sidebar-fg);
398 | }
399 |
400 | .chapter li a:hover {
401 | color: var(--sidebar-active);
402 | }
403 |
404 | .chapter li a.active {
405 | color: var(--sidebar-active);
406 | }
407 |
408 | .chapter li > a.toggle {
409 | cursor: pointer;
410 | display: block;
411 | margin-left: auto;
412 | padding: 0 10px;
413 | user-select: none;
414 | opacity: 0.68;
415 | }
416 |
417 | .chapter li > a.toggle div {
418 | transition: transform 0.5s;
419 | }
420 |
421 | /* collapse the section */
422 | .chapter li:not(.expanded) + li > ol {
423 | display: none;
424 | }
425 |
426 | .chapter li.chapter-item {
427 | line-height: 1.5em;
428 | margin-top: 0.6em;
429 | }
430 |
431 | .chapter li.expanded > a.toggle div {
432 | transform: rotate(90deg);
433 | }
434 |
435 | .spacer {
436 | width: 100%;
437 | height: 3px;
438 | margin: 5px 0px;
439 | }
440 | .chapter .spacer {
441 | background-color: var(--sidebar-spacer);
442 | }
443 |
444 | @media (-moz-touch-enabled: 1), (pointer: coarse) {
445 | .chapter li a { padding: 5px 0; }
446 | .spacer { margin: 10px 0; }
447 | }
448 |
449 | .section {
450 | list-style: none outside none;
451 | padding-left: 20px;
452 | line-height: 1.9em;
453 | }
454 |
455 | /* Theme Menu Popup */
456 |
457 | .theme-popup {
458 | position: absolute;
459 | left: 10px;
460 | top: var(--menu-bar-height);
461 | z-index: 1000;
462 | border-radius: 4px;
463 | font-size: 0.7em;
464 | color: var(--fg);
465 | background: var(--theme-popup-bg);
466 | border: 1px solid var(--theme-popup-border);
467 | margin: 0;
468 | padding: 0;
469 | list-style: none;
470 | display: none;
471 | }
472 | .theme-popup .default {
473 | color: var(--icons);
474 | }
475 | .theme-popup .theme {
476 | width: 100%;
477 | border: 0;
478 | margin: 0;
479 | padding: 2px 10px;
480 | line-height: 25px;
481 | white-space: nowrap;
482 | text-align: left;
483 | cursor: pointer;
484 | color: inherit;
485 | background: inherit;
486 | font-size: inherit;
487 | }
488 | .theme-popup .theme:hover {
489 | background-color: var(--theme-hover);
490 | }
491 | .theme-popup .theme:hover:first-child,
492 | .theme-popup .theme:hover:last-child {
493 | border-top-left-radius: inherit;
494 | border-top-right-radius: inherit;
495 | }
496 |
--------------------------------------------------------------------------------
/theme/css/general.css:
--------------------------------------------------------------------------------
1 | /* Base styles and content styles */
2 |
3 | @import 'variables.css';
4 |
5 | :root {
6 | /* Browser default font-size is 16px, this way 1 rem = 10px */
7 | font-size: 62.5%;
8 | }
9 |
10 | html {
11 | font-family: "Open Sans", sans-serif;
12 | color: var(--fg);
13 | background-color: var(--bg);
14 | text-size-adjust: none;
15 | }
16 |
17 | body {
18 | margin: 0;
19 | font-size: 1.6rem;
20 | overflow-x: hidden;
21 | }
22 |
23 | code {
24 | font-family: "Source Code Pro", Consolas, "Ubuntu Mono", Menlo, "DejaVu Sans Mono", monospace, monospace !important;
25 | font-size: 0.875em; /* please adjust the ace font size accordingly in editor.js */
26 | }
27 |
28 | /* Don't change font size in headers. */
29 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
30 | font-size: unset;
31 | }
32 |
33 | .left { float: left; }
34 | .right { float: right; }
35 | .boring { opacity: 0.6; }
36 | .hide-boring .boring { display: none; }
37 | .hidden { display: none !important; }
38 |
39 | h2, h3 { margin-top: 2.5em; }
40 | h4, h5 { margin-top: 2em; }
41 |
42 | .header + .header h3,
43 | .header + .header h4,
44 | .header + .header h5 {
45 | margin-top: 1em;
46 | }
47 |
48 | h1 a.header:target::before,
49 | h2 a.header:target::before,
50 | h3 a.header:target::before,
51 | h4 a.header:target::before {
52 | display: inline-block;
53 | content: "»";
54 | margin-left: -30px;
55 | width: 30px;
56 | }
57 |
58 | h1 a.header:target,
59 | h2 a.header:target,
60 | h3 a.header:target,
61 | h4 a.header:target {
62 | scroll-margin-top: calc(var(--menu-bar-height) + 0.5em);
63 | }
64 |
65 | .page {
66 | outline: 0;
67 | padding: 0 var(--page-padding);
68 | margin-top: calc(0px - var(--menu-bar-height)); /* Compensate for the #menu-bar-hover-placeholder */
69 | }
70 | .page-wrapper {
71 | box-sizing: border-box;
72 | }
73 | .js:not(.sidebar-resizing) .page-wrapper {
74 | transition: margin-left 0.3s ease, transform 0.3s ease; /* Animation: slide away */
75 | }
76 |
77 | .content {
78 | overflow-y: auto;
79 | padding: 0 15px;
80 | padding-bottom: 50px;
81 | }
82 | .content main {
83 | margin-left: auto;
84 | margin-right: auto;
85 | max-width: var(--content-max-width);
86 | }
87 | .content p { line-height: 1.45em; }
88 | .content ol { line-height: 1.45em; }
89 | .content ul { line-height: 1.45em; }
90 | .content a { text-decoration: none; }
91 | .content a:hover { text-decoration: underline; }
92 | .content img { max-width: 100%; }
93 | .content .header:link,
94 | .content .header:visited {
95 | color: var(--fg);
96 | }
97 | .content .header:link,
98 | .content .header:visited:hover {
99 | text-decoration: none;
100 | }
101 |
102 | table {
103 | margin: 0 auto;
104 | border-collapse: collapse;
105 | }
106 | table td {
107 | padding: 3px 20px;
108 | border: 1px var(--table-border-color) solid;
109 | }
110 | table thead {
111 | background: var(--table-header-bg);
112 | }
113 | table thead td {
114 | font-weight: 700;
115 | border: none;
116 | }
117 | table thead th {
118 | padding: 3px 20px;
119 | }
120 | table thead tr {
121 | border: 1px var(--table-header-bg) solid;
122 | }
123 | /* Alternate background colors for rows */
124 | table tbody tr:nth-child(2n) {
125 | background: var(--table-alternate-bg);
126 | }
127 |
128 |
129 | blockquote {
130 | margin: 20px 0;
131 | padding: 0 20px;
132 | color: var(--fg);
133 | background-color: var(--quote-bg);
134 | border-top: .1em solid var(--quote-border);
135 | border-bottom: .1em solid var(--quote-border);
136 | }
137 |
138 |
139 | :not(.footnote-definition) + .footnote-definition,
140 | .footnote-definition + :not(.footnote-definition) {
141 | margin-top: 2em;
142 | }
143 | .footnote-definition {
144 | font-size: 0.9em;
145 | margin: 0.5em 0;
146 | }
147 | .footnote-definition p {
148 | display: inline;
149 | }
150 |
151 | .tooltiptext {
152 | position: absolute;
153 | visibility: hidden;
154 | color: #fff;
155 | background-color: #333;
156 | transform: translateX(-50%); /* Center by moving tooltip 50% of its width left */
157 | left: -8px; /* Half of the width of the icon */
158 | top: -35px;
159 | font-size: 0.8em;
160 | text-align: center;
161 | border-radius: 6px;
162 | padding: 5px 8px;
163 | margin: 5px;
164 | z-index: 1000;
165 | }
166 | .tooltipped .tooltiptext {
167 | visibility: visible;
168 | }
169 |
170 | .chapter li.part-title {
171 | color: var(--sidebar-fg);
172 | margin: 5px 0px;
173 | font-weight: bold;
174 | }
175 |
176 |
177 | /*
178 | *
179 | * Items introduced by the LFE Theme
180 | *
181 | */
182 |
183 | code,
184 | :not(pre):not(a)>.hljs {
185 | background-color: var(--inline-code-background-color);
186 | color: var(--inline-code-color);
187 | }
188 |
189 | .alert {
190 | position: relative;
191 | padding: 0.75rem 1.25rem;
192 | margin-bottom: 1rem;
193 | border: 1px solid transparent;
194 | border-radius: 6px;
195 | }
196 |
197 | .alert-heading {
198 | color: inherit;
199 | margin: 0;
200 | padding: 0;
201 | }
202 |
203 | .alert-link {
204 | font-weight: 700;
205 | }
206 |
207 | /* Primary */
208 |
209 | .alert-primary {
210 | color: var(--alert-primary-fg);
211 | background-color: var(--alert-primary-bg);
212 | border-color: var(--alert-primary-border);
213 | }
214 |
215 | .alert-primary hr {
216 | border-top-color: var(--alert-primary-hr);
217 | }
218 |
219 | .alert-primary .alert-link {
220 | color: var(--alert-primary-link);
221 | }
222 |
223 | .alert-primary,
224 | .alert-primary>th,
225 | .alert-primary>td {
226 | background-color: var(--alert-primary-bg);
227 | }
228 |
229 | /* Secondary */
230 |
231 | .alert-secondary {
232 | color: var(--alert-secondary-fg);
233 | background-color: var(--alert-secondary-bg);
234 | border-color: var(--alert-secondary-border);
235 | }
236 |
237 | .alert-secondary hr {
238 | border-top-color: var(--alert-secondary-hr);
239 | }
240 |
241 | .alert-secondary .alert-link {
242 | color: var(--alert-secondary-link);
243 | }
244 |
245 | .alert-secondary,
246 | .alert-secondary>th,
247 | .alert-secondary>td {
248 | background-color: var(--alert-secondary-bg);
249 | }
250 |
251 | /* Success */
252 |
253 | .alert-success {
254 | color: var(--alert-success-fg);
255 | background-color: var(--alert-success-bg);
256 | border-color: var(--alert-success-border);
257 | }
258 |
259 | .alert-success hr {
260 | border-top-color: var(--alert-success-hr);
261 | }
262 |
263 | .alert-success .alert-link {
264 | color: var(--alert-success-link);
265 | }
266 |
267 | .alert-success,
268 | .alert-success>th,
269 | .alert-success>td {
270 | background-color: var(--alert-success-bg);
271 | }
272 |
273 | /* Info */
274 |
275 | .alert-info {
276 | color: var(--alert-info-fg);
277 | background-color: var(--alert-info-bg);
278 | border-color: var(--alert-info-border);
279 | }
280 |
281 | .alert-info hr {
282 | border-top-color: var(--alert-info-hr);
283 | }
284 |
285 | .alert-info .alert-link {
286 | color: var(--alert-info-link);
287 | }
288 |
289 | .alert-info,
290 | .alert-info>th,
291 | .alert-info>td {
292 | background-color: var(--alert-info-bg);
293 | }
294 |
295 | /* Warning */
296 |
297 | .alert-warning {
298 | color: var(--alert-warning-fg);
299 | background-color: var(--alert-warning-bg);
300 | border-color: var(--alert-warning-border);
301 | }
302 |
303 | .alert-warning hr {
304 | border-top-color: var(--alert-warning-hr);
305 | }
306 |
307 | .alert-warning .alert-link {
308 | color: var(--alert-warning-link);
309 | }
310 |
311 | .alert-warning,
312 | .alert-warning>th,
313 | .alert-warning>td {
314 | background-color: var(--alert-warning-bg);
315 | }
316 |
317 | /* Danger */
318 |
319 | .alert-danger {
320 | color: var(--alert-danger-fg);
321 | background-color: var(--alert-danger-bg);
322 | border-color: var(--alert-danger-border);
323 | }
324 |
325 | .alert-danger hr {
326 | border-top-color: var(--alert-danger-hr);
327 | }
328 |
329 | .alert-danger .alert-link {
330 | color: var(--alert-danger-link);
331 | }
332 |
333 | .alert-danger,
334 | .alert-danger>th,
335 | .alert-danger>td {
336 | background-color: var(--alert-danger-bg);
337 | }
338 |
339 | /* Footnotes */
340 |
341 | .footnotes {
342 | font-size: 10pt;
343 | padding-top: 2em;
344 | margin-top: 2em;
345 | padding-left: 0;
346 | padding-right: 0;
347 | margin-left: 0;
348 | margin-right: 0;
349 | }
350 |
351 | .footnotes ol,
352 | .footnotes ol li {
353 | padding-left: 0;
354 | padding-right: 0;
355 | margin-left: 0.6em;
356 | margin-right: 0;
357 | }
358 |
359 | .footnotes ol {
360 | /* margin-left: -1em; */
361 | }
362 |
363 | .footnotes hr {
364 | border: 0;
365 | border-bottom: solid 1px #666;
366 | }
367 |
--------------------------------------------------------------------------------
/theme/css/print.css:
--------------------------------------------------------------------------------
1 |
2 | #sidebar,
3 | #menu-bar,
4 | .nav-chapters,
5 | .mobile-nav-chapters {
6 | display: none;
7 | }
8 |
9 | #page-wrapper.page-wrapper {
10 | transform: none;
11 | margin-left: 0px;
12 | overflow-y: initial;
13 | }
14 |
15 | #content {
16 | max-width: none;
17 | margin: 0;
18 | padding: 0;
19 | }
20 |
21 | .page {
22 | overflow-y: initial;
23 | }
24 |
25 | code {
26 | background-color: #666666;
27 | border-radius: 5px;
28 |
29 | /* Force background to be printed in Chrome */
30 | -webkit-print-color-adjust: exact;
31 | }
32 |
33 | pre > .buttons {
34 | z-index: 2;
35 | }
36 |
37 | a, a:visited, a:active, a:hover {
38 | color: #4183c4;
39 | text-decoration: none;
40 | }
41 |
42 | h1, h2, h3, h4, h5, h6 {
43 | page-break-inside: avoid;
44 | page-break-after: avoid;
45 | }
46 |
47 | pre, code {
48 | page-break-inside: avoid;
49 | white-space: pre-wrap;
50 | }
51 |
52 | .fa {
53 | display: none !important;
54 | }
55 |
--------------------------------------------------------------------------------
/theme/css/variables.css:
--------------------------------------------------------------------------------
1 | /* Globals */
2 |
3 | :root {
4 | --sidebar-width: 300px;
5 | --page-padding: 15px;
6 | --content-max-width: 750px;
7 | --menu-bar-height: 50px;
8 | }
9 |
10 | /* Themes */
11 |
12 | .lfe-pdp {
13 | --bg: #D9D8D4;
14 | --fg: #1E252B;
15 |
16 | --sidebar-bg: #16161A;
17 | --sidebar-fg: #ECE5C9;
18 | --sidebar-non-existant: #505254;
19 | --sidebar-active: #377CA2;
20 | --sidebar-spacer: #0B2D3F;
21 |
22 | --scrollbar: var(--sidebar-fg);
23 |
24 | --icons: #737480;
25 | --icons-hover: #1E252B;
26 |
27 | --links: #377CA2;
28 |
29 | --inline-code-color: #16455E;
30 | --inline-code-background-color: transparent;
31 |
32 | --theme-popup-bg: #DDD09C;
33 | --theme-popup-border: #b38f6b;
34 | --theme-hover: #B4BEC4;
35 |
36 | --quote-bg: hsl(60, 5%, 75%);
37 | --quote-border: hsl(60, 5%, 70%);
38 |
39 | --table-border-color: hsl(60, 9%, 82%);
40 | --table-header-bg: #b3a497;
41 | --table-alternate-bg: hsl(60, 9%, 84%);
42 |
43 | --searchbar-border-color: #aaa;
44 | --searchbar-bg: #fafafa;
45 | --searchbar-fg: #000;
46 | --searchbar-shadow-color: #aaa;
47 | --searchresults-header-fg: #666;
48 | --searchresults-border-color: #888;
49 | --searchresults-li-bg: #dec2a2;
50 | --search-mark-bg: #e69f67;
51 |
52 | /* New, custom styles */
53 | --alert-primary-fg: #1a3047;
54 | //--alert-primary-bg: #d6dfe7;
55 | --alert-primary-bg: #325D88;
56 | --alert-primary-border: #c6d2de;
57 | --alert-primary-hr: #b6c5d5;
58 | --alert-primary-link: #0c1722;
59 |
60 |
61 | --alert-secondary-fg: #4a4945;
62 | //--alert-secondary-bg: #e8e8e6;
63 | --alert-secondary-bg: #8E8C84;
64 | --alert-secondary-border: #b4b465;
65 | --alert-secondary-hr: #d3d3d0;
66 | --alert-secondary-link: #302f2c;
67 |
68 |
69 | --alert-success-fg: #4c6627;
70 | //--alert-success-bg: #e9f3db;
71 | --alert-success-bg: #93C54B;
72 | --alert-success-border: #94b861;
73 | --alert-success-hr: #d5e9ba;
74 | --alert-success-link: #314119;
75 |
76 |
77 | --alert-info-fg: #155974;
78 | //--alert-info-bg: #d4eef9;
79 | --alert-info-bg: #29ABE0;
80 | --alert-info-border: #66a6c2;
81 | --alert-info-hr: #addef3;
82 | --alert-info-link: #0d3849;
83 |
84 |
85 | --alert-warning-fg: #7f401f;
86 | //--alert-warning-bg: #fde5d8;
87 | --alert-warning-bg: #F47C3C;
88 | --alert-warning-border: #ce865f;
89 | --alert-warning-hr: #fbcab0;
90 | --alert-warning-link: #562b15;
91 |
92 | --alert-danger-fg: #712b29;
93 | //--alert-danger-bg: #f7dddc;
94 | --alert-danger-bg: #d9534f;
95 | --alert-danger-border: #c07471;
96 | --alert-danger-hr: #efbbb9;
97 | --alert-danger-link: #4c1d1b;
98 | }
99 |
100 | .ayu {
101 | --bg: hsl(210, 25%, 8%);
102 | --fg: #c5c5c5;
103 |
104 | --sidebar-bg: #14191f;
105 | --sidebar-fg: #c8c9db;
106 | --sidebar-non-existant: #5c6773;
107 | --sidebar-active: #ffb454;
108 | --sidebar-spacer: #2d334f;
109 |
110 | --scrollbar: var(--sidebar-fg);
111 |
112 | --icons: #737480;
113 | --icons-hover: #b7b9cc;
114 |
115 | --links: #0096cf;
116 |
117 | --inline-code-color: #ffb454;
118 |
119 | --theme-popup-bg: #14191f;
120 | --theme-popup-border: #5c6773;
121 | --theme-hover: #191f26;
122 |
123 | --quote-bg: hsl(226, 15%, 17%);
124 | --quote-border: hsl(226, 15%, 22%);
125 |
126 | --table-border-color: hsl(210, 25%, 13%);
127 | --table-header-bg: hsl(210, 25%, 28%);
128 | --table-alternate-bg: hsl(210, 25%, 11%);
129 |
130 | --searchbar-border-color: #848484;
131 | --searchbar-bg: #424242;
132 | --searchbar-fg: #fff;
133 | --searchbar-shadow-color: #d4c89f;
134 | --searchresults-header-fg: #666;
135 | --searchresults-border-color: #888;
136 | --searchresults-li-bg: #252932;
137 | --search-mark-bg: #e3b171;
138 | }
139 |
140 | .coal {
141 | --bg: hsl(200, 7%, 8%);
142 | --fg: #98a3ad;
143 |
144 | --sidebar-bg: #292c2f;
145 | --sidebar-fg: #a1adb8;
146 | --sidebar-non-existant: #505254;
147 | --sidebar-active: #3473ad;
148 | --sidebar-spacer: #393939;
149 |
150 | --scrollbar: var(--sidebar-fg);
151 |
152 | --icons: #43484d;
153 | --icons-hover: #b3c0cc;
154 |
155 | --links: #2b79a2;
156 |
157 | --inline-code-color: #c5c8c6;
158 | ;
159 |
160 | --theme-popup-bg: #141617;
161 | --theme-popup-border: #43484d;
162 | --theme-hover: #1f2124;
163 |
164 | --quote-bg: hsl(234, 21%, 18%);
165 | --quote-border: hsl(234, 21%, 23%);
166 |
167 | --table-border-color: hsl(200, 7%, 13%);
168 | --table-header-bg: hsl(200, 7%, 28%);
169 | --table-alternate-bg: hsl(200, 7%, 11%);
170 |
171 | --searchbar-border-color: #aaa;
172 | --searchbar-bg: #b7b7b7;
173 | --searchbar-fg: #000;
174 | --searchbar-shadow-color: #aaa;
175 | --searchresults-header-fg: #666;
176 | --searchresults-border-color: #98a3ad;
177 | --searchresults-li-bg: #2b2b2f;
178 | --search-mark-bg: #355c7d;
179 | }
180 |
181 | .light {
182 | --bg: hsl(0, 0%, 100%);
183 | --fg: #333333;
184 |
185 | --sidebar-bg: #fafafa;
186 | --sidebar-fg: #364149;
187 | --sidebar-non-existant: #aaaaaa;
188 | --sidebar-active: #008cff;
189 | --sidebar-spacer: #f4f4f4;
190 |
191 | --scrollbar: #cccccc;
192 |
193 | --icons: #cccccc;
194 | --icons-hover: #333333;
195 |
196 | --links: #4183c4;
197 |
198 | --inline-code-color: #6e6b5e;
199 |
200 | --theme-popup-bg: #fafafa;
201 | --theme-popup-border: #cccccc;
202 | --theme-hover: #e6e6e6;
203 |
204 | --quote-bg: hsl(197, 37%, 96%);
205 | --quote-border: hsl(197, 37%, 91%);
206 |
207 | --table-border-color: hsl(0, 0%, 95%);
208 | --table-header-bg: hsl(0, 0%, 80%);
209 | --table-alternate-bg: hsl(0, 0%, 97%);
210 |
211 | --searchbar-border-color: #aaa;
212 | --searchbar-bg: #fafafa;
213 | --searchbar-fg: #000;
214 | --searchbar-shadow-color: #aaa;
215 | --searchresults-header-fg: #666;
216 | --searchresults-border-color: #888;
217 | --searchresults-li-bg: #e4f2fe;
218 | --search-mark-bg: #a2cff5;
219 | }
220 |
221 | .navy {
222 | --bg: hsl(226, 23%, 11%);
223 | --fg: #bcbdd0;
224 |
225 | --sidebar-bg: #282d3f;
226 | --sidebar-fg: #c8c9db;
227 | --sidebar-non-existant: #505274;
228 | --sidebar-active: #2b79a2;
229 | --sidebar-spacer: #2d334f;
230 |
231 | --scrollbar: var(--sidebar-fg);
232 |
233 | --icons: #737480;
234 | --icons-hover: #b7b9cc;
235 |
236 | --links: #2b79a2;
237 |
238 | --inline-code-color: #c5c8c6;
239 | ;
240 |
241 | --theme-popup-bg: #161923;
242 | --theme-popup-border: #737480;
243 | --theme-hover: #282e40;
244 |
245 | --quote-bg: hsl(226, 15%, 17%);
246 | --quote-border: hsl(226, 15%, 22%);
247 |
248 | --table-border-color: hsl(226, 23%, 16%);
249 | --table-header-bg: hsl(226, 23%, 31%);
250 | --table-alternate-bg: hsl(226, 23%, 14%);
251 |
252 | --searchbar-border-color: #aaa;
253 | --searchbar-bg: #aeaec6;
254 | --searchbar-fg: #000;
255 | --searchbar-shadow-color: #aaa;
256 | --searchresults-header-fg: #5f5f71;
257 | --searchresults-border-color: #5c5c68;
258 | --searchresults-li-bg: #242430;
259 | --search-mark-bg: #a2cff5;
260 | }
261 |
262 | .rust {
263 | --bg: hsl(60, 9%, 87%);
264 | --fg: #262625;
265 |
266 | --sidebar-bg: #3b2e2a;
267 | --sidebar-fg: #c8c9db;
268 | --sidebar-non-existant: #505254;
269 | --sidebar-active: #e69f67;
270 | --sidebar-spacer: #45373a;
271 |
272 | --scrollbar: var(--sidebar-fg);
273 |
274 | --icons: #737480;
275 | --icons-hover: #262625;
276 |
277 | --links: #2b79a2;
278 |
279 | --inline-code-color: #6e6b5e;
280 |
281 | --theme-popup-bg: #e1e1db;
282 | --theme-popup-border: #b38f6b;
283 | --theme-hover: #99908a;
284 |
285 | --quote-bg: hsl(60, 5%, 75%);
286 | --quote-border: hsl(60, 5%, 70%);
287 |
288 | --table-border-color: hsl(60, 9%, 82%);
289 | --table-header-bg: #b3a497;
290 | --table-alternate-bg: hsl(60, 9%, 84%);
291 |
292 | --searchbar-border-color: #aaa;
293 | --searchbar-bg: #fafafa;
294 | --searchbar-fg: #000;
295 | --searchbar-shadow-color: #aaa;
296 | --searchresults-header-fg: #666;
297 | --searchresults-border-color: #888;
298 | --searchresults-li-bg: #dec2a2;
299 | --search-mark-bg: #e69f67;
300 | }
301 |
302 | @media (prefers-color-scheme: dark) {
303 | .light.no-js {
304 | --bg: hsl(200, 7%, 8%);
305 | --fg: #98a3ad;
306 |
307 | --sidebar-bg: #292c2f;
308 | --sidebar-fg: #a1adb8;
309 | --sidebar-non-existant: #505254;
310 | --sidebar-active: #3473ad;
311 | --sidebar-spacer: #393939;
312 |
313 | --scrollbar: var(--sidebar-fg);
314 |
315 | --icons: #43484d;
316 | --icons-hover: #b3c0cc;
317 |
318 | --links: #2b79a2;
319 |
320 | --inline-code-color: #c5c8c6;
321 | ;
322 |
323 | --theme-popup-bg: #141617;
324 | --theme-popup-border: #43484d;
325 | --theme-hover: #1f2124;
326 |
327 | --quote-bg: hsl(234, 21%, 18%);
328 | --quote-border: hsl(234, 21%, 23%);
329 |
330 | --table-border-color: hsl(200, 7%, 13%);
331 | --table-header-bg: hsl(200, 7%, 28%);
332 | --table-alternate-bg: hsl(200, 7%, 11%);
333 |
334 | --searchbar-border-color: #aaa;
335 | --searchbar-bg: #b7b7b7;
336 | --searchbar-fg: #000;
337 | --searchbar-shadow-color: #aaa;
338 | --searchresults-header-fg: #666;
339 | --searchresults-border-color: #98a3ad;
340 | --searchresults-li-bg: #2b2b2f;
341 | --search-mark-bg: #355c7d;
342 | }
343 | }
344 |
--------------------------------------------------------------------------------
/theme/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cnbbooks/lfe-tutorial/b1c1e1b004fa33ee26fcd4ecbe1ea3222a0f7507/theme/favicon.png
--------------------------------------------------------------------------------
/theme/favicon.svg:
--------------------------------------------------------------------------------
1 |
22 |
23 |
--------------------------------------------------------------------------------
/theme/highlight.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Night Owl for highlight.js (c) Carl Baxter
4 |
5 | An adaptation of Sarah Drasner's Night Owl VS Code Theme
6 | https://github.com/sdras/night-owl-vscode-theme
7 |
8 | Copyright (c) 2018 Sarah Drasner
9 |
10 | Permission is hereby granted, free of charge, to any person obtaining a copy
11 | of this software and associated documentation files (the "Software"), to deal
12 | in the Software without restriction, including without limitation the rights
13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 | copies of the Software, and to permit persons to whom the Software is
15 | furnished to do so, subject to the following conditions:
16 |
17 | The above copyright notice and this permission notice shall be included in all
18 | copies or substantial portions of the Software.
19 |
20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 | SOFTWARE.
27 |
28 | */
29 |
30 | .hljs {
31 | display: block;
32 | overflow-x: auto;
33 | padding: 0.5em;
34 | background: #011627;
35 | color: #d6deeb;
36 | }
37 |
38 | /* General Purpose */
39 | .hljs-keyword {
40 | color: #c792ea;
41 | font-style: italic;
42 | }
43 | .hljs-built_in {
44 | color: #addb67;
45 | font-style: italic;
46 | }
47 | .hljs-type {
48 | color: #82aaff;
49 | }
50 | .hljs-literal {
51 | color: #ff5874;
52 | }
53 | .hljs-number {
54 | color: #F78C6C;
55 | }
56 | .hljs-regexp {
57 | color: #5ca7e4;
58 | }
59 | .hljs-string {
60 | color: #ecc48d;
61 | }
62 | .hljs-subst {
63 | color: #d3423e;
64 | }
65 | .hljs-symbol {
66 | color: #82aaff;
67 | }
68 | .hljs-class {
69 | color: #ffcb8b;
70 | }
71 | .hljs-function {
72 | color: #82AAFF;
73 | }
74 | .hljs-title {
75 | color: #DCDCAA;
76 | font-style: italic;
77 | }
78 | .hljs-params {
79 | color: #7fdbca;
80 | }
81 |
82 | /* Meta */
83 | .hljs-comment {
84 | color: #637777;
85 | font-style: italic;
86 | }
87 | .hljs-doctag {
88 | color: #7fdbca;
89 | }
90 | .hljs-meta {
91 | color: #82aaff;
92 | }
93 | .hljs-meta-keyword {
94 | color: #82aaff;
95 | }
96 | .hljs-meta-string {
97 | color: #ecc48d;
98 | }
99 |
100 | /* Tags, attributes, config */
101 | .hljs-section {
102 | color: #82b1ff;
103 | }
104 | .hljs-tag,
105 | .hljs-name,
106 | .hljs-builtin-name {
107 | color: #7fdbca;
108 | }
109 | .hljs-attr {
110 | color: #7fdbca;
111 | }
112 | .hljs-attribute {
113 | color: #80cbc4;
114 | }
115 | .hljs-variable {
116 | color: #addb67;
117 | }
118 |
119 | /* Markup */
120 | .hljs-bullet {
121 | color: #d9f5dd;
122 | }
123 | .hljs-code {
124 | color: #80CBC4;
125 | }
126 | .hljs-emphasis {
127 | color: #c792ea;
128 | font-style: italic;
129 | }
130 | .hljs-strong {
131 | color: #addb67;
132 | font-weight: bold;
133 | }
134 | .hljs-formula {
135 | color: #c792ea;
136 | }
137 | .hljs-link {
138 | color: #ff869a;
139 | }
140 | .hljs-quote {
141 | color: #697098;
142 | font-style: italic;
143 | }
144 |
145 | /* CSS */
146 | .hljs-selector-tag {
147 | color: #ff6363;
148 | }
149 |
150 | .hljs-selector-id {
151 | color: #fad430;
152 | }
153 |
154 | .hljs-selector-class {
155 | color: #addb67;
156 | font-style: italic;
157 | }
158 |
159 | .hljs-selector-attr,
160 | .hljs-selector-pseudo {
161 | color: #c792ea;
162 | font-style: italic;
163 | }
164 |
165 | /* Templates */
166 | .hljs-template-tag {
167 | color: #c792ea;
168 | }
169 | .hljs-template-variable {
170 | color: #addb67;
171 | }
172 |
173 | /* diff */
174 | .hljs-addition {
175 | color: #addb67ff;
176 | font-style: italic;
177 | }
178 |
179 | .hljs-deletion {
180 | color: #EF535090;
181 | font-style: italic;
182 | }
183 |
--------------------------------------------------------------------------------
/theme/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ title }}
8 | {{#if is_print }}
9 |
10 | {{/if}}
11 | {{#if base_url}}
12 |
13 | {{/if}}
14 |
15 |
16 |
17 | {{> head}}
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | {{#if copy_fonts}}
34 |
35 | {{/if}}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {{#each additional_css}}
44 |
45 | {{/each}}
46 |
47 | {{#if mathjax_support}}
48 |
49 |
51 | {{/if}}
52 |
53 |
54 |
55 |
56 |
60 |
61 |
62 |
76 |
77 |
78 |
88 |
89 |
90 |
100 |
101 |
107 |
108 |