From 55d1c5d323b19014f2413dc2adf55e2df7fb3fa5 Mon Sep 17 00:00:00 2001 From: Elijah Cohen Date: Sun, 1 Dec 2024 17:17:03 -0600 Subject: [PATCH] Adds documentation, some small fixes --- demos.kl | 2 + doc/builtins.html | 524 ++++++++++++++++++++++++++++++++++++++++++++ doc/index.html | 131 +++++++++++ doc/style.css | 202 +++++++++++++++++ src/builtins.c | 2 + src/builtins/meta.c | 13 ++ src/builtins/meta.h | 7 +- src/repl.c | 4 +- src/test.c | 8 +- web/shell.html | 12 +- 10 files changed, 891 insertions(+), 14 deletions(-) create mode 100644 doc/builtins.html create mode 100644 doc/index.html create mode 100644 doc/style.css diff --git a/demos.kl b/demos.kl index b724c21..45960f9 100644 --- a/demos.kl +++ b/demos.kl @@ -14,6 +14,8 @@ (def U (L O)) (def F (E T T E T)) +(def if I) + (def church-and (B W (B C) I)) (def church-or (S I I)) (def church-not (B C (C I) nil t)) diff --git a/doc/builtins.html b/doc/builtins.html new file mode 100644 index 0000000..41016d4 --- /dev/null +++ b/doc/builtins.html @@ -0,0 +1,524 @@ + + + + + + Builtins + + + +
+
+

Builtins

+

The operations built in to the language are described here, roughly organized by topic and significance. Examples ought to be included.

+ +
+

core

+

These operations are generally the most basic and foundational parts of the language, that make the underlying bits work

+
+ quote + cons + car + cdr + eq + not + atom + rest + unquote + type + def + exit + applyn +
+
+

quote

+

quote ought to function in much the same way it does in other lisps. Perhaps it's a bit more complicated and maybe a bit less well-defined, but the idea is the same. Applies to one argument, unevaluated, returns its quoted form. I might change things up with this for various reasons, just a heads up

+
+← (quote asdf)
+ → asdf
+← (quote (+ 4 5)
+ → (+ 4 5)
+
+
+

cons

+

cons is another standard from lisps, it combines two values into a cons cell. Applies to two arguments, returns the cons cell containing those two values.

+
+← (cons 4 5)
+ → (4 . 5)
+← (cons 4 (cons 5 (cons 6 nil)))
+ → (4 5 6)
+
+
+

car

+

car takes a cons cell, returns the first element (the 'car') of the pairing. Applies to one argument.

+
+← (car (cons 4 5))
+ → 4
+← (car (cons (cons 4 5) 6))
+ → (4 . 5)
+← (car (list 1 2 3))
+ → 1
+
+
+

cdr

+

cdr takes a cons cell, returns the second element (the 'cdr') of the pairing. Applies to one argument.

+
+← (cdr (cons 4 5))
+ → 5
+← (cdr (cons (cons 4 5) 6))
+ → 6
+← (cdr (list 1 2 3))
+ → (2 3)
+
+
+

eq

+

eq determines whether two things are equal. Applies to two arguments, returns t if equal and nil otherwise.

+
+← (eq 4 5)
+ → nil
+← (eq 5 (+ 2 3))
+ → t
+← (eq 5 "5")
+ → nil
+← (eq (list 1 2 3) (cons 1 (cons 2 (cons 3 nil))))
+ → t
+
+
+

not

+

not applies to one argument. If that argument evaluates to nil, it returns t, otherwise it returns nil.

+
+← (not t)
+ → nil
+← (not nil)
+ → t
+← (not "truthy value")
+ → nil
+← (not (cdr (list 1)))
+ → t
+
+
+

atom

+

atom applies to one argument. If that argument evaluates to a cons cell, it returns nil, otherwise it returns t

+
+← (atom 4)
+ → t
+← (atom (+ 4 5))
+ → t
+← (atom (list 1 2 3 4))
+ → nil
+← (atom (cons 3 4))
+ → nil
+
+
+

rest

+

rest is the first 'novel' operation in this language, and (as of now) is the only 'variadic' operation in the language. rest requires at least two arguments to evaluate. The first argument is a function, which is applied to the rest the arguments collected in a list. That is, (rest f 1 2 3) evaluates as if it were (f (list 1 2 3)). Is used to construct other variadic functions (such as list itself).

+
+← (rest quote 1 2 3)
+ → (1 2 3)
+← (rest car 1 2 3)
+ → 1
+← (rest car 1 2 3)
+ → (2 3)
+
+
+

unquote

+

unquote takes things out of a quote (if applicable). Applies to a single argument, and functions somewhat as a pseudo-eval. To be used perhaps sparingly or delicately.

+
+← (unquote 5)
+ → 5
+← (unquote (quote 5))
+ → 5
+← (unquote (quote (+ 4 5)))
+ → 9
+
+
+

type

+

type takes one argument and returns a string representing that argument's type. Perhaps not what I want a 'type' operator to do in the long run, but for the time being it's pretty swell.

+
+← (type 5)
+ → "uint"
+← (type "uint")
+ → "string"
+← (type nil)
+ → "nil"
+
+
+

def

+

It's how we set (global) variables. Applies to two arguments, a symbol and anything else, and sets the symbol to the value of anything else. The background implementation of this is liable to change, so the fun mysterious behaviours of not using this one as described may become more or less interesting.

+
+← (def six (+ 2 4))
+ → 6
+← (def five 5)
+ → 5
+← (def plus +)
+ → _512
+note: the prior line just shows how builtins might be represented in the language
+← (plus five six)
+ → 11
+
+
+

exit

+

This one takes a single argument and exits with the argument as the return value. (exit 0) is the standard way to exit a program or the interpreter without error.

+
+
+

applyn

+

This is a secret mystery operation. It's not directly accessible, but is called upon tacitly when using uints as functions. It takes first a uint n, then a function, then a final argument, and applies the function to the argument n times.

+
+← (3 cdr (list 1 2 3 4))
+ → (4)
+← (4 (+ 5) 0)
+ → 20
+
+
+
+

arithmetic

+

Basic arithmetic operations

+
+ add + sub + mul + div + mod +
+
+

+ (add)

+

Applies to two uints, adds them, returns the sum.

+
+← (+ 4 5)
+ → 9
+
+
+

- (subtract)

+

Applies to two uints, subtracts them, returns the difference.

+
+← (- 8 5)
+ → 3
+
+
+

* (multiply)

+

Applies to two uints, multiplies them, returns the product.

+
+← (* 4 5)
+ → 20
+
+
+

/ (divide)

+

Applies to two uints, does division on them, returns the unsigned integer quotient.

+
+← (/ 36 4)
+ → 9
+← (/ 37 5)
+-> 7
+
+
+

% (modulus)

+

Applies to two uints, calculates the value of the first modulo the second, returns it.

+
+← (% 10 3)
+ → 1
+← (% 30 7)
+ → 2
+
+
+
+

combinators

+

This might just be the heart of the language. It's the basics for how more complicated functions are composed, and how things get moved around and operated on. Incredibly useful for many partial application of functions. Heavy inspiration from languages like APL, BQN, and related variants, as well as various Forths and other stack-based languages. They will typically be described in terms of how they transform a sequence of arguments. The bird names are also provided in parenthesis

+
+ I + S + K + B + C + W + Phi + Psi + Z +
+
+

I

+

The I (Idiot) combinator is the identity, it returns whatever it is given. It takes I a to a

+
+← (I 8)
+ → 8
+< (I + 3 5)
+ → 8
+
+
+

S

+

The S (Starling) combinator takes S a b c to a c (b c)

+
+← (S * (+ 4) 3)
+ → 21
+← (S cons cdr (list 3 4))
+ → ((3 4) 4)
+
+
+

K

+

The K (Kestrel) combinator takes two arguments and returns the first, or in other words K a b to a

+
+← (K 4 5)
+ → 4
+
+
+

B

+

The B (Bluebird) is the composition combinator which takes three arguments, and applies the first to the application of the second to the third. B a b c goes to a (b c)

+
+← (B car cdr (list 1 2 3))
+ → 2
+← (B (eq 5) car (list 5 6 7))
+ → t
+
+
+

C

+

The C (Cardinal) combinator swaps its second and third arguments, it takes C a b c to a c b +

+← (C - 4 5)
+ → 1
+← (C cons 4 5)
+ → (5 . 4)
+
+
+

W

+

The W (Warbler) combinator duplicates its second argument, taking W a b to a b b

+
+← (W + 5)
+ → 10
+← (def square (W *))
+ → _261
+← (square 7)
+ → 49
+
+
+

Phi

+

The Phi (Phoenix) combinator takes Phi a b c d to a (b d) (c d), and is very useful for restructuring lists and cons cells

+
+← (Phi cons cdr car (cons 4 5))
+ → (5 . 4)
+← (Phi * I (+ 1) 7)
+ → 56
+
+
+

Psi

+

The Psi combinator takes Psi a b c d to a (b c) (b d)

+
+← (Psi * (+ 5) 2 3)
+ → 56
+← (Psi cons cdr (list 1 2 3) (list 3 4 5))
+ → ((2 3) 4 5)
+
+
+

Z

+

The Z combinator is probably the most complicated and the most crucial combinator here. It allows for recursion, by taking Z g v to g (Z g) v. It is incredibly useful, but also rather difficult to use given the context of the rest of the language at this point. So, enjoy the example below, which is a non-optimized implementation of factorial

+
+← (def fac (Z ((B B) S (C (eq 0) 1) ((B B) S * (C B (C - 1))))))
+ → _264
+← (fac 5)
+ → 120
+
+
+
+

io

+

Operations for printing strings, reading and writing files, things of that nature, all very platform-dependent I'm sure

+
+ print + printstr + pb + readfile + writefile +
+
+

print

+

print takes one argument, and prints it to standard output, then returns true if it successfuly did so

+
+← (print (list 1 2 3 4 5))
+(1 2 3 4 5)
+ → t
+← (print "hello, world!")
+"hello, world!"
+ → t
+
+
+

printstr

+

printstr prints a string to standard output (without the quotation marks), returns t if successfully done, and returns nil otherwise (or if passed something that's not a string)

+
+← printstr "hello, world!"
+hello, world!
+ → t
+← printstr 45
+ → nil
+
+
+

pb

+

pb prints a builtin with any arguments partially applied in reverse order. Pretty handy for debugging and such, but should probably be avoided otherwise +

+← (pb +)
+ → _+<nil>
+← (pb (+ 5))
+ → _+<(5)>
+← (pb (+ 5 4))
+ → 9
+
+
+

readfile

+

readfile applies to one argument, a string representing the path to a file. If the string does not match a file or cannot open it, it returns nil, otherwise it returns a string containing the file's contents. Doesn't yet do anything fancy with path expansions or anything like that, gotta be specific

+
+← (readfile "myfile.txt")
+ → "these are the contents of 'myfile.txt"
+
+
+

writefile

+

writefile applies to two arguments, a string representing the path to a file, and a string to write into that file. If the file string does not match a file or cannot open or write to it, it returns nil, otherwise it the number of bytes written. Again, doesn't yet do anything fancy with path expansions or anything like that, gotta be specific

+
+← (writefile "myfile.txt" "writing\na\nstring\nto\nfile")
+ → 24
+
+
+
+

strings

+

Operations on strings, nothing more to it

+
+ strlen + strcat + strat + strexpand + substr + strtok + tostr +
+
+

strlen takes a string and returns its length

+
+← (strlen "hello, world!")
+ → 13
+← (strlen "escape\ncharacters")
+ → 17
+
+
+

strcat

+

strcat concatenates two strings, simple as that

+
+← (strcat "hello, " "world!")
+ → "hello, world!"
+
+
+

strat

+

strat takes a number n and a string, and returns the nth character of the string. Poorly named, I know

+
+← (strat 3 "test")
+ → "s"
+
+
+

strexpand takes a string, and returns a list of its characters. Breaks down with unicode

+
+← (strexpand "test")
+ → ("t" "e" "s" "t")
+
+
+

substr

+

substr takes two strings, and returns a list of indexes where the first string begins in the second

+
+← (substr "hi" "chili")
+ → (2)
+← (substr "oo" "loooong woords")
+ → (2 3 4 10)
+
+
+

strtok

+

strtok takes a two strings, and returns a list of substrings of the second string separated by characters in the first

+
+← (strtok " " "hello world")
+ → ("hello" "world")
+← (strtok " \n" "showing how\nthis works")
+ → ("showing" "how" "this" "works")
+
+
+

tostr

+

tostr takes one argument and returns its representation as a string

+
+← (tostr (cons 45 "hi")
+ → "(45 . "hi")"
+					
+
+
+

meta

+

This is the messy stuff, if you wanna break things keep reading. Maybe the most interesting bits of messing and extending the language

+
+ utob + btou + parse + lookup + getargs + getenv + setenv +
+
+

utob

+

utob takes a uint, and returns a builtin whose operation corresponds with the value passed. The numbers will most likely be subject to change with rearrangements or additions

+
+← (utob 512)
+ → _512
+← ((utob 512) 4 5)
+ → 9
+
+
+

btou

+

btou takes a builtin operation and returns the uint whose value corresponds with the operation. A sort of 'opcode', if you will

+
+← (btou +)
+ → 512
+← (btou (+ 5))
+ → 512
+
+
+

parse

+

parse parses a string into an s-expression, the same way the repl does it pretty much

+
+← (parse "(cons 4 5)")
+ → ((cons 4 5))
+
+
+

getargs

+

getargs takes a builtin, and returns any arguments partially applied to it. These arguments are almost surely unevaluated

+
+← (getargs (+ 5))
+ → (5)
+← (getargs (S + (+ 4)))
+ → ((+ 4) +)
+
+
+

lookup

+

lookup looks into the environment to see if the passed argument is defined. Returns nil if undefined, returns a single-element list with its value as the car if it is

+
+← (lookup +)
+ → (_512)
+← (lookup notdefined)
+→ nil
+← (lookup nil)
+→ (nil)
+
+
+

getenv

+

getenv returns the current environment, that is, all the definitions, as an s-expression (which, incidentally, is its internal representation). Must pass it a t for it to work

+
+← (getenv t)
+ → <some big long list of things>
+
+
+

setenv

+

Here there be monsters. setenv allows you to overwrite the environment completely. This is dangerous and you may end up without any sort of meaningfully functional repl if you ever do this. Have fun!

+
+
+
+
+ + diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..c3525c3 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,131 @@ + + + + + + klaupacius + + + +
+
+

klaupacius

+

A language so inspired by lisps, forths and array languages that it resembles none of these.

+

A combinator-based, concatenative, tacit lisp with automatic memory management, immutability, partial application, some church encodings, without variables or lambdas, where everything is a function.

+ +
+

Setting Out

+

This language emerged as a result of thinking about computing on stack machines. I wanted to spawn some sort of convergence of lisp-like and forth-like languages, I wanted to take the point-free style one gets in forths, and bring it into something with a structure based more upon s-expressions. It's a take on stack-based programming, but where we work from the other direction in a sense.

+

To demonstrate, let's break down something simple, like (+ 4 5). If one has some experience with other lisps, this code might appear pretty obvious, and even without that experience, if one were to look at a plus, a four, and a five arranged like so, one might guess that this program adds four and five, returning 9. And yes, that's exactly what happens, but understanding how we get there sheds light on how we can understand this language. It's already been stated that everything is a function, but it might be helpful to think of everything as a monadic function, a function that takes only one argument (which generally holds true, save for a particularly exciting and useful exception). But isn't the above-mentioned + a function that takes two values? Sort of! In this case, it is reasonable to think of + as a function that takes one argument n, and returns another function, which takes one argument m and returns n + m. Breaking this down a bit further reveals some aspects of the underlying system.

+

How might we think about + being a monadic function that returns a function? Well let's start by breaking our example into 'simpler' pieces. Let's start with just (+). If we type that into our repl and hit enter, we get _512 in return. What is this? You're right to ask. Things prefixed with an underscore like that are just how internal operations are printed, the underscore followed by a numeric value, here 512 corresponds to our plus. So let's call this function now. Type in (+ 4), and you get _512 in return. The same thing? What happened to the 4? I promise it's still in there, it's just printed like that to not overwhelm the output when dealing with more complicated matters. You can even get the four back out, doing getargs (+ 4) returns a list of arguments applied to any builtin operation (here, we'd get (4)).

+

So, if you're still with me, now we have a nice new function (+ 4). Let's call this function now. Give it ((+ 4) 5), and indeed you'll get a 9 in return! When it's parenthesized this way, I feel it's easier to understand that (+ 4) is itself a function, and can be applied to a number to get the sum of that number and four. So now is there a difference between ((+ 4) 5) and (+ 4 5)? Practically speaking they're the same. When the interpreter evaluates a statement like (a b c d), it takes a and b off the top of the list, appplies a to b, and sticks that (let's call it ab) to the beginning of the list and starts over again. So (a b c d) becomes (ab c d) which in turn becomes (abc d), which goes to (abcd), and now since there aren't any more arguments, it drops the parenthesis giving us abcd. This gives us a decent amount of leeway with the use of parenthesis in a way that's different from other lisps. The expressions +

(+ 4 5)
+((+ 4) 5)
+(((+) 4) 5)
+(((+ ((4)) 5)))
+ all evaluate to the same thing, in (practically) the same way. This allows us to use parenthesis for refactoring or to make code clearer. It also allows a nice trick in the repl: we can just drop the outmost parenthesis, so instead of any of those examples we can just write + 4 5! Doing this feels so nice to me, and since I haven't encountered it in other lisps before, I consider this to be the most significant idea other lisps could learn from.

+

One thing to note about this style is that as it currently stands, this makes this language a bit lazier than most languages. That is, the arguments to functions are only evaluated when there's enough information to completely evaluate the function. So, say, if one were to use the function (+ <a>), the argument a will only be evaluated when the function is passed another value. This will always be in the language, though I do imagine the introduction of a way to more eagerly evaluate arguments.

+
+
+

Combinators

+

Quite a bit is needed to make a language as tacit as this one, and the method I've selected to do so is through the use and overuse of combinators. One can think of the use of combinators here as a form of rewriting expressions. Let's take as an example the C combinator. (C a b c) takes its three arguments and gives back (a c b). Let's look at how this can work in a more practical example: +

+← C - 1 5
+ = - 5 1
+ → 4
+ so if one were interested in a function that subtracts one from a number, there you have it, in (C - 1).

+

Another often-useful combinator is the B combinator, sometimes called the composition combinator. This one takes (B a b c) to (a (b c)). An example one might come across frequently is: +

+← B car cdr (list 1 2 3 4)
+ = car (cdr (list 1 2 3 4))
+ → 2
+ which is commonly called cadr in many other lisps. A decent amount of other compositions of car and cdr can also easily be defined, but we'll leave that for later.

+

This language has all the combinators from the I S K and B C K W combinator calculi. From the composition of these, many other useful combinators can be made. Two combinators provided are the phi and psi combinators, where Phi a b c d gives you a (b d) (c d), and where Psi a b c d gives you a (b c) (b d), both very useful in general, and phi particularly useful for processing lists. For more, I encourage you to check out this and this link for many nice examples of other combinators and their uses.

+

There is one further built-in combinator, incredibly powerful and used for recursion. The Z combinator. It is quite cumbersome at this point to work with, but it can power most anything you might want to do that requires any sort of iteration. The Z combinator takes Z g v to g (Z g) v, enabling recursion. One major implementation difference is that Z evaluates its second argument rather than just reproducing the symbols in the desired order, for the sake of performance (this may change at some point).

+

Okay, we've described it, now let's see how we might work with it. Please note that this is currently the least pleasant part of the language, and the brunt of the work going forward will likely be how to make actually implementing things comfortable. But let's think about implementing the factorial function (please note that this implementation is not tail-recursive, so it's not the most practical implementation). My process has been starting with an expression with variables, and abstracting them out (getting them to be the last things in the expression). So, the process starts with +

(if (eq 0 n) 1 (* n (fac (- n 1))))
+ and our goal is to extract the n and fib terms, in that order, to get something usable with the Z combinator. There are several different things we could do first, but let's start with something we've already seen before, with getting a function to subtract one from something. We just replace below +
(if (eq 0 n) 1 (* n (fac ((C - 1) n))))
+ Next we can use the B combinator to come to +
(if (eq 0 n) 1 (* n (B fac (C - 1) n)))
+ At this point, it's pretty useful to remember how much freedom we have in using parenthesis to help us refactor things, and group a couple different things up to get +
(if ((eq 0) n) 1 (* n ((B fac (C - 1)) n)))
+ Now, we get to use the S combinator, which takes S a b c to a c b c. If we put it right in front of the multiplication, we get +
(if ((eq 0) n) 1 (S * (B fac (C - 1)) n))
+ which reduces the number of times n appears in the expression. The next several steps will be applications of the same replacements we've already done (including reparenthesizing), but in different places. Try and follow along! +
(if ((eq 0) n) 1 (S * (B fac (C - 1)) n))
+(if ((eq 0) n) 1 ((S * (B fac (C - 1))) n))
+((B if (eq 0) n) 1 ((S * (B fac (C - 1))) n))
+(((B if (eq 0)) n) 1 ((S * (B fac (C - 1))) n))
+ The next tool we have in our bag of tricks is another way of messing with parenthesis, which has been alluded to earlier. Since ((a b) (c d)) functions the same as (a b (c d)), we can remove parenthesis that start expressions (though not ones in the middle of expressions, because calling c on d needs to happen before being used as the argument for (a b)). So, we can rewrite our latest expression: +
(((B if (eq 0)) n) 1 ((S * (B fac (C - 1))) n))
+((B if (eq 0)) n 1 ((S * (B fac (C - 1))) n))
+ This gives us more power for refactoring and reorganizing code in general. We continue on: +
((B if (eq 0)) n 1 ((S * (B fac (C - 1))) n))
+(C (B if (eq 0)) 1 n ((S * (B fac (C - 1))) n))
+((C (B if (eq 0)) 1) n ((S * (B fac (C - 1))) n))
+(S (C (B if (eq 0)) 1) (S * (B fac (C - 1))) n)
+(S (C (B if (eq 0)) 1) (S * (B fac (C - 1)))) n
+
+ At this point, n is the last term in this expression, so we can consider it factored out, and then proceed to do the same thing for fac +
+(S (C (B if (eq 0)) 1) (S * (B fac (C - 1))))
+(S (C (B if (eq 0)) 1) (S * (C B (C - 1) fac)))
+(S (C (B if (eq 0)) 1) ((S *) ((C B (C - 1)) fac)))
+(S (C (B if (eq 0)) 1) (B (S *) (C B (C - 1)) fac))
+((S (C (B if (eq 0)) 1)) ((B (S *) (C B (C - 1))) fac))
+(B (S (C (B if (eq 0)) 1)) (B (S *) (C B (C - 1))) fac)
+(B (S (C (B if (eq 0)) 1)) (B (S *) (C B (C - 1)))) fac
+(B (S (C (B if (eq 0)) 1)) (B (S *) (C B (C - 1))))
+
+ And now we have factored out our recursive call. What do we do with this now? Now we get to use the Z combinator. We've just figured out the g we want to pass it, and since this is a factorial function, the v we need is just whatever iteger we want the factorial of. So, +
+← Z (B (S (C (B if (eq 0)) 1)) (B (S *) (C B (C - 1)))) 5
+ → 120
+ Wonderful! Now, if we wanted to use factorial more often, we'd just assign it a (global) name, like so: +
+← def factorial (Z (B (S (C (B if (eq 0)) 1)) (B (S *) (C B (C - 1)))))
+ → _264
+← factorial 7
+ → 5040
+← factorial 0
+→ 1
+

+

One might think all this to be a bit much to get something as simple as factorial. I'd probably agree. Aside from fixing various pain points and adding practical features, the majority of effort that will go into this language will be toward making writing things like recursive functions much easier.

+
+
+

Church Encodings

+

Everything in this language is a function. That includes numbers, strings, and everything else. Most things, including strings, are pretty unexciting, a string is just a function that returns itself. But, for unsigned integers, t, and nil, we get some exciting behaviours. When t is applied as a function, it takes two arguments and returns the first, and nil takes two and returns the second (these are represented in combinators as K and K I respectively). Numbers are pretty nice here, when treated as a function, n takes two arguments, and applies the first to the second n times. So, the code (4 f v) turns into the code (f (f (f (f v))))

+

Particularly for numbers, this is a nice way to iterate things. A couple simpler examples include multiplication and exponentiation: +

+← 5 (+ 3) 0
+ → 15
+← 6 (C - 4) 100
+ → 76
+← 4 (* 5) 1
+ → 625
+← 4 3 (+ 1) 0
+ → 81
+← 7 2 (+ 1) 0
+ → 128
+ These are certainly not the most efficient ways to calculate these values, but they illustrate how one might use numbers as functions. Another more practical and exciting use of numbers can come in the form of composition of car and cdr, like the cadr mentioned above. We can define cadddr as (B car (3 cdr)) and so on.

+

The use of t and nil as functions isn't quite so broad in its uses, but the combination of them can really make code more compact. Consider any sort of expression that evaluates to a boolean, such as (eq 4 n). If it evaluates to true, it'll function as the t function, so passing it two arguments returns the first. If it evaluates to false, it'll function as nil, so passing it two arguments returns the second. So, the following examples work the same way: +

+if (eq 6 n) "n was six" "n was not six"
+(eq 6 n) "n was six" "n was not six"
+ So 'if' is generally superfluous and not needed. In fact, using the I combinator (which takes I a to a), one can define (and I have, though I don't actually use it that much) 'if' with the following statement: (def if I). Simple as that. All this is the biggest use of Church encodings so far, though I may play around with doing some more useful and interesting things.

+
+
+

rest

+

The last fairly significant thing I want to tell you about here is rest. This operation could probably be considered the most 'improper' thing in this language. This is how we can do variadic functions. rest is passed a function that takes a function that operates on a list. rest takes that function, and applies it to all the rest of the arguments in that statement. So, one could implement a more commonly lisp-style variadic addition: +

+← def +/ (rest (fold +))
+ → _7
+← +/ 1 2 3 4 5
+ → 15
+ which gives us a nice variadic addition.

+
+
+
+ + diff --git a/doc/style.css b/doc/style.css new file mode 100644 index 0000000..07e8b67 --- /dev/null +++ b/doc/style.css @@ -0,0 +1,202 @@ +/* yo imma do this /mobile first/ */ + +body { + background-color: #e0e0f0; +} + +ul { + list-style-type: "- "; +} + +pre { + white-space: pre-wrap; + background-color: #f0f0f0; + padding: 2px; +} + +code { + white-space: nowrap; + background-color: #f0f0f0; + padding: 2px; +} + +p { + font-family: serif; +} + +h1 { + font-size: xx-large; + font-weight: bold; +} + +h2 { + font-size: x-large; + font-weight: bold; + font-variant: small-caps; + border-bottom: 1px solid grey; +} +h3 { + font-variant: small-caps; + /*border-bottom: 1px solid grey;*/ + text-decoration: underline; +} + +nav { + background-color: #f9f9f9; +} + +aside { + /*border: 1px solid red;*/ + border-left: 2px solid #c0c0c0; + color: black; + background-color: #f6f6f6; + border-radius: 3px; + padding-left: 5px; + padding-top: 2px; + padding-bottom: 2px; + font-size: smaller; + margin-left: 2em; + margin-right: 2em; +} + +.italic { + font-style: italic; +} + +.fn { + white-space: nowrap; + position: relative; +} + +.close { + position: absolute; + right: 20px; + font-size: x-large; +} + +.foot { + visibility: hidden; + display: flex; + position: fixed; + margin-bottom: 30px; + margin-bottom: 2px; + min-height: 40px; + white-space: normal; + bottom: 0px; + background-color: #f0f0f0; + border: 2px solid #c0c0c0; + width: 90%; +} + +.foottxt { + width: 90%; + margin: 2px; + font-size: larger; +} + +.fn:hover .foot { + /*color: green;*/ + /*background-color: red;*/ + visibility: visible; +} + +.foot:target { + visibility: visible; +} + +.hover { + white-space: normal; + position: absolute; + visibility: hidden; + /*background-color: orange;*/ + width: 300px; + top: -20px; +} +.hoverbox { + background-color: #f0f0f0; + border: 2px solid #c0c0c0; + border-radius: 3px; + position: absolute; + max-width: 300px; + padding: 3px; +} +.hovertxt { + padding: 2px; +} + + +.codeblock { + display: block; + /*white-space: pre-wrap;*/ + margin-bottom: 1em; +} + +figure { + text-align: center; +} +img { + width: 90%; +} +figcaption { + font-size: small; + font-style: italic; +} + +figure.quote { + background-color: #f0f0f0; + border-radius: 10px; + border-left: solid 4px #d0d0d0; +} +blockquote { + padding-top: 4px; + padding-bot: 2px; +} + +table { + width: 100%; + overflow-x: scroll; + display: block; + border-collapse: collapse; +} + +td { + border: 1px solid #909090; +} +th { + border: 1px solid #a0a0a0; + background-color: #f0f0f0; +} + +@media screen and (min-width: 1000px) { + article { + max-width: 800px; + margin: 0 auto; + } + aside { + margin-left: 6em; + } + .fn:hover .hover { + visibility: visible; + } + .fn:hover .foot { + /*color: green;*/ + visibility: hidden; + } + .foot:target { + visibility: hidden; + } + pre { + width: 80%; + margin: 0 auto; + } + figure { + width: 70%; + margin: 0 auto; + } + blockquote { + width: 70%; + margin: 0 auto; + padding-bottom: 4px; + } + +} diff --git a/src/builtins.c b/src/builtins.c index adc1da6..840eac5 100644 --- a/src/builtins.c +++ b/src/builtins.c @@ -248,6 +248,8 @@ char* lookup_builtin(Sexpr* b) { return META_PARSE_STR; case META_GETARGS: return META_GETARGS_STR; + case META_LOOKUP: + return META_LOOKUP_STR; case META_GETENV: return META_GETENV_STR; case META_SETENV: diff --git a/src/builtins/meta.c b/src/builtins/meta.c index cf8ed88..15ce7d7 100644 --- a/src/builtins/meta.c +++ b/src/builtins/meta.c @@ -90,6 +90,16 @@ Sexpr* m_getargs(Sexpr* b, Sexpr* rest, Sexpr* env) { return cons(from_quote(args), rest); } +Sexpr* m_lookup(Sexpr* b, Sexpr* rest, Sexpr* env) { + if(META_LOOKUP_ARGS != u64_get_num_args(b)) { + return cons(b, rest); + } + Sexpr* args = b->value.b.args; + Sexpr* lookedup = lookup(env, car(args)); + sexpr_free(b); + return cons(from_quote(lookedup), rest); +} + Sexpr* m_getenv(Sexpr* b, Sexpr* rest, Sexpr* env) { // lol what kind of argument would even be appropriate here? // maybe i should just only accept t, to limit misuse (lol) @@ -145,6 +155,8 @@ Sexpr* x_meta_dispatch(Sexpr* b, Sexpr* rest, Sexpr* env) { return m_parse(b, rest, env); case META_GETARGS: return m_getargs(b, rest, env); + case META_LOOKUP: + return m_lookup(b, rest, env); case META_GETENV: return m_getenv(b, rest, env); case META_SETENV: @@ -161,6 +173,7 @@ Sexpr* load_meta_env(Sexpr* env) { load_builtin(META_BTOU_STR, (META_PREFIX << 8) | META_BTOU, env); load_builtin(META_PARSE_STR, (META_PREFIX << 8) | META_PARSE, env); load_builtin(META_GETARGS_STR, (META_PREFIX << 8) | META_GETARGS, env); + load_builtin(META_LOOKUP_STR, (META_PREFIX << 8) | META_LOOKUP, env); load_builtin(META_GETENV_STR, (META_PREFIX << 8) | META_GETENV, env); load_builtin(META_SETENV_STR, (META_PREFIX << 8) | META_SETENV, env); diff --git a/src/builtins/meta.h b/src/builtins/meta.h index 070c318..7539ee3 100644 --- a/src/builtins/meta.h +++ b/src/builtins/meta.h @@ -19,10 +19,13 @@ #define META_GETARGS 0x03 #define META_GETARGS_ARGS 1 #define META_GETARGS_STR "getargs" -#define META_GETENV 0x04 +#define META_LOOKUP 0x04 +#define META_LOOKUP_ARGS 1 +#define META_LOOKUP_STR "lookup" +#define META_GETENV 0x05 #define META_GETENV_ARGS 1 #define META_GETENV_STR "getenv" -#define META_SETENV 0x05 +#define META_SETENV 0x06 #define META_SETENV_ARGS 1 #define META_SETENV_STR "setenv" diff --git a/src/repl.c b/src/repl.c index d1c7940..fab25e0 100644 --- a/src/repl.c +++ b/src/repl.c @@ -48,7 +48,7 @@ int main(int argc, char** argv) { char* input = NULL; while(1) { - input = readline("> "); + input = readline("← "); if(input == NULL) return 0; add_history(input); @@ -67,7 +67,7 @@ int main(int argc, char** argv) { //printf("- -%s\n", sprint_sexpr(in)); Sexpr* out = eval(clone(in), env); char* outstr = sprint_sexpr(out); - PRINTMV(" - ", outstr); + PRINTMV(" → ", outstr); //printf(" - %s\n", outstr); sexpr_free(in); sexpr_free(out); diff --git a/src/test.c b/src/test.c index 817882b..ecbf8fa 100644 --- a/src/test.c +++ b/src/test.c @@ -22,7 +22,7 @@ void assert_eq(Sexpr* env, char* a, char* b) { printf("\033[32mpassed\033[0m\n"); } else { - printf("\033[1m\033[31mfailed\033[0m\n"); + printf("\033[1m\033[31mFAILED\033[0m: %s != %s\n", a, b); } sexpr_free(av); sexpr_free(bv); @@ -33,13 +33,13 @@ void assert_eq(Sexpr* env, char* a, char* b) { } void run_eval_test(char* str) { - printf("<- %s\n", str); + printf("← %s\n", str); Sexpr* env = init_dict(); env = load_env(env); Sexpr* parsed = parse(str); Sexpr* val = eval(parsed, env); char* out = sprint_sexpr(val); - printf(" -> %s\n", out); + printf(" → %s\n", out); free(out); //sexpr_free(parsed); sexpr_free(val); @@ -458,7 +458,7 @@ void run_tests(){ //isolating_problem(); //test_string_parsing(); eval_tests(); - //many_asserts(); + many_asserts(); //memtest_eval(); //mem_testing(); //mem_parser(); diff --git a/web/shell.html b/web/shell.html index 3226da4..c2d5282 100644 --- a/web/shell.html +++ b/web/shell.html @@ -24,13 +24,15 @@ -- 2.39.2