Can now parse lambda lists

This commit is contained in:
FelixBrendel
2018-10-15 00:46:09 +02:00
parent 2006e3b45c
commit f6c2219bd5
15 changed files with 1013 additions and 48 deletions
+197 -17
View File
@@ -1,3 +1,196 @@
* 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
* TODO =assert_equal_type= macro in testing
* DONE use an enum for builtin identifiers
CLOSED: [2018-10-11 Do 17:15]
@@ -25,9 +218,9 @@
CLOSED: [2018-10-05 Fr 22:21]
** DONE not
CLOSED: [2018-10-05 Fr 22:21]
#+begin_src emacs-lisp
#+begin_src
(not 1) ;; == (not . (1 . nil))
(not (expression 1 3)) ;; == (not . ((expression . (1 . (3 . nil))) . nil))
(not (expression 1 3)) ;; == (not . ((expression . (1 . (3 . nil))) . nil) . nil)
(not) ;; == (not . nil)
(not 1 2) ;; == (not . (1 . (2 . nil)))
#+end_src
@@ -62,14 +255,14 @@
** DONE list
CLOSED: [2018-10-08 Mo 21:06]
#+begin_src emacs-lisp
#+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 emacs-lisp
#+begin_src
(list (cons 2 (cons 3 (cons 4 ()))))
;; ((2 3 4))
(list (quote(2 . (3 . (4 . ())))))
@@ -91,16 +284,3 @@
- pair (cons-cell)
- lambda function lambda
- built-in function
* Arbeitsweise
1) Parse to AST
2) Start with namespace only containing the built-ins
3) every AST node has its own namespace,
inherting the one from its parent (pointer /
references) because when a function sets a
variable within an upper namespace it should
infact change the environment even if the
function exited
(progn (setq x 4)(* x 2))