cleanup and writing docs
This commit is contained in:
@@ -1,438 +0,0 @@
|
||||
* Issues
|
||||
** DONE #001
|
||||
CLOSED: [2018-10-26 Fr 22:36]
|
||||
|
||||
#+begin_src lisp
|
||||
(define seq (list 1 2 3))
|
||||
(when 1 (printf seq))
|
||||
#+end_src
|
||||
|
||||
The problem is, that =when= calls =apply= and the params of =apply= are named =fun= and =seq=. So
|
||||
when we refer to seq in =printf= then we are refering to the =apply='s argument names. Maybe a good
|
||||
solution to that is, that a child environment can never look up something in a parent macro
|
||||
environment, but will always pass through
|
||||
|
||||
| stack depth | is macro environment | defined symbols | scope |
|
||||
|-------------+----------------------+--------------------------+---------------------|
|
||||
| 0 | no | seq nil printf apply etc | |
|
||||
| 1 | yes | test body | (when 1 printf seq) |
|
||||
| 2 | yes | fun seq | (apply prog body) |
|
||||
| 3 | no | | (printf seq) |
|
||||
|
||||
** DONE #002
|
||||
CLOSED: [2018-10-27 Sa 13:50]
|
||||
#+begin_src lisp
|
||||
(define l (list 1 2 3))
|
||||
(extend l 4)
|
||||
|
||||
;; === Environment ===
|
||||
;; current: in (apply)
|
||||
;; fun -> prog
|
||||
;; seq -> body
|
||||
;; parent (hidden): in (when)
|
||||
;; test -> (pair? seq)
|
||||
;; body -> ((define e (end seq)) (mutate e (pair (first e) elem)))
|
||||
;; parent: in (extend)
|
||||
;; seq -> (1.000000 2.000000 3.000000)
|
||||
;; elem -> 4.000000
|
||||
;; parent: (top level)
|
||||
;; nil -> nil
|
||||
;; defun -> [macro]
|
||||
;; defmacro -> [macro]
|
||||
;; pe -> [macro]
|
||||
;; nil? -> [lambda]
|
||||
;; number? -> [lambda]
|
||||
;; symbol? -> [lambda]
|
||||
;; keyword? -> [lambda]
|
||||
;; pair? -> [lambda]
|
||||
;; string? -> [lambda]
|
||||
;; dynamic-function? -> [lambda]
|
||||
;; dynamic-macro? -> [lambda]
|
||||
;; built-in-function? -> [lambda]
|
||||
;; apply -> [macro]
|
||||
;; when -> [macro]
|
||||
;; unless -> [macro]
|
||||
;; end -> [lambda]
|
||||
;; last -> [lambda]
|
||||
;; extend -> [lambda]
|
||||
;; append -> [lambda]
|
||||
;; n-times -> [macro]
|
||||
;; range -> [lambda]
|
||||
;; map -> [lambda]
|
||||
;; reduce -> [lambda]
|
||||
;; reduce-binary -> [lambda]
|
||||
;; filter -> [lambda]
|
||||
;; printf-quoted -> [macro]
|
||||
;; printf -> [lambda]
|
||||
;; l -> (1.000000 2.000000 3.000000)
|
||||
|
||||
;; M (apply fun -> prog seq -> body)
|
||||
;; M (when test -> (pair? seq) body -> ((define e (end seq)) (mutate e (pair (first e) elem))))
|
||||
;; (extend seq -> (1.000000 2.000000 3.000000) elem -> 4.000000)
|
||||
#+end_src
|
||||
|
||||
The problem is that a funciton g called by a function f has access to the environment of f. This is
|
||||
a real problem.
|
||||
|
||||
#+begin_src lisp
|
||||
(defun f () 1)
|
||||
(defun g () (f))
|
||||
(defun h (f) (g))
|
||||
(h 4)
|
||||
#+end_src
|
||||
|
||||
What *should* happen:
|
||||
- (h 1) calls (g)
|
||||
- (g) calls (f)
|
||||
- (f) retunrs 1
|
||||
|
||||
What *actually* happens:
|
||||
- (h 1) calls (g)
|
||||
- (g) tries to call (f) but f is not a funciton but =4=
|
||||
|
||||
** DONE #003
|
||||
CLOSED: [2018-10-27 Sa 15:22]
|
||||
#+begin_src lisp
|
||||
(defmacro n-times (times action)
|
||||
(unless (<= (eval times) 0)
|
||||
(breakpoint)
|
||||
(eval action)
|
||||
(macro-define args (pair (pair - (pair (eval times) (pair 1 nil))) (pair action nil)))
|
||||
;; [o|o] --------------------------> [o|o] -> nil
|
||||
;; | |
|
||||
;; V V
|
||||
;; [o|o] -> [o|o] -> [o|o]-> nil action
|
||||
;; | | |
|
||||
;; V V V
|
||||
;; - times 1
|
||||
(eval (pair n-times args))))
|
||||
#+end_src
|
||||
|
||||
- *let* should never have access to parenting macro because =(while 2 test) -> 2.00000=
|
||||
- Should it have access to the grandad macro?
|
||||
- no, =(when 2 (when 3 test)) -> 2=
|
||||
|
||||
- in the example, =n-times= is a macro, that calls =unless=, that itself is a macro that calls the
|
||||
content of =n-times= in a let. So of course the contents of =n-times= wont have acess to the
|
||||
parameters to =n-times=, which is bad.
|
||||
|
||||
So right now the only way I see to fix this is to make maco environments accessable again, but pay
|
||||
attention on how to name macro parameters, to avoid collision, maybe a naming convention would help,
|
||||
maybe prefix with =@=
|
||||
*** Solutions:
|
||||
|
||||
First instinct to prevent that, is that if a parent environment is a function then just pass it and
|
||||
go for their parent etc. Then we need =let= of a way to create environments that are neither macro-
|
||||
nor funciton environmetns. They will be still different though.
|
||||
|
||||
*** Defining
|
||||
In a macro environment define will still behave differently than in a function environment. If you
|
||||
use =define= in a macro environment, it will actually be defined in the closest non-macro
|
||||
environment. If you actually want to define something in a macro environment, then use
|
||||
=macro-define=. However =define= will do the same if you are in a function or let environment.
|
||||
|
||||
*** Looking up
|
||||
If the symbol is found on the active environment then use it. If not: Go up the environments untily
|
||||
ou find a let-environment and recurse on that.
|
||||
* Arguments
|
||||
|
||||
In Emacs lisp, keyword arguments must be passed in the same order as they were
|
||||
defined in, so =((lambda (a &key b c) (+ a b c)) 1 3 :b 2)= will preduce an
|
||||
error, because =b= was passed as last argument but was defined as second argument.
|
||||
#+begin_src emacs-lisp
|
||||
((lambda (a b) (+ a b)) 1 2) ;; 3
|
||||
((lambda (a &key b) (+ a b)) 1 :b 2) ;; 3
|
||||
((lambda (a &key b) (+ a b)) :b 2 1) ;; error
|
||||
#+end_src
|
||||
|
||||
|
||||
But if mulitiple keyword arguments are defined, their ordering does not matter.
|
||||
#+begin_src emacs-lisp
|
||||
((lambda (a &key b &key c &key d) (+ a b c d)) 1 :d 4 :c 3 :b 2) ;; 10
|
||||
#+end_src
|
||||
|
||||
It is even possible to have a non keyword argument bewtween keyword arguments
|
||||
and when calling, switch the order.
|
||||
#+begin_src emacs-lisp
|
||||
((lambda (a &key b c &key d) (+ a b c d)) 1 :d 4 3 :b 2) ;; 10
|
||||
((lambda (a &key b c &key d) (+ a b c d)) 1 :d 4 :b 2 3) ;; error
|
||||
#+end_src
|
||||
|
||||
However somehow it is not possible to flip the ordering at the first argument
|
||||
#+begin_src emacs-lisp
|
||||
((lambda (a &key b) (+ a b)) 2 :b 1) ;; 3
|
||||
((lambda (a &key b) (+ a b)) :b 2 1) ;; error
|
||||
((lambda (&key a b) (+ a b)) :a 2 1) ;; 3
|
||||
((lambda (&key a b) (+ a b)) 1 :a 2) ;; error
|
||||
#+end_src
|
||||
|
||||
So it seems, positional arguments have to be at the exact position they were
|
||||
defined in, but keyword arguments can appear in any order, but at the places
|
||||
where keyword arguments have been defined.
|
||||
|
||||
** Idea
|
||||
|
||||
Every argument can be set using keys, but keyword arguments always come strictly
|
||||
after positional arguments
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
((lambda (a b c d)) 1 2 3 4) ;; ok
|
||||
((lambda (a b c d)) 1 2 :c 2 :d 1) ;; ok
|
||||
((lambda (a b c d)) 1 2 :d 1 :c 2) ;; ok
|
||||
((lambda (a b c d)) :d 1 1 2 :c 2) ;; error, keyword arguments must come last
|
||||
((lambda (a b c d)) 1 2 :c 2 :d 1 :a 2) ;; error, 'a' was passed two times
|
||||
|
||||
((lambda (a b c d)) 1 2 :d 1) ;; error, c was not passed, but does not have a default value
|
||||
((lambda (a b c :default 10 d)) 1 2 :d 1) ;; okay again
|
||||
#+end_src
|
||||
|
||||
The drawback then is that you can never pass a keyword as a positional argument,
|
||||
because it will always try to assign the next argument to the variable defined
|
||||
by the keyword. You could still pass it as a keyword argument though. This is
|
||||
especially annoying because we want to use keywords as type identifiers.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(= (type "hello") :string)
|
||||
#+end_src
|
||||
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
((lambda (i-wanna-get-a-kw)) :hey) ;; error, unmatched value for keyword argument
|
||||
((lambda (i-wanna-get-a-kw)) :i-wanna-get-a-kw :hey) ;; this would work again
|
||||
#+end_src
|
||||
|
||||
Other Idea is to mark the place in the parameter list where keyword argumetns
|
||||
start and arguments before that are positional and will not be stuffed into
|
||||
key-value pairs and arguments after that will be keyword arguments
|
||||
|
||||
#+begin_src ebnf
|
||||
<lambda list> -> <positional argument>*
|
||||
[:keys ( <keyword arguments> [:defaults-to <value>])*]
|
||||
[(:rest <rest argument>)|(:body <body argument>)]
|
||||
#+end_src
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(= (type "hello") :string) ;; this will work again because (=) does
|
||||
;; not use keyword arguments
|
||||
((lambda (i-wanna-get-a-kw)) :hey) ;; would work because of the same reason
|
||||
|
||||
(define fun
|
||||
(lambda (:keys a b c d)
|
||||
(+ a b c d)))
|
||||
|
||||
(fun 1 2 3 4) ;; okay
|
||||
(fun 1 2 :d 3 :c 4) ;; okay
|
||||
(fun 1 2 :d 3 :c 4 :a 2) ;; error, 'a' was passed two times
|
||||
(fun :c 1 :b 2 :d 3 :a 4) ;; okay
|
||||
(fun :c 1 :b 2 :d 3 4) ;; error, positional argument after keyword
|
||||
;; and fun does not accept rest parameter
|
||||
|
||||
(define fun2
|
||||
(lambda (:quoted op a b :keys c d :rest r)
|
||||
(print r)
|
||||
(op a b c d)))
|
||||
|
||||
>> (fun2 * 1 2 :c 3 :d 4 "this" "can" :be "whatever")
|
||||
("this" "can" :be "whatever")
|
||||
24
|
||||
|
||||
|
||||
>> (define print-before-eval (lambda (:body expr)
|
||||
(print expr)
|
||||
(eval expr)))
|
||||
>> (define r (print-before-eval (+ 1 2 3)))
|
||||
(+ 1 2 3)
|
||||
>> r
|
||||
6
|
||||
|
||||
#+end_src
|
||||
|
||||
Wait this does not work either because you won't be able to pass keywords into
|
||||
rest.
|
||||
|
||||
*Unrealated Idea: Distinguish between functions and macros*
|
||||
|
||||
The difference is really simple:
|
||||
- Function :: like a lambda, but no argument is automatically quoted
|
||||
- Macro :: like a lambda, but every argument is automatically quoted
|
||||
|
||||
This is important, because a macro should not introduce a new environment, but
|
||||
use the one it lives in.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
|
||||
>> (define print-before-eval (macro (:rest expr)
|
||||
(print expr)
|
||||
(eval expr))
|
||||
|
||||
#+end_src
|
||||
|
||||
** the actual problem
|
||||
|
||||
We have some requirements to the arguments:
|
||||
- positional arguments (always required, no default value)
|
||||
- keyword arguments (always come after positionals), can be not passed if there
|
||||
is a default value
|
||||
- We want to be able to have a rest paramter, where everything is dumped in to
|
||||
that exceeds the other parameters
|
||||
|
||||
*The problem is, we have no way of knowing if the current keyword we are
|
||||
reading is still part of the keyword block or part of the rest paramter.*
|
||||
|
||||
Consider:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
>> (define fun (lambda (a b :key c d :defaults-to 4 :rest) nil))
|
||||
>> (fun 1 2 :c 3 :d 5)
|
||||
|
||||
|
||||
(defun haha (par1 par2)
|
||||
(+ 1 2))
|
||||
#+end_src
|
||||
What should this be evaluated to?
|
||||
|
||||
| | a | b | c | d | rest |
|
||||
|----+---+---+---+---+--------|
|
||||
| v1 | 1 | 2 | 3 | 5 | nil |
|
||||
| v2 | 1 | 2 | 3 | 4 | (:d 5) |
|
||||
|
||||
It is ambiguous. It is however important to note that this can only happen when
|
||||
there are optinal keyword arguments (keyword arguments with default values).
|
||||
Otherwise we could always know which variables are set and wich have not been
|
||||
set to see if we are in an errornious state.
|
||||
|
||||
** Solution
|
||||
We can resolve that problem by always making the the decision of chosing =v1=
|
||||
between =v1= and =v1=. This means, when we are expecting to read kwargs, and we
|
||||
encounter an kwarg, try to apply it (this can fail, for two reasons:
|
||||
|
||||
1. The keyword has no following value
|
||||
2. The keyword is not a keyword argument in the funciton
|
||||
|
||||
), if it fails treat it as part of the rest arguments, and read all the
|
||||
following arguments into the rest arguments, or if it works, use it as the
|
||||
keyword argument.
|
||||
|
||||
** Implementation
|
||||
|
||||
#+begin_src c
|
||||
typedef struct {
|
||||
char* docstring;
|
||||
char** positional_arguments;
|
||||
struct {
|
||||
char* identifier;
|
||||
Ast_node* default_value; // will be nullptr if no defalut value was declared
|
||||
}* keyword_arguments;
|
||||
char* rest_argument; // will be nullptr if no rest argument is declared
|
||||
Ast_Node_array_list body;
|
||||
} lambda;
|
||||
#+end_src
|
||||
* DONE use an enum for builtin identifiers
|
||||
CLOSED: [2018-10-11 Do 17:15]
|
||||
|
||||
* DONE Print escaped chracters correctly
|
||||
CLOSED: [2018-10-27 Sa 18:16]
|
||||
* TODO [#A] =t= ast node type, universal source of truth
|
||||
* TODO [#A] source code locations for errors
|
||||
* TODO [#A] String error messages
|
||||
* TODO [#A] Rename macro to =special= or something
|
||||
* TODO [#B] =assert_equal_type= macro in testing
|
||||
* TODO [#B] dont create new nils or builtins, but store one of each globally
|
||||
* TODO [#B] make keywords unique (binary tree)
|
||||
* TODO [#B] store all ast nodes in a huge arena
|
||||
* TODO [#B] Auto doc generation
|
||||
* TODO [#C] backquoting
|
||||
* Build-in forms [29/30]
|
||||
** TODO info
|
||||
|
||||
** DONE let
|
||||
CLOSED: [2018-10-27 Sa 15:30]
|
||||
** DONE +
|
||||
CLOSED: [2018-09-18 Di 12:14]
|
||||
** DONE -
|
||||
CLOSED: [2018-09-18 Di 12:14]
|
||||
** DONE *
|
||||
CLOSED: [2018-09-18 Di 12:14]
|
||||
** DONE /
|
||||
CLOSED: [2018-09-18 Di 12:14]
|
||||
** DONE >
|
||||
CLOSED: [2018-10-26 Fr 23:30]
|
||||
** DONE >=
|
||||
CLOSED: [2018-10-26 Fr 23:30]
|
||||
** DONE <
|
||||
CLOSED: [2018-10-26 Fr 23:30]
|
||||
** DONE >=
|
||||
CLOSED: [2018-10-26 Fr 23:30]
|
||||
** DONE =
|
||||
CLOSED: [2018-10-21 So 00:25]
|
||||
** DONE if
|
||||
CLOSED: [2018-09-18 Di 12:14]
|
||||
** DONE and (short circuiting)
|
||||
CLOSED: [2018-10-05 Fr 22:21]
|
||||
** DONE or
|
||||
CLOSED: [2018-10-05 Fr 22:21]
|
||||
** DONE not
|
||||
CLOSED: [2018-10-05 Fr 22:21]
|
||||
#+begin_src
|
||||
(not 1) ;; == (not . (1 . nil))
|
||||
(not (expression 1 3)) ;; == (not . ((expression . (1 . (3 . nil))) . nil) . nil)
|
||||
(not) ;; == (not . nil)
|
||||
(not 1 2) ;; == (not . (1 . (2 . nil)))
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
: t
|
||||
|
||||
** DONE first (car)
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
** DONE rest (cdr)
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
** DONE pair (cons)
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
|
||||
** DONE load (import)
|
||||
CLOSED: [2018-10-21 So 00:25]
|
||||
** DONE define
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
** DONE mutate
|
||||
CLOSED: [2018-10-25 Do 19:40]
|
||||
** DONE lambda
|
||||
CLOSED: [2018-10-21 So 00:25]
|
||||
** DONE macro
|
||||
CLOSED: [2018-10-25 Do 19:40]
|
||||
** DONE prog
|
||||
CLOSED: [2018-10-21 So 00:25]
|
||||
** DONE eval
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
|
||||
(setq condition (quote (= a 10)))
|
||||
(setq a 10)
|
||||
(eval condition)
|
||||
(setq a 11)
|
||||
(eval condition)
|
||||
|
||||
** DONE quote
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
|
||||
** DONE list
|
||||
CLOSED: [2018-10-08 Mo 21:06]
|
||||
|
||||
#+begin_src
|
||||
(quote (cons 2 (cons 3 (cons 4 ()))))
|
||||
;; (cons 2 (cons 3 (cons 4 nil)))
|
||||
(quote (2 . (3 . (4 . ()))))
|
||||
;; (2 3 4)
|
||||
#+end_src
|
||||
|
||||
#+begin_src
|
||||
(list (cons 2 (cons 3 (cons 4 ()))))
|
||||
;; ((2 3 4))
|
||||
(list (quote(2 . (3 . (4 . ())))))
|
||||
#+end_src
|
||||
|
||||
** DONE print
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
** DONE read
|
||||
CLOSED: [2018-10-08 Mo 20:28]
|
||||
** DONE type
|
||||
CLOSED: [2018-10-08 Mo 21:30]
|
||||
Reference in New Issue
Block a user