About this guide
This is a guide to LISPsmos, the programming language which compiles to Desmos expressions. This document is CTRL+F-friendly, so if you're looking to do a specific thing, just hit Ctrl+F, and you'll be likely to find the result with few (if any) false positives!
What is LISPsmos?
I like to create horrendously overcomplicated things in Desmos— a 3D maze, a photorealistic 3D fractal that took ten hours to render, and a 3D game where you fly a plane, to name a few. However, working in Desmos is somewhat challenging... to say the least. So I wanted to create a tool to make it easier. And thus, I created LISPsmos.
LISPsmos is— put simply‐ a programming language that compiles to Desmos expressions. Well, it's kind of more than that: It's a text-based language you can use to build entire Desmos graphs from the ground up.
Installation / Quick Start
- Get NodeJS, if you haven't already.
- Create a directory in which you will put your first LISPsmos project. Call it whatever you want.
- Go to the terminal, navigate to the directory you've created, and type in
npm install lispsmos
. This will allow you to use LISPsmos - Create a file
index.mjs
in the directory. Paste the following code into index.mjs:import * as lispsmos from "lispsmos"; lispsmos.buildServer(async () => { let compiler = new lispsmos.compiler.LispsmosCompiler(); console.log("Compiled!"); return await compiler.compile("(displayMe (= y (^ x 3)))") })
- Type
node index.mjs
to start the web server. - Try testing your web server by going to http://localhost:8090. You can use a custom port and hostname (second and third parameter to
buildServer
) if you so desire. - Now that we've created a LISPsmos server, we need to tell Desmos how to receive the LISPsmos graph. First, get Tampermonkey.
- Install this Tampermonkey userscript, which will add a text box to Desmos which will enable you to load Desmos graph state from any source.
- Once the userscript is installed and enabled, find the new textbox in Desmos in the bottom-right corner, type
http://localhost:8090
into it, and press the "Grab State" button. You should see that the graph has changed to show the equation Y=x3. This userscript does the exact same thing that you did when you tried to go to http://localhost:8090— it requests the Desmos state, which the server promptly compiles. However, after that, it gives the graph state to Desmos, which displays it like it would any other graph.
If you managed to get everything in this section working, move onto the next section.
Installation, Continued
The web server we created in the last part was a minimal example— enough to get LISPsmos working, but a very suboptimal way of using it. You may have noticed that we are directly compiling a string literal. As you can imagine, this will be inconvenient to edit, should we want to add more LISPsmos code. Let's make it get the LISPsmos code from a file called main.lisp
instead. Replace the contents of index.mjs
with the following:
import * as lispsmos from "lispsmos";
import * as fs from "node:fs";
lispsmos.buildServer(async () => {
let compiler = new lispsmos.compiler.LispsmosCompiler();
lispsmos.utilityMacros.register(compiler);
console.log("Compiled!");
return await compiler.compile(fs.readFileSync("main.lisp"))
})
Now, create a file main.lisp
in the same directory and put the following in it:
(displayMe (= y (^ x 3)))
If you try running the web server again, you should end up displaying the same equation as before. However, this time, LISPsmos will be compiling the code in the file main.lisp
, not the string literal.
Syntax
Polish Notation
LISPsmos follows an extremely simple (but quite unorthodox) syntax called Polish notation. Normally, when you're doing math— for instance, 1 + 2 + 3
— you put the operator +
between the operands 1
, 2
, and 3
. In LISPsmos, which uses Polish notation, you would write the same expression like this: (+ 1 2 3)
. The first thing in the list— the "+
," is treated as an operator, whereas everything after it is treated as its operands. This may seem very odd to some. However, there are some advantages to this approach which I hope you will come to appreciate: For one, it's extremely consistent— every operator in LISPsmos works like this, making one's mental model of the syntax much simpler. For another advantage, it also cuts down on extra operators. Notice how we only used a single +
by using Polish notation. Conversely, with the more "standard" infix notation, we used two of them! On top of all this, Polish notation isn't actually that far off from what we already do with other functions: For instance, sin(x)
becomes (sin x)
and mod(x, 2)
becomes (mod x 2)
.
Do be aware, however, that the semantics of LISPsmos are somewhat more complicated than this at times— some complex operators may define slightly different rules that do not exist outside of them (which will be explained as they come up). However, the underlying syntax will remain the same.
Basic Examples
Here are some examples of LISPsmos syntax compared to (approximately) how they would appear in Desmos:
LISPsmos | Desmos Output (approx) |
---|---|
(+ 1 2 3 4 5) |
\left(1+2+3+4+5\right) |
(* 5 (+ 2 3)) |
\left(5*\left(2+3\right)\right) |
(sin (* 3 x)) |
\sin\left(\left(3*x\right)\right) |
Variables
Create a variable with the =
operator. Be aware that (like everything in LISPsmos) this follows Polish notation. Here is an example
LISPsmos | Desmos Output (approx) |
---|---|
(= a 5) |
a=5 |
(= y (^ x 2)) |
y=x^{2} |
Variables can have multi-character names. LISPsmos will represent these with subscripts in Desmos.
LISPsmos | Desmos Output (approx) |
---|---|
(= foo 12) |
f_{oo}=12 |
Functions
Create a function with the fn
keyword, followed by its name, then its arguments separated by spaces, and then finally its actual definition in a single block. Take note of the second example— LISPsmos does not care about line breaks in the middle of expressions. Use them to make long expressions easier to read.
LISPsmos | Desmos Output (approx) |
---|---|
(fn square x (^ x 2)) |
s_{quare}\left(x\right)=x^{2} s_{quare}\left(4\right) |
|
d_{ist}\left(x_{1},y_{1},x_{2},y_{2}\right)=\sqrt{\left(\left(x_{1}-x_{2}\right)^{2}+\left(y_{1}-y_{2}\right)^{2}\right)} |
Display/Show Expressions
Display an Expression
Up until now, you won't have seen anything on the screen upon loading your LISPsmos expressions. This changes now— use the displayMe
operator to display an expression. The expression should be the first argument to displayMe
. Be warned that you can only put a single expression into displayMe.
LISPsmos | Desmos Output (approx) |
---|---|
(displayMe (= y x))
|
y=x |
Display Properties
You can set "display properties" of an expression to customize the way it is displayed. For instance, the graph produced by the following LISPsmos code (not shown on screen) will have a line width of 20 pixels, be dotted, have 20% opacity, and be a bright red color.
LISPsmos | Desmos Output (approx) |
---|---|
|
y=x |
Points
LISPsmos | Desmos Output (approx) | Description |
---|---|---|
(point 0 0) |
\left(0,0\right) | Creates a point at the origin, (0, 0). |
(= pt (point 3 4)) |
p_{t}=\left(3,4\right) p_{t}.x |
Creates a point called pt, and then finds its x-coordinate with the ".x" operator. The second expression should evaluate to 3. |
Lists
Create a list with the builtin list
function. These lists can also contain ranges with ...
LISPsmos | Desmos Output (approx) |
---|---|
(list 1 2 3 4 5 6 7) |
\left[1,2,3,4,5,6,7\right] |
(list 1 ... 20) |
\left[1,...,20\right] |
Piecewises
Create a piecewise with the builtin piecewise
function. Every argument to the piecewise function takes the form of a parenthesized pair of statements. The first one is the condition for that branch of the piecewise, whereas the second is the value which the piecewise is to evaluate to.
LISPsmos | Desmos Output (approx) | Description |
---|---|---|
(piecewise ((> x 0) x) ((<= x 0) (* -1 x))) |
\left\{x>0:x,x<=0:\left(-1*x\right)\right\} | The absolute value function, represented as a piecewise. |
(piecewise ((> x 0) x) ((* -1 x))) |
\left\{x>0:x,\left(-1*x\right)\right\} | The condition for the last branch of the piecewise can be omitted to mean that it should be used when none of the other branches match. |
Actions
Just as a variable can be created with =
, an action can be created with ->
.
LISPsmos | Desmos Output (approx) | Description |
---|---|---|
(= a 0) |
a=0 a\to\left(a+1\right) |
Viewport/Graph Bounds
Want to change the size of the default starting graph? LISPsmos can do that too. For instance, the line (viewport -2 2 -2 2)
will try to make the graph range from -2 to 2 on the x-axis, and -2 to 2 on the y-axis. However, Desmos will still try to preserve aspect ratio, so this will not be exact!
Folders
Create a folder with the folder
function. The following defines a folder named "equations":
(folder ((title "equations"))
(= y x)
(= y (^ x 2))
(= y (^ x 3))
)
Macros
Macros are one of LISPsmos's most powerful features. They allow you to eliminate repetitive sections of code which would otherwise be tedious to write by hand. Macros are not enabled by default in LISPsmos— you can enable them by adding the following line to your JS file immediately after defining the variable compiler
:
lispsmos.utilityMacros.register(compiler);
LISPsmos | Desmos Output (approx) | Description |
---|---|---|
(defineFindAndReplace plusOne variable ((+ variable 1))) |
\left(x+1\right) | The first line of LISPsmos code defines a "find-and-replace" macro called plusOne . This macro takes a single argument called variable and returns a statement which adds one to that variable. The second line of code calls that macro with x as a parameter. You can imagine an intermediate step here where (plusOne x) is substituted with (+ variable 1) (as per the macro definition)— except variable is x (since we're calling plusOne with x ), so we end up with (+ x 1) |
(inlineJS "return [['=', 'y', ['*', 'x', '3']]]") |
y=\left(x*3\right) | inlineJS macros are an extremely powerful feature of LISPsmos that let you define snippets of JavaScript code that create parsed LISPsmos code at compile time. Parsed LISPsmos code becomes what I call "AST Nodes." An AST Node can be one of two things: a string, or an array of AST Nodes. An AST node represents a list of items enclosed within parentheses, whereas a string represents an individual item— a variable, operator, function name, macro name, et cetera. |