diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..beab90c --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,36 @@ +((c-mode . ((eval . (company-clang-set-prefix "main.c")) + (eval . (flycheck-mode 0)) + (eval . (rainbow-mode 0)))) + (nil . ((eval . (progn + (defvar context-mode-map (make-sparse-keymap) + "Keymap while context-mode is active.") + (define-minor-mode context-mode + "A temporary minor mode to be activated only specific to a buffer." + nil + :lighter " Context" + context-mode-map) + (context-mode 1) + + ;; additional scripts + (defun save-and-find-test-script-and-compile () + (interactive) + (let ((build-script-name "test.bat")) + (save-and-find-build-script-and-compile))) + (defun save-and-find-run-script-and-compile () + (interactive) + (let ((build-script-name "run.bat")) + (save-and-find-build-script-and-compile))) + (defun save-and-find-debug-script-and-compile () + (interactive) + (let ((build-script-name "debug.bat")) + (save-and-find-build-script-and-compile))) + + (defhydra hydra-context (context-mode-map "") + "Context Actions:" + ("b" save-and-find-build-script-and-compile "build" :color blue) + ("r" save-and-find-run-script-and-compile "run" :color blue) + ("d" save-and-find-debug-script-and-compile "debug" :color blue) + ("t" save-and-find-test-script-and-compile "test" :color blue) + ("q" nil "quit" :color blue)) + (define-key context-mode-map (kbd "") 'hydra-context/body) + ))))) diff --git a/.gitignore b/.gitignore index af8047f..aef634a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,6 @@ /*.ilk /*.pdb /.vs/ -/vs/ \ No newline at end of file +/vs/ +/*.exe +*.exe diff --git a/bin/test.lsp b/bin/test.lsp index f07f3dd..e184309 100644 --- a/bin/test.lsp +++ b/bin/test.lsp @@ -1 +1,12 @@ -(/ (+ 1 (- 100 7 3)) 2) +((lambda (x) (* x x)) 2) + +(setq a 3) + +(let ((a 10)) + (let ((b 5)) + (setq a 4)) + a) + + +a + diff --git a/build.bat b/build.bat index 40d7812..cbc0e41 100644 --- a/build.bat +++ b/build.bat @@ -2,8 +2,6 @@ @setlocal pushd %~dp0 -set start=%time% - set exeName=lisp.exe set binDir=bin @@ -17,11 +15,6 @@ if %errorlevel% == 0 ( echo. if not exist ..\%binDir% mkdir ..\%binDir% move %exeName% ..\%binDir%\ > NUL - pushd ..\%binDir% - echo ---------- Running ---------- - call timecmd %exeName% test.lsp - del %exeName% /S /Q > NUL - popd ) else ( echo. echo Fucki'n 'ell diff --git a/debug.bat b/debug.bat new file mode 100644 index 0000000..f3d1fe7 --- /dev/null +++ b/debug.bat @@ -0,0 +1,3 @@ +pushd %~dp0\vs +start lisp.sln +popd diff --git a/run.bat b/run.bat new file mode 100644 index 0000000..7abd6bf --- /dev/null +++ b/run.bat @@ -0,0 +1,9 @@ +@echo off +pushd %~dp0\bin + +call ..\build.bat +if %errorlevel% == 0 ( + echo ---------- Running ---------- + call timecmd lisp.exe +) +popd diff --git a/src/.dir-locals.el b/src/.dir-locals.el deleted file mode 100644 index 7b889ee..0000000 --- a/src/.dir-locals.el +++ /dev/null @@ -1,3 +0,0 @@ -((c-mode . ((eval . (company-clang-set-prefix "main.c")) - (eval . (flycheck-mode 0)) - (eval . (rainbow-mode 0))))) diff --git a/src/env.c b/src/env.c index c93fe4a..b9ff38f 100644 --- a/src/env.c +++ b/src/env.c @@ -1,7 +1,8 @@ struct Environment { struct Environment* parent; - int key_count; + int capacity; + int next_index; char** keys; Ast_Node* values; }; @@ -11,16 +12,37 @@ typedef struct Environment Environment; Environment* create_empty_environment() { Environment* env = new(Environment); - env->parent = nullptr; - env->key_count = 0; - env->keys = nullptr; - env->values = nullptr; + int start_capacity = 1; + + env->parent = nullptr; + env->capacity = start_capacity; + env->next_index = 0; + env->keys = (char**)malloc(start_capacity * sizeof(char*)); + env->values = (Ast_Node*)malloc(start_capacity * sizeof(Ast_Node)); return env; } +void define_symbol(Ast_Node* symbol, Ast_Node* value, Environment* env) { + // NOTE(Felix): right now we are simmply adding the symol at the + // back of the list without checking if it already exists but are + // also searching for thesymbol from the back, so we will find the + // latest defined one first, but a bit messy. Later we should use + // a hashmap here. @refactor + + if (env->next_index == env->capacity) { + env->capacity *= 2; + env->keys = (char**)realloc(env->keys, env->capacity * sizeof(char*)); + env->values = (Ast_Node*)realloc(env->values, env->capacity * sizeof(Ast_Node)); + } + + env->keys [env->next_index] = symbol->value.symbol->identifier; + env->values[env->next_index] = *value; + ++env->next_index; +} + Ast_Node* lookup_symbol(Symbol* sym, Environment* env) { - for (int i = 0; i < env->key_count; ++i) + for (int i = env->next_index - 1; i >= 0; --i) if (string_equal(env->keys[i], sym->identifier)) return env->values+i; @@ -30,19 +52,19 @@ Ast_Node* lookup_symbol(Symbol* sym, Environment* env) { char* built_in_names[] = { // Math stuff "+", "-", "*", "/", - ">", "<", "=", + ">", "<", "=", "<=", ">=", // Conditional stuff "if", "and", "or", "not", // Cons stuff "first", "rest", "pair", // rest - "load", "define", + "load", "define", "lambda", "progn", - "eval", "quote", - "print", "read", - "help" + "eval", "quote", + "print", "read", + "help", "exit" }; - int built_in_count = 23; + int built_in_count = 26; for (int i = 0; i < built_in_count; ++i) { if (string_equal(built_in_names[i], sym->identifier)) { diff --git a/src/error.c b/src/error.c index 764aab0..fa67ce2 100644 --- a/src/error.c +++ b/src/error.c @@ -7,6 +7,8 @@ typedef enum { Error_Type_Not_Yet_Implemented, Error_Type_Syntax_Error, Error_Type_Unexpected_Eof, + Error_Type_Unbalanced_Parenthesis, + Error_Type_Trailing_Garbage, Error_Type_Unknown_Error, } Error_Type; @@ -34,15 +36,16 @@ void create_error(Error_Type type, Ast_Node* location) { char* Error_Type_to_string(Error_Type type) { switch (type) { - case Error_Type_Ill_Formed_List: return "Ill formed list"; - case Error_Type_Not_A_Function: return "Not a function"; - case Error_Type_Symbol_Not_Defined: return "Symbol not defined"; - case Error_Type_Wrong_Number_Of_Arguments: return "Wrong number of arguments"; - case Error_Type_Type_Missmatch: return "Type Missmatch"; - case Error_Type_Not_Yet_Implemented: return "Not yet implemented"; + case Error_Type_Ill_Formed_List: return "Evaluation-error: Ill formed list"; + case Error_Type_Not_A_Function: return "Evaluation-error: Not a function"; + case Error_Type_Symbol_Not_Defined: return "Evaluation-error: Symbol not defined"; + case Error_Type_Wrong_Number_Of_Arguments: return "Evaluation-error: Wrong number of arguments"; + case Error_Type_Type_Missmatch: return "Evaluation-error: Type Missmatch"; + case Error_Type_Not_Yet_Implemented: return "Evaluation-error: Not yet implemented"; + case Error_Type_Trailing_Garbage: return "Evaluation-error: Trailing garbage following expression"; case Error_Type_Syntax_Error: return "Syntax Error"; - case Error_Type_Unexpected_Eof: return "Unexpected EOF"; + case Error_Type_Unexpected_Eof: return "Parsing-error: Unexpected EOF"; + case Error_Type_Unbalanced_Parenthesis: return "Parsing-error: Unbalanced parenthesis"; default: return "Unknown Error"; } } - diff --git a/src/eval.c b/src/eval.c index 223d16d..897ce3f 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1,9 +1,5 @@ /* Ast_Node* apply_to_lambda () {} */ -/* Ast_Node* apply_to_built_in (Ast_Node* function, Ast_Node* arguments) { */ - -/* } */ - Ast_Node* eval_expr(Ast_Node* node, Environment* env); bool is_truthy (Ast_Node* expression, Environment* env); @@ -43,10 +39,10 @@ void eval_operands(Ast_Node* operands, Environment* env) { Ast_Node* eval_expr(Ast_Node* node, Environment* env) { #define report_error(_type) { \ create_error(_type, node); \ - log_error(); \ - exit(1); \ return nullptr; \ } + if (error) + return nullptr; Ast_Node* ret = new(Ast_Node); switch (node->type) { @@ -56,31 +52,112 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { case Ast_Node_Type_Symbol: return lookup_symbol(node->value.symbol, env); case Ast_Node_Type_Number: + case Ast_Node_Type_Keyword: case Ast_Node_Type_String: return node; case Ast_Node_Type_Pair: { Ast_Node* operator = eval_expr(node->value.pair->first, env); + if (error) return nullptr; + Ast_Node* operands = node->value.pair->rest; // check for built ins functions if (operator->type == Ast_Node_Type_Built_In_Function) { char* operator_name = operator->value.built_in_function->identifier; if (string_equal("quote", operator_name)) { - return node->value.pair->rest; + int operands_length = list_length(operands); + if (operands_length != 1) { + report_error(Error_Type_Wrong_Number_Of_Arguments); + } + return operands->value.pair->first; + } else if (string_equal("eval", operator_name)) { + int operands_length = list_length(operands); + if (operands_length != 1) { + report_error(Error_Type_Wrong_Number_Of_Arguments); + } + if (error) return nullptr; + return eval_expr(operands->value.pair->first, env); } else if (string_equal("+", operator_name)) { eval_operands(operands, env); + if (error) return nullptr; return built_in_add(operands); } else if (string_equal("-", operator_name)) { eval_operands(operands, env); + if (error) return nullptr; return built_in_substract(operands); } else if (string_equal("*", operator_name)) { eval_operands(operands, env); + if (error) return nullptr; return built_in_multiply(operands); } else if (string_equal("/", operator_name)) { eval_operands(operands, env); + if (error) return nullptr; return built_in_divide(operands); + } else if (string_equal("define", operator_name)) { + int operands_length = list_length(operands); + if (error) return nullptr; + if (operands_length != 2) { + report_error(Error_Type_Wrong_Number_Of_Arguments); + } + + Ast_Node* symbol = operands->value.pair->first; + if (symbol->type != Ast_Node_Type_Symbol) + report_error(Error_Type_Type_Missmatch); + + Ast_Node* value = operands->value.pair->rest->value.pair->first; + value = eval_expr(value, env); + + define_symbol(symbol, value, env); + + return value; + } else if (string_equal("exit", operator_name)) { + int operands_length = list_length(operands); + if (error) return nullptr; + if (operands_length > 1) { + report_error(Error_Type_Wrong_Number_Of_Arguments); + } + + if (operands_length == 1) { + Ast_Node* error_code = operands->value.pair->first; + if (error_code->type != Ast_Node_Type_Number) + report_error(Error_Type_Type_Missmatch); + + exit((int)error_code->value.number->value); + } + exit(0); + + } else if (string_equal("print", operator_name)) { + int operands_length = list_length(operands); + if (error) return nullptr; + if (operands_length != 1) { + report_error(Error_Type_Wrong_Number_Of_Arguments); + } + eval_operands(operands, env); + if (error) return nullptr; + print(operands->value.pair->first); + printf("\n"); + return operands->value.pair->first; + } else if (string_equal("read", operator_name)) { + int operands_length = list_length(operands); + if (error) return nullptr; + if (operands_length > 1) { + report_error(Error_Type_Wrong_Number_Of_Arguments); + } + + if (operands_length == 1) { + eval_operands(operands, env); + if (error) return nullptr; + Ast_Node* prompt = operands->value.pair->first; + if (prompt->type == Ast_Node_Type_String) + printf("%s", prompt->value.string->value); + else + print(operands->value.pair->first); + } + char* line = read_line(); + return create_ast_node_string(line, (int)strlen(line)); } else if (string_equal("if", operator_name)) { int operands_length = list_length(operands); + if (error) return nullptr; if (operands_length != 2 && operands_length != 3) { report_error(Error_Type_Wrong_Number_Of_Arguments); } @@ -89,7 +166,9 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { Ast_Node* then_part = operands->value.pair->rest; Ast_Node* else_part = then_part->value.pair->rest; - if (is_truthy(condition, env)) + bool truthy = is_truthy(condition, env); + if (error) return nullptr; + if (truthy) return eval_expr(then_part->value.pair->first, env); else if (operands_length == 3) return eval_expr(else_part->value.pair->first, env); @@ -102,6 +181,7 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { report_error(Error_Type_Ill_Formed_List); } result &= is_truthy(operands->value.pair->first, env); + if (error) return nullptr; operands = operands->value.pair->rest; if (!result) return create_ast_node_nil(); @@ -116,6 +196,7 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { report_error(Error_Type_Ill_Formed_List); } result |= is_truthy(operands->value.pair->first, env); + if (error) return nullptr; operands = operands->value.pair->rest; if (result) return create_ast_node_number(1);; @@ -129,6 +210,7 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { return nullptr; } bool truthy = is_truthy(operands->value.pair->first, env); + if (error) return nullptr; if (truthy) return create_ast_node_nil(); return create_ast_node_number(1); @@ -150,6 +232,7 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { bool is_truthy (Ast_Node* expression, Environment* env) { Ast_Node* result = eval_expr(expression, env); + if (error) return false; switch (result->type) { case Ast_Node_Type_Nil: return false; /* case Ast_Node_Type_Number: return result->value.number->value != 0; */ diff --git a/src/main.c b/src/main.c index e6c4651..b44c18b 100644 --- a/src/main.c +++ b/src/main.c @@ -16,46 +16,66 @@ #include "./eval.c" #include "./testing.c" +int interprete_file (char* file_content) { + Ast_Node_Array_List* program = parse_program(file_content); + if (error) { + log_error(); + return 1; + } + Environment* env = create_empty_environment(); + Ast_Node* result = create_ast_node_nil(); + for (int i = 0; i < program->next_index; ++i) { + result = eval_expr(program->data[i], env); + if (error) { + log_error(); + return 1; + } + } + print(result); + printf("\n"); + return 0; +} + +int interprete_stdin () { + printf("Welcome to the lispy interpreter.\n"); + char* line; + Environment* env = create_empty_environment(); + Ast_Node* parsed, * evaluated; + while (true) { + printf(">"); + line = read_line(); + parsed = parse_single_expression(line); + if (error) { + log_error(); + delete_error(); + continue; + } + evaluated = eval_expr(parsed, env); + if (error) { + log_error(); + delete_error(); + continue; + } + print(evaluated); + printf("\n"); + } + return 0; +} + + int main (int argc, char *argv[]) { if (argc > 1) { - char* fileContent = read_entire_file(argv[1]); - if (fileContent) { - Ast_Node_Array_List* program = parse_program(fileContent); - if (error) { - log_error(); - exit(1); - } - Environment* env = create_empty_environment(); - Ast_Node* result = create_ast_node_nil(); - for (int i = 0; i < program->next_index; ++i) { - result = eval_expr(program->data[i], env); - if (error) { - log_error(); - exit(1); - } - } - print(result); - printf("\n"); + char* file_content = read_entire_file(argv[1]); + if (file_content) { + return interprete_file(file_content); } else { printf("The file could not be read\n"); + return 1; } } else { run_all_tests(); - - printf("Welcome to the lispy interpreter.\n"); - char* line; - Environment* env = create_empty_environment(); - Ast_Node* parsed, * evaluated; - while (true) { - printf(">"); - line = read_line(); - parsed = parse_single_expression(line); - evaluated = eval_expr(parsed, env); - print(evaluated); - printf("\n"); - } + return interprete_stdin(); } - return 0; } diff --git a/src/parse.c b/src/parse.c index ca63f11..6d731cb 100644 --- a/src/parse.c +++ b/src/parse.c @@ -18,10 +18,8 @@ Ast_Node_Array_List* create_Ast_Node_Array_List() { void append_to_Ast_Node_Array_List(Ast_Node_Array_List* list, 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 = (Ast_Node**)realloc(list->data, list->length * sizeof(Ast_Node)); } - list->data[list->next_index++] = node; } @@ -231,7 +229,17 @@ Ast_Node* parse_expression(char* text, int* index_in_text) { Ast_Node* parse_single_expression(char* text) { int index_in_text = 0; - return parse_expression(text, &index_in_text); + Ast_Node* result; + eat_until_code(text, &index_in_text); + if (text[(index_in_text)] == '(') + result = parse_expression(text, &index_in_text); + else + result = parse_atom(text, &index_in_text); + eat_until_code(text, &index_in_text); + if (text[(index_in_text)] == '\0') + return result; + create_error(Error_Type_Trailing_Garbage, create_ast_node_nil()); + return nullptr; } Ast_Node_Array_List* parse_program(char* text) { @@ -247,13 +255,11 @@ Ast_Node_Array_List* parse_program(char* text) { return nullptr; append_to_Ast_Node_Array_List(program, parsed); } break; - case ';': { - eat_comment_line(text, &index_in_text); - } break; + case ';': case ' ': case '\t': case '\n': { - ++index_in_text; + eat_until_code(text, &index_in_text); } break; default: /* syntax error */ diff --git a/test.bat b/test.bat new file mode 100644 index 0000000..53ed669 --- /dev/null +++ b/test.bat @@ -0,0 +1,10 @@ +@echo off +pushd %~dp0\bin + +call ..\build.bat +if %errorlevel% == 0 ( + echo ---------- Testing ---------- + call timecmd lisp.exe test.lsp +) + +popd