From f6c2219bd5fb5c50e273951243ecb92eb6098a7d Mon Sep 17 00:00:00 2001 From: FelixBrendel Date: Mon, 15 Oct 2018 00:46:09 +0200 Subject: [PATCH] Can now parse lambda lists --- bin/{test.lsp => test.slime} | 0 build.bat | 2 +- debug.bat | 2 +- run.bat | 2 +- src/ast.c | 81 ++++- src/error.c | 2 + src/eval.c | 196 ++++++++++- src/init.c | 2 +- src/io.c | 1 + src/parse.c | 13 +- test.bat | 2 +- todo.html | 539 +++++++++++++++++++++++++++++ todo.org | 214 +++++++++++- vs/{lisp.sln => slime.sln} | 2 +- vs/{lisp.vcxproj => slime.vcxproj} | 3 +- 15 files changed, 1013 insertions(+), 48 deletions(-) rename bin/{test.lsp => test.slime} (100%) create mode 100644 todo.html rename vs/{lisp.sln => slime.sln} (93%) rename vs/{lisp.vcxproj => slime.vcxproj} (98%) diff --git a/bin/test.lsp b/bin/test.slime similarity index 100% rename from bin/test.lsp rename to bin/test.slime diff --git a/build.bat b/build.bat index 7915cb5..9099ce9 100644 --- a/build.bat +++ b/build.bat @@ -2,7 +2,7 @@ @setlocal pushd %~dp0 -set exeName=lisp.exe +set exeName=slime.exe set binDir=bin mkdir quickbuild 2>nul diff --git a/debug.bat b/debug.bat index b95544b..8bba81a 100644 --- a/debug.bat +++ b/debug.bat @@ -1,4 +1,4 @@ @echo off pushd %~dp0\vs -start lisp.sln +start slime.sln popd diff --git a/run.bat b/run.bat index 7abd6bf..198cd5f 100644 --- a/run.bat +++ b/run.bat @@ -4,6 +4,6 @@ pushd %~dp0\bin call ..\build.bat if %errorlevel% == 0 ( echo ---------- Running ---------- - call timecmd lisp.exe + call timecmd slime.exe ) popd diff --git a/src/ast.c b/src/ast.c index 2215d7b..aff1bb4 100644 --- a/src/ast.c +++ b/src/ast.c @@ -36,14 +36,77 @@ typedef struct { } Pair; typedef struct { - Pair* regular_params; - Pair* keyword_params; -} Lambda_List; + struct Ast_Node** data; + int length; + int next_index; +} Ast_Node_Array_List; + + +typedef struct { + char** identifiers; + int next_index; + int length; +} Positional_Arguments; typedef struct { - Lambda_List* parameters; - Pair* form; - char* docstring; + char** identifiers; + // values[i] will be nullptr if no defalut value was declared for + // key identifiers[i] + Ast_Node_Array_List* values; + int next_index; + int length; +} Keyword_Arguments; + + +Ast_Node_Array_List* create_Ast_Node_Array_List(int initial_length); +void append_to_Ast_Node_Array_List(Ast_Node_Array_List* list, struct Ast_Node* node); + +Positional_Arguments* create_positional_argument_list(int initial_capacity) { + Positional_Arguments* ret = new(Positional_Arguments); + ret->identifiers = (char**)malloc(initial_capacity * sizeof(char*)); + ret->next_index = 0; + ret->length = initial_capacity; + return ret; +} + +void append_to_positional_argument_list(Positional_Arguments* args, char* identifier) { + if (args->next_index == args->length) { + args->length *= 2; + args->identifiers = (char**)realloc(args->identifiers, args->length * sizeof(char*)); + } + args->identifiers[args->next_index++] = identifier; +} + +Keyword_Arguments* create_keyword_argument_list(int initial_capacity) { + Keyword_Arguments* ret = new(Keyword_Arguments); + ret->identifiers = (char**)malloc(initial_capacity * sizeof(char*)); + ret->values = create_Ast_Node_Array_List(initial_capacity); + ret->next_index = 0; + ret->length = initial_capacity; + return ret; +} + +void append_to_keyword_argument_list(Keyword_Arguments* args, + char* identifier, + struct Ast_Node* default_value) +{ + if (args->next_index == args->length) { + args->length *= 2; + args->identifiers = (char**)realloc(args->identifiers, args->length * sizeof(char*)); + } + + args->identifiers[args->next_index++] = identifier; + append_to_Ast_Node_Array_List(args->values, default_value); +} + + +typedef struct { + char* docstring; + Positional_Arguments* positional_arguments; + Keyword_Arguments* keyword_arguments; + // rest_argument will be nullptr if no rest argument is declared + char* rest_argument; + struct Ast_Node* body; // implicit prog } Function; typedef enum { @@ -134,12 +197,6 @@ struct Ast_Node { // was forward declarated typedef struct Ast_Node Ast_Node; -typedef struct { - Ast_Node** data; - int length; - int next_index; -} Ast_Node_Array_List; - typedef struct { Ast_Node_Array_List* positional_arguments; // TODO(Felix): Really use hashmap (keyword[sting] -> diff --git a/src/error.c b/src/error.c index b95fb9c..99d635f 100644 --- a/src/error.c +++ b/src/error.c @@ -1,6 +1,7 @@ typedef enum { Error_Type_Ill_Formed_List, Error_Type_Ill_Formed_Arguments, + Error_Type_Ill_Formed_Lambda_List, Error_Type_Wrong_Number_Of_Arguments, Error_Type_Unknown_Keyword_Argument, Error_Type_Type_Missmatch, @@ -40,6 +41,7 @@ char* Error_Type_to_string(Error_Type type) { switch (type) { case Error_Type_Ill_Formed_List: return "Evaluation-error: Ill formed list"; case Error_Type_Ill_Formed_Arguments: return "Evaluation-error: Ill formed arguments"; + case Error_Type_Ill_Formed_Lambda_List: return "Evaluation-error: Ill formed lambda list"; case Error_Type_Unknown_Keyword_Argument: return "Evaluation-error: Unknown keyword argument"; case Error_Type_Not_A_Function: return "Evaluation-error: Not a function"; case Error_Type_Symbol_Not_Defined: return "Evaluation-error: Symbol not defined"; diff --git a/src/eval.c b/src/eval.c index a98ed3e..01b28b2 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1,7 +1,140 @@ -/* Ast_Node* apply_to_lambda () {} */ -Ast_Node* eval_expr(Ast_Node* node, Environment* env); -bool is_truthy (Ast_Node* expression, Environment* env); +Ast_Node* apply_arguments_to_function (Ast_Node* arguments, Ast_Node* function) { + printf("calling function, yey\n"); + + return create_ast_node_nil(); +} + +/** + This parses the argument specification of funcitons into their + Function struct. It dois this by allocating new + positional_arguments, keyword_arguments and rest_argument and + filling it in +*/ +void parse_argument_list(Ast_Node* arguments, Function* function) { + // first init the fields + function->positional_arguments = create_positional_argument_list(16); + function->keyword_arguments = create_keyword_argument_list(16); + function->rest_argument = nullptr; + + // okay let's try to read some positional arguments + while (arguments->type == Ast_Node_Type_Pair) { + if (arguments->value.pair->first->type == Ast_Node_Type_Keyword) { + if (string_equal(arguments->value.pair->first->value.keyword->identifier, "keys") || + string_equal(arguments->value.pair->first->value.keyword->identifier, "rest")) + break; + else { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + } + + if (arguments->value.pair->first->type != Ast_Node_Type_Symbol) { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + + // okay wow we found an actual symbol + append_to_positional_argument_list( + function->positional_arguments, + arguments->value.pair->first->value.symbol->identifier); + + arguments = arguments->value.pair->rest; + } + + // okay we are done with positional arguments, lets check for + // keywords, + if (arguments->type != Ast_Node_Type_Pair) { + if (arguments->type != Ast_Node_Type_Nil) + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + + if (arguments->value.pair->first->type == Ast_Node_Type_Keyword && + string_equal(arguments->value.pair->first->value.keyword->identifier, "keys")) + { + arguments = arguments->value.pair->rest; + if (arguments->type != Ast_Node_Type_Pair || + arguments->value.pair->first->type != Ast_Node_Type_Symbol) + { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + + while (arguments->type == Ast_Node_Type_Pair) { + if (arguments->value.pair->first->type == Ast_Node_Type_Keyword) { + if (string_equal(arguments->value.pair->first->value.keyword->identifier, "rest")) + break; + else { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + } + + if (arguments->value.pair->first->type != Ast_Node_Type_Symbol) { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + + // we found a symbol (arguments->value.pair->first) for + // the keyword args! Let's check if the next arguement is + // :defaults-to + Ast_Node* next = arguments->value.pair->rest; + if (next->type == Ast_Node_Type_Pair && + next->value.pair->first->type == Ast_Node_Type_Keyword && + string_equal(next->value.pair->first->value.keyword->identifier, + "defaults-to")) + { + // check if there is a next argument too, otherwise it + // would be an error + next = next->value.pair->rest; + if (next->type == Ast_Node_Type_Pair) { + append_to_keyword_argument_list(function->keyword_arguments, + arguments->value.pair->first->value.symbol->identifier, + next->value.pair->first); + arguments = next->value.pair->rest; + } else { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + } else { + // No :defaults-to, so just add it to the list + append_to_keyword_argument_list(function->keyword_arguments, + arguments->value.pair->first->value.symbol->identifier, + nullptr); + arguments = next; + } + } + } + + + // Now we are also done with keyword arguments, lets check for + // if there is a rest argument + if (arguments->type != Ast_Node_Type_Pair) { + if (arguments->type != Ast_Node_Type_Nil) + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + + if (arguments->value.pair->first->type == Ast_Node_Type_Keyword && + string_equal(arguments->value.pair->first->value.keyword->identifier, "rest")) + { + arguments = arguments->value.pair->rest; + if (arguments->type != Ast_Node_Type_Pair || + arguments->value.pair->first->type != Ast_Node_Type_Symbol) + { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + return; + } + function->rest_argument = arguments->value.pair->first->value.symbol->identifier; + if (arguments->value.pair->rest->type != Ast_Node_Type_Nil) { + create_error(Error_Type_Ill_Formed_Lambda_List, arguments); + } + } else { + printf("this should not happen?"); + create_error(Error_Type_Unknown_Error, arguments); + } +} int list_length(Ast_Node* node) { @@ -56,6 +189,9 @@ Ast_Node* copy_list(Ast_Node* node) { return result; } +Ast_Node* eval_expr(Ast_Node* node, Environment* env); +bool is_truthy (Ast_Node* expression, Environment* env); + Parsed_Arguments* eval_and_parse_arguments(Ast_Node* arguments, Environment* env) { // TODO(Felix): if arguments is nil then maybe return a constant // Parsed_Arguments_nil? @@ -70,9 +206,9 @@ Parsed_Arguments* eval_and_parse_arguments(Ast_Node* arguments, Environment* env Parse_State parse_state = Expecting_Anything; Parsed_Arguments* result = new(Parsed_Arguments); - result->positional_arguments = create_Ast_Node_Array_List(); - result->keyword_keys = create_Ast_Node_Array_List(); - result->keyword_values = create_Ast_Node_Array_List(); + result->positional_arguments = create_Ast_Node_Array_List(16); + result->keyword_keys = create_Ast_Node_Array_List(16); + result->keyword_values = create_Ast_Node_Array_List(16); Ast_Node* current_head = copy_list(arguments); Ast_Node* current_evaluated_head; @@ -171,6 +307,47 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { // check for special form if (operator->type == Ast_Node_Type_Built_In_Function) { switch (operator->value.built_in_function->type) { + case Built_In_Lambda: { + /* + * (lambda ()) + * (lambda (x d) (+ 1 2) (- 1 2) (* 1 2)) + */ + try { + arguments_length = list_length(arguments); + } + if (arguments_length == 0) + report_error(Error_Type_Wrong_Number_Of_Arguments); + + + Function* function = new(Function); + // if parameters were specified + if (arguments->value.pair->first->type != Ast_Node_Type_Nil) { + try { + assert_type(arguments->value.pair->first, Ast_Node_Type_Pair); + } + try { + parse_argument_list(arguments->value.pair->first, function); + } + } + + arguments = arguments->value.pair->rest; + // if there is a docstring, use it + if (arguments->value.pair->first->type == Ast_Node_Type_String) { + function->docstring = arguments->value.pair->first->value.string->value; + arguments = arguments->value.pair->rest; + } + + // we are now in the function body, just wrap it in an + // implicit prog + function->body = create_ast_node_pair( + create_ast_node_built_in_function("prog"), + arguments); + + Ast_Node* ret = new(Ast_Node); + ret->type = Ast_Node_Type_Function; + ret->value.function = function; + return ret; + } case Built_In_And: { bool result = true; while (arguments->type != Ast_Node_Type_Nil) { @@ -454,6 +631,13 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { // assume it's lambda function and evaluate the arguments arguments = eval_arguments(arguments, env, &arguments_length); + if (operator->type == Ast_Node_Type_Function) { + Ast_Node* result; + try { + result = apply_arguments_to_function(arguments, operator); + } + return result; + } } default: diff --git a/src/init.c b/src/init.c index 0936392..fa93ae6 100644 --- a/src/init.c +++ b/src/init.c @@ -14,5 +14,5 @@ void init () { #endif // TODO(Felix): Init the constants here (Bulit-in function - // ast_nodes, nil, ..more?) + // ast_nodes, nil, type_keywords ..more?) } diff --git a/src/io.c b/src/io.c index c8c346e..2668954 100644 --- a/src/io.c +++ b/src/io.c @@ -69,6 +69,7 @@ void print(Ast_Node* node) { } break; case (Ast_Node_Type_Function): { printf("[lambda]"); + /* print(node->value.function->body); */ } break; case (Ast_Node_Type_Built_In_Function): { printf("[built-in-function %s]", Built_In_Name_to_string(node->value.built_in_function->type)); diff --git a/src/parse.c b/src/parse.c index c0b63e8..7004604 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1,22 +1,23 @@ -Ast_Node_Array_List* create_Ast_Node_Array_List() { +Ast_Node_Array_List* create_Ast_Node_Array_List(int initial_length) { Ast_Node_Array_List* ret = new (Ast_Node_Array_List); // create one with 16 entries first - ret->length = 16; - ret->data = (Ast_Node**)malloc(ret->length * sizeof(Ast_Node)); + ret->length = initial_length; + ret->data = (struct Ast_Node**)malloc(initial_length * sizeof(struct Ast_Node)); ret->next_index = 0; return ret; } -void append_to_Ast_Node_Array_List(Ast_Node_Array_List* list, Ast_Node* node) { +void append_to_Ast_Node_Array_List(Ast_Node_Array_List* list, struct Ast_Node* node) { if (list->next_index == list->length) { list->length *= 2; - list->data = (Ast_Node**)realloc(list->data, list->length * sizeof(Ast_Node)); + list->data = (struct Ast_Node**)realloc(list->data, list->length * sizeof(struct Ast_Node)); } list->data[list->next_index++] = node; } + void eat_comment_line(char* text, int* index_in_text) { // safety check if we are actually starting a comment here if (text[*index_in_text] != ';') @@ -237,7 +238,7 @@ Ast_Node* parse_single_expression(char* text) { } Ast_Node_Array_List* parse_program(char* text) { - Ast_Node_Array_List* program = create_Ast_Node_Array_List(); + Ast_Node_Array_List* program = create_Ast_Node_Array_List(16); int index_in_text = 0; diff --git a/test.bat b/test.bat index 53ed669..5c794e4 100644 --- a/test.bat +++ b/test.bat @@ -4,7 +4,7 @@ pushd %~dp0\bin call ..\build.bat if %errorlevel% == 0 ( echo ---------- Testing ---------- - call timecmd lisp.exe test.lsp + call timecmd slime.exe test.slime ) popd diff --git a/todo.html b/todo.html new file mode 100644 index 0000000..c5d53d7 --- /dev/null +++ b/todo.html @@ -0,0 +1,539 @@ + + + + + + + + + + + + + + + + + +
+

+
+

1 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. +

+
+ +
((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
+
+
+ + +

+But if mulitiple keyword arguments are defined, their ordering does not matter. +

+
+ +
((lambda (a &key b &key c &key d) (+ a b c d)) 1 :d 4 :c 3 :b 2) ;; 10
+
+
+ +

+It is even possible to have a non keyword argument bewtween keyword arguments +and when calling, switch the order. +

+
+ +
((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
+
+
+ +

+However somehow it is not possible to flip the ordering at the first argument +

+
+ +
((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
+
+
+ +

+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. +

+
+ +
+

1.1 Idea

+
+

+Every argument can be set using keys, but keyword arguments always come strictly +after positional arguments +

+ +
+ +
((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
+
+
+ +

+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. +

+ +
+ +
(= (type "hello") :string)
+
+
+ + +
+ +
((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
+
+
+ +

+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 +

+ +
+ +
<lambda list> -> <positional argument>*
+                 [:keys ( <keyword arguments> [:defaults-to <value>])*]
+                 [(:rest <rest argument>)|(:body <body argument>)]
+
+
+ +
+ +
  (= (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
+
+
+
+ +

+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. +

+ +
+ +
>> (define print-before-eval (macro (:rest expr)
+     (print expr)
+     (eval expr))
+
+
+
+
+
+ +
+

1.2 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: +

+ +
+ +
>> (define fun (lambda (a b :key c d :defaults-to 4 :rest) nil))
+>> (fun 1 2 :c 3 :d 5)
+
+
+

+What should this be evaluated to? +

+ + + + +++ ++ ++ ++ ++ ++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 abcdrest
v11235nil
v21234(: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. +

+
+
+ +
+

1.3 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. +
  3. The keyword is not a keyword argument in the funciton +
  4. +
+ +

+), 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. +

+
+
+ +
+

1.4 Implementation

+
+
+ +
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;
+
+
+
+
+
+
+ + + diff --git a/todo.org b/todo.org index ca0cceb..af2c616 100644 --- a/todo.org +++ b/todo.org @@ -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 + -> * + [:keys ( [:defaults-to ])*] + [(:rest )|(:body )] +#+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)) diff --git a/vs/lisp.sln b/vs/slime.sln similarity index 93% rename from vs/lisp.sln rename to vs/slime.sln index 4ce7f5a..d61dfe6 100644 --- a/vs/lisp.sln +++ b/vs/slime.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2005 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "lisp", "lisp.vcxproj", "{1A47A3ED-871F-4CB4-875B-8CAA385B1771}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slime", "slime.vcxproj", "{1A47A3ED-871F-4CB4-875B-8CAA385B1771}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/vs/lisp.vcxproj b/vs/slime.vcxproj similarity index 98% rename from vs/lisp.vcxproj rename to vs/slime.vcxproj index 76f9b77..b74f8cc 100644 --- a/vs/lisp.vcxproj +++ b/vs/slime.vcxproj @@ -29,8 +29,9 @@ 15.0 {1A47A3ED-871F-4CB4-875B-8CAA385B1771} - lisp + slime 10.0.16299.0 + slime