Skip to content
This repository has been archived by the owner on Jul 7, 2024. It is now read-only.

Commit

Permalink
Merge branch 'feature/documentation' into 'master'
Browse files Browse the repository at this point in the history
Documentation

See merge request !7
  • Loading branch information
hydraz committed Feb 23, 2017
2 parents 0d5a3ff + 47fed31 commit dda47cd
Show file tree
Hide file tree
Showing 20 changed files with 1,694 additions and 1,308 deletions.
52 changes: 42 additions & 10 deletions lib/base.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,46 @@
(true
`(,@header (lambda ,args ,@body))))))

(define-macro defun (lambda (name args &body)
(define-macro defun
"Define NAME to be the function given by (lambda ARGS @BODY), with
optional metadata at the start of BODY."
(lambda (name args &body)
(copy-meta `(define ,name) args body)))

(define-macro defmacro (lambda (name args &body)
(copy-meta `(define-macro ,name) args body)))
(define-macro defmacro
"Define NAME to be the macro given by (lambda ARGS @BODY), with
optional metadata at the start of BODY."
(lambda (name args &body)
(copy-meta `(define-macro ,name) args body)))

(define car (lambda (xs) (get-idx xs 1)))
(define cdr (lambda (xs) (slice xs 2)))
(defun list (&xs) xs)
(defun cons (x xs) (list x (unpack xs)))
(defun list (&xs)
"Return the list of variadic arguments given."
xs)
(defun cons (x xs)
"Add X to the start of the list XS. Note: this is linear in time."
(list x (unpack xs)))

(defmacro progn (&body)
"Group a series of expressions together."
`((lambda () ,@body)))

(defmacro if (c t b) `(cond (,c ,t) (true ,b)))
(defmacro when (c &body) `(cond (,c ,@body) (true)))
(defmacro unless (c &body) `(cond (,c) (true ,@body)))
(defmacro if (c t b)
"Evaluate T if C is true, otherwise, evaluate B."
`(cond (,c ,t) (true ,b)))

(defun debug (x) (print (pretty x)) x)
(defmacro when (c &body)
"Evaluate BODY when C is true, otherwise, evaluate `nil`."
`(cond (,c ,@body) (true)))

(defmacro unless (c &body)
"Evaluate BODY if C is false, otherwise, evaluate `nil`."
`(cond (,c) (true ,@body)))

(defun debug (x)
"Print the value X, then return it unmodified."
(print (pretty x)) x)

(defmacro let* (vars &body)
(if (= (# vars) 0)
Expand All @@ -53,9 +74,14 @@
(let* ,(cdr vars) ,@body))
,(car (cdr (car vars))))))

(defun ! (expr) (if expr false true))
(defun ! (expr)
"Negate the expresison EXPR."
(if expr false true))

(defmacro for (ctr start end step &body)
"Iterate BODY, with the counter CTR bound to START, being incremented
by STEP every iteration until CTR is outside of the range given by
[START .. END]"
(let* [(impl (gensym))
(ctr' (gensym))
(end' (gensym))
Expand All @@ -72,6 +98,7 @@
(,impl ,start))))

(defmacro while (check &body)
"Iterate BODY while the expression CHECK evaluates to `true`."
(let* [(impl (gensym))]
`(let* [(,impl nil)]
(set! ,impl
Expand All @@ -82,12 +109,17 @@
(,impl))))

(defmacro with (var &body)
"Bind the single variable VAR, then evaluate BODY."
`((lambda (,(get-idx var 1)) ,@body) ,(get-idx var 2)))

(defmacro and (a b &rest)
"Return the logical and of values A and B, and, if present, the
logical and of all the values in REST."
(with (symb (gensym))
`(with (,symb ,a) (if ,symb ,(if (= (# rest) 0) b `(and ,b ,@rest)) ,symb))))

(defmacro or (a b &rest)
"Return the logical or of values A and B, and, if present, the
logical or of all the values in REST."
(with (symb (gensym))
`(with (,symb ,a) (if ,symb ,symb ,(if (= (# rest) 0) b `(or ,b ,@rest))))))
60 changes: 60 additions & 0 deletions lib/binders.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,66 @@

;; Bind multiple variables in succession
(defmacro let* (vars &body)
"Bind several variables (given in VARS), then evaluate BODY.
Variables bound with `let*' can refer to variables bound previously,
as they are evaluated in order.
Example:
```
(let* [(foo 1)
(bar (+ foo 1))]
foo
```"
(if (! (nil? vars))
`((lambda (,(caar vars)) (let* ,(cdr vars) ,@body)) ,(cadar vars))
`((lambda () ,@body))))

;; Binds a variable to an expression
(defmacro let (vars &body)
"Bind several variables (given in VARS), then evaluate BODY.
In contrast to `let*', variables bound with [[let]] can not refer to
eachother.
Example:
```
(let [(foo 1)
(bar 2)]
(+ foo bar))
```"
`((lambda ,(cars vars) ,@body) ,@(cadrs vars)))

(defmacro when-let (vars &body)
"Bind VARS, as with [[let]], and check they are all truthy before evaluating
BODY.
```
(when-let [(foo 1)
(bar nil)]
foo)
```
Does not evaluate `foo`, while
```
(when-let [(foo 1)
(bar 2)]
(+ foo bar))
```
does."
`((lambda ,(cars vars)
(when (and ,@(cars vars)) ,@body)) ,@(cadrs vars)))

(defmacro when-let* (vars &body)
"Bind each pair of `(name value)` of VARS, checking if the value is truthy
before binding the next, and finally evaluating BODY. As with [[let*]],
bindings inside 'when-let*' can refer to previously bound names.
Example:
```
(when-let* [(foo 1)
(bar nil)
(baz 2)
(+ foo baz))
```
Since `1` is truthy, it is evaluated and bound to `foo`, however, since
`nil` is falsey, evaluation does not continue."
(cond
[(nil? vars) `((lambda () ,@body))]
[true `((lambda (,(caar vars))
Expand All @@ -27,6 +74,19 @@

;; Pre-declare variable and define it, allowing recursive functions to exist
(defmacro letrec (vars &body)
"Bind several variables (given in VARS), which may be recursive.
Example:
```
> (letrec [(is-even? (lambda (n)
(or (= 0 n)
(is-odd? (pred n)))))
(is-odd? (lambda (n)
(and (! (= 0 n))
(is-even? (pred n)))))]
(is-odd? 11))
true
```"
`((lambda ,(cars vars)
,@(map (lambda (var) `(set! ,(car var) ,(cadr var))) vars)
,@body)))
38 changes: 37 additions & 1 deletion lib/extra/assert.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,34 @@
(import list (elem? traverse))

(defun format-value (value)
"Format the result of VALUE: extracting `:contents` or `:value` if required"
:hidden
(if (and (table? value) (get-idx value "contents"))
(get-idx value "contents")
(pretty value)))

(defmacro assert! (cnd msg) `(unless ,cnd (error! ,msg 0)))
(defmacro assert! (cnd msg)
"Assert CND is true, otherwise failing with MSG"
`(unless ,cnd (error! ,msg 0)))

(defmacro assert (&assertions)
"Assert each assertion in ASSERTIONS is true
Each assertion can take several forms:
- `(= a b)`: Assert that A and B are equal, printing their values if not
- `(/= a b)`: Assert that A and B are not equal, printing their values if they are
- Type assertions of the form `(list? a)`: Assert that A is of the required type.
Example:
```
> (assert (= (+ 2 3) 4))
[ERROR] expected (+ 2 3) and 4 to be equal
> (assert (/= (+ 2 3) 5))
[ERROR] expected (+ 2 3) and 5 to be unequal
> (assert (string? 2))
[ERROR] expected 2 to be a string
```"
(let* [(handle-eq
(lambda (tree)
`(assert!
Expand Down Expand Up @@ -55,6 +76,21 @@
[true (print! (pretty assertion))])))))))

(defmacro affirm (&asserts)
"Assert each expression in ASSERTS evaluates to true
Each expression is expected to be a function call. Each argument is evaluated and the
final function executed. If it returns a falsey value (nil or false) then each argument
will be have it's value printed out.
Example:
```
> (affirm (= (+ 2 3) (* 2 3)))
[ERROR] Assertion failed
(= (+ 2 3) (* 2 3))
| |
| 6
5
```"
`(progn
,@(traverse asserts
(lambda (expr)
Expand Down
49 changes: 49 additions & 0 deletions lib/extra/do.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,55 @@
(import list (prune nth cddr))

(defmacro do (&statements)
"Comprehend over (potentially several) lists according to the rules given by
STATEMENTS.
Unfolding rules to convert STATEMENTS into code are as follows:
- If `(car STATEMENTS)` is the symbol `'for`, then
- If the third symbol in STATEMENTS is `from`, then iterate with the
variable given in the second symbol the range of values from the fourth
symbol to the sixth symbol. that is:
```
(do for x from 1 to 10)
```
Will iterate `x` from `1` to `10`.
- If the third symbol in STATEMENTS is `in`, then iterate with the
variable given in the second symbol over the list given in the fourth
symbol, that is:
```
(do for x in '(1 2 3 4 5))
```
will iterate `x` to be `1`, then `2`, all the way to `5`.
- If the third symbol in STATEMENTS is `over`, then iterate with the
variable given in the second symbol over the string given in the fourth
symbol, that is:
```
(do for x over \"hello\")
```
will iterate `x` to be the elements of the string `\"hello\"`
- If `(car STATEMENTS)` is the symbol `'when`, then only evaluate the rest
of `STATEMENTS` when the condition given in `(cadr STATEMENTS)` evaluates
to true.
- If `(car STATEMENTS)` is the symbol `'yield`, then add the value of
evaluating `(cadr STATEMENTS)` to the output list.
- If `(car STATEMENTS)` is the symbol `'do`, then evaluate `(cadr STATEMENTS)`
without adding the result to the output list.
Examples:
- The cartesian product of two lists:
```
(do for x in xs
for y in ys
yield (pair x y))
```
- Pythagorean triples less than a number N:
(lambda (n)
(do for c from 1 to n
for b from 1 to c
for a from 1 to b
when (eq? (^ c 2) (+ (^ a 2) (^ b 2)))
yield (list a b c))) "
(letrec [(name (gensym))
(unfold
(lambda (stmt)
Expand Down
31 changes: 22 additions & 9 deletions lib/extra/test.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
(import lua/debug (traceback))
(import extra/assert (assert!) :export)

(define tests-passed (gensym))
(define tests-failed (gensym))
(define tests-pending (gensym))
(define tests-total (gensym))
(define prefix (gensym))
(define quiet (gensym))

(defmacro marker (col)
(define tests-passed :hidden (gensym))
(define tests-failed :hidden (gensym))
(define tests-pending :hidden (gensym))
(define tests-total :hidden (gensym))
(define prefix :hidden (gensym))
(define quiet :hidden (gensym))

(defmacro marker (color)
"Add a dot with the given COLOR to mark a single test's result"
:hidden
`(when ,quiet
(write (.. "\27[1;" ,col "m\226\128\162" "\27[0m"))))
(write (.. "\27[1;" ,color "m\226\128\162" "\27[0m"))))

(defmacro it (name &body)
"Create a test NAME which executes all expressions and assertions in BODY"
`(progn
(inc! ,tests-total)
(xpcall
Expand All @@ -27,26 +30,36 @@
(push-cdr! ,tests-failed (list (.. ,prefix " " ,name) (if ,quiet ,'msg (traceback ,'msg))))))))

(defmacro pending (name &body)
"Create a test NAME whose BODY will not be run.
This is primarily designed for assertions you know will fail and need to be fixed,
or features which have not been implemented yet"
`(progn
(marker 33)
(push-cdr! ,tests-pending (.. ,prefix " " ,name))))

(defmacro may (name &body)
"Create a group of tests defined in BODY whose names take the form `<prefix> may NAME, and <test_name>`"
`(with (,prefix (.. ,prefix " may " ,name ", and")) ,@body))

(defmacro will (name &body)
"Create a test whose BODY asserts NAME will happen"
`(with (,prefix (.. ,prefix " will")) (it ,name ,@body)))

(defmacro will-not (name &body)
"Create a test whose BODY asserts NAME will not happen"
`(with (,prefix (.. ,prefix " won't")) (it ,name ,@body)))

(defmacro is (name &body)
"Create a test whose BODY asserts NAME is true"
`(with (,prefix (.. ,prefix " is")) (it ,name ,@body)))

(defmacro can (name &body)
"Create a test whose BODY asserts NAME can happen"
`(with (,prefix (.. ,prefix " can")) (it ,name ,@body)))

(defmacro describe (name &body)
"Create a group of tests, defined in BODY, which test NAME"
`(let ((,tests-passed '())
(,tests-failed '())
(,tests-pending '())
Expand Down
Loading

0 comments on commit dda47cd

Please sign in to comment.