diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..1909c38 --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,2 @@ +((nil . ((eval . (flycheck-mode 0)) + (eval . (rainbow-mode 0))))) diff --git a/assert.c b/assert.c new file mode 100644 index 0000000..03549cf --- /dev/null +++ b/assert.c @@ -0,0 +1,11 @@ +void assert_type (Ast_Node* node, Ast_Node_Type type) { + if (node->type == type) return; + + char *wanted, *got, *message; + wanted = Ast_Node_Type_to_string(type); + got = Ast_Node_Type_to_string(node->type); + asprintf(&message, "Type assertion failed:\n\t" + "Wanted: %s\n\t" + "Got : %s\n", wanted, got); + panic(message); +} diff --git a/ast.c b/ast.c index cc1a494..b2c916f 100644 --- a/ast.c +++ b/ast.c @@ -4,18 +4,23 @@ typedef enum { Ast_Node_Type_Nil, Ast_Node_Type_Symbol, + Ast_Node_Type_Keyword, Ast_Node_Type_Number, Ast_Node_Type_String, Ast_Node_Type_Cons_Cell, Ast_Node_Type_Function, Ast_Node_Type_Built_In_Function, -} Ast_Type; +} Ast_Node_Type; typedef struct { char* identifier; } Symbol; +typedef struct { + char* identifier; +} Keyword; + typedef struct { double value; } Number; @@ -44,9 +49,10 @@ typedef struct { } Built_In_Function; struct Ast_Node { - Ast_Type type; + Ast_Node_Type type; union { Symbol* symbol; + Keyword* keyword; Number* number; String* string; Cons_Cell* cons_cell; @@ -73,6 +79,13 @@ Ast_Node* create_ast_node_number(double number) { return node; } +Ast_Node* create_ast_node_string(char* str) { + Ast_Node* node = new(Ast_Node); + node->type = Ast_Node_Type_String; + node->value.string = new(String); + node->value.string->value = str; + return node; +} Ast_Node* create_ast_node_symbol(char* identifier) { Ast_Node* node = new(Ast_Node); diff --git a/built_ins.c b/built_ins.c index 8996c2d..c98c560 100644 --- a/built_ins.c +++ b/built_ins.c @@ -1,7 +1,7 @@ Ast_Node* built_in_add(Ast_Node* operands) { double sum = 0; while (operands->type == Ast_Node_Type_Cons_Cell) { - // TODO(Felix): assert type + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); sum += operands->value.cons_cell->first->value.number->value; operands = operands->value.cons_cell->rest; } @@ -10,12 +10,43 @@ Ast_Node* built_in_add(Ast_Node* operands) { } Ast_Node* built_in_substract(Ast_Node* operands) { - double diff = operands->value.cons_cell->first->value.number->value; + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); + double difference = operands->value.cons_cell->first->value.number->value; + + operands = operands->value.cons_cell->rest; + while (operands->type == Ast_Node_Type_Cons_Cell) { + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); + + difference -= operands->value.cons_cell->first->value.number->value; + operands = operands->value.cons_cell->rest; + } + return create_ast_node_number(difference); +} + +Ast_Node* built_in_multiply(Ast_Node* operands) { + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); + double product = operands->value.cons_cell->first->value.number->value; + operands = operands->value.cons_cell->rest; while (operands->type == Ast_Node_Type_Cons_Cell) { - // TODO(Felix): assert type - diff -= operands->value.cons_cell->first->value.number->value; + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); + + product *= operands->value.cons_cell->first->value.number->value; + operands = operands->value.cons_cell->rest; + } + return create_ast_node_number(product); +} + +Ast_Node* built_in_divide(Ast_Node* operands) { + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); + double quotient = operands->value.cons_cell->first->value.number->value; + + operands = operands->value.cons_cell->rest; + while (operands->type == Ast_Node_Type_Cons_Cell) { + assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number); + + quotient /= operands->value.cons_cell->first->value.number->value; operands = operands->value.cons_cell->rest; } - return create_ast_node_number(diff); + return create_ast_node_number(quotient); } diff --git a/env.c b/env.c index bdb9ab5..c829411 100644 --- a/env.c +++ b/env.c @@ -54,5 +54,9 @@ Ast_Node* lookup_symbol(Symbol* sym, Environment* env) { } } + char* message; + asprintf(&message, "Symbol not defined: %s\n", sym->identifier); + panic(message); + return NULL; } diff --git a/error.c b/error.c new file mode 100644 index 0000000..b3d49e3 --- /dev/null +++ b/error.c @@ -0,0 +1,6 @@ +typedef struct { + char* message; + Ast_Node* location; +} Error; + +Error* error; diff --git a/eval.c b/eval.c index a74f492..4b3f5d9 100644 --- a/eval.c +++ b/eval.c @@ -5,19 +5,28 @@ /* } */ Ast_Node* eval_expr(Ast_Node* node, Environment* env); +int is_truthy (Ast_Node* expression, Environment* env); void eval_operands(Ast_Node* operands, Environment* env) { - while (1) { + while (!error) { if (operands->type == Ast_Node_Type_Cons_Cell) { operands->value.cons_cell->first = eval_expr(operands->value.cons_cell->first, env); operands = operands->value.cons_cell->rest; } else { - break; + return; } } + // if we reach here we got an error + log_error("An error occurred while evaluating operands to a function:"); } Ast_Node* eval_expr(Ast_Node* node, Environment* env) { +#define report_error(_message) \ + error = new(Error); \ + error->message = _message; \ + error->location = node; \ + return NULL + Ast_Node* ret = new(Ast_Node); switch (node->type) { case Ast_Node_Type_Nil: @@ -44,8 +53,32 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { } else if (string_equal("-", operator_name)) { eval_operands(operands, env); return built_in_substract(operands); + } else if (string_equal("*", operator_name)) { + eval_operands(operands, env); + return built_in_multiply(operands); + } else if (string_equal("/", operator_name)) { + eval_operands(operands, env); + return built_in_divide(operands); + } else if (string_equal("if", operator_name)) { + if (Ast_Node_Type_Cons_Cell != operands->value.cons_cell->rest->type || + Ast_Node_Type_Cons_Cell != operands->value.cons_cell->rest->value.cons_cell->rest->type || + Ast_Node_Type_Nil != operands->value.cons_cell->rest->value.cons_cell->rest->value.cons_cell->rest->type) + { + report_error("Ill formed if statement"); + } + + Ast_Node* then_part = operands->value.cons_cell->rest; + Ast_Node* else_part = then_part->value.cons_cell->rest; + + if (is_truthy(operands->value.cons_cell->first, env)) + return eval_expr(then_part->value.cons_cell->first, env); + else + return eval_expr(else_part->value.cons_cell->first, env); + } else if (string_equal("not", operator_name)) { + } else if (string_equal("and", operator_name)) { + } else if (string_equal("or", operator_name)) { } else { - log_message(Log_Level_Critical, "The operator is not yet implemented\n"); + report_error("The operator is not yet implemented"); } } @@ -55,6 +88,16 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) { } default: - return 0; + report_error("Unknown error"); + } +#undef report_error +} + +int is_truthy (Ast_Node* expression, Environment* env) { + Ast_Node* result = eval_expr(expression, env); + switch (result->type) { + case Ast_Node_Type_Nil: return 0; + case Ast_Node_Type_Number: return result->value.number->value != 0; + default: return 1; } } diff --git a/helpers.c b/helpers.c index 1bf6a28..5fb5d4b 100644 --- a/helpers.c +++ b/helpers.c @@ -1,3 +1,71 @@ +#define new(type) (type*)malloc(sizeof(type)) + int string_equal(char* a, char* b) { return !strcmp(a, b); } + +// asprintf implementation +int _vscprintf_so(const char * format, va_list pargs) { + int retval; + va_list argcopy; + va_copy(argcopy, pargs); + retval = vsnprintf(NULL, 0, format, argcopy); + va_end(argcopy); + return retval; +} + +int vasprintf(char **strp, const char *fmt, va_list ap) { + int len = _vscprintf_so(fmt, ap); + if (len == -1) return -1; + char *str = malloc((size_t) len + 1); + if (!str) return -1; + int r = vsnprintf(str, len + 1, fmt, ap); /* "secure" version of vsprintf */ + if (r == -1) return free(str), -1; + *strp = str; + return r; +} + +int asprintf(char *strp[], const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int r = vasprintf(strp, fmt, ap); + va_end(ap); + return r; +} +// asprintf implementation end + +char* read_entire_file (char* filename) { + char *fileContent = NULL; + FILE *fp = fopen(filename, "r"); + if (fp != NULL) { + /* Go to the end of the file. */ + if (fseek(fp, 0L, SEEK_END) == 0) { + /* Get the size of the file. */ + long bufsize = ftell(fp); + if (bufsize == -1) { + fputs("Empty file", stderr); + goto closeFile; + } + + /* Go back to the start of the file. */ + if (fseek(fp, 0L, SEEK_SET) != 0) { + fputs("Error reading file", stderr); + goto closeFile; + } + + /* Allocate our buffer to that size. */ + fileContent = malloc(sizeof(char) * (bufsize)); + + /* Read the entire file into memory. */ + size_t newLen = fread(fileContent, sizeof(char), bufsize, fp); + if ( ferror( fp ) != 0 ) { + fputs("Error reading file", stderr); + } + } + closeFile: + fclose(fp); + } + + return fileContent; + /* Don't forget to call free() later! */ +} diff --git a/io.c b/io.c index 5dd8454..6410d62 100644 --- a/io.c +++ b/io.c @@ -1,55 +1,60 @@ typedef enum { + Log_Level_None, + Log_Level_Critical, Log_Level_Warning, Log_Level_Info, Log_Level_Debug, } Log_Level; +Log_Level log_level = Log_Level_Debug; + void log_message(Log_Level type, char* message) { + if (type > log_level) + return; + char* prefix; switch (type) { - case Log_Level_Critical: prefix = "CRIT"; break; - case Log_Level_Warning: prefix = "WARN"; break; - case Log_Level_Info: prefix = "INFO"; break; - case Log_Level_Debug: prefix = "DBUG"; break; + case Log_Level_Critical: prefix = "CRITICAL"; break; + case Log_Level_Warning: prefix = "WARNING"; break; + case Log_Level_Info: prefix = "INFO"; break; + case Log_Level_Debug: prefix = "DEBUG"; break; + case Log_Level_None: return; } - printf("%s: %s",prefix, message); + printf("%s: %s\n",prefix, message); } -char* read_entire_file (char* filename) { - char *fileContent = NULL; - FILE *fp = fopen(filename, "r"); - if (fp != NULL) { - /* Go to the end of the file. */ - if (fseek(fp, 0L, SEEK_END) == 0) { - /* Get the size of the file. */ - long bufsize = ftell(fp); - if (bufsize == -1) { - fputs("Empty file", stderr); - goto closeFile; - } +void log_error(char* message) { + log_message(Log_Level_Critical, message); + log_message(Log_Level_Critical, error->message); +} - /* Go back to the start of the file. */ - if (fseek(fp, 0L, SEEK_SET) != 0) { - fputs("Error reading file", stderr); - goto closeFile; - } - /* Allocate our buffer to that size. */ - fileContent = malloc(sizeof(char) * (bufsize)); +void panic(char* message) { + log_message(Log_Level_Critical, message); + exit(1); +} - /* Read the entire file into memory. */ - size_t newLen = fread(fileContent, sizeof(char), bufsize, fp); - if ( ferror( fp ) != 0 ) { - fputs("Error reading file", stderr); - } - } - closeFile: - fclose(fp); - } - return fileContent; - /* Don't forget to call free() later! */ +char* Ast_Node_Type_to_string (Ast_Node_Type type) { + switch (type) { + case(Ast_Node_Type_Nil): + return "nil"; + case(Ast_Node_Type_Number): + return "number"; + case(Ast_Node_Type_String): + return "string"; + case(Ast_Node_Type_Symbol): + return "symbol"; + case(Ast_Node_Type_Keyword): + return "keyword"; + case(Ast_Node_Type_Function): + return "function"; + case(Ast_Node_Type_Built_In_Function): + return "built-in function"; + case(Ast_Node_Type_Cons_Cell): + return "pair"; + } } void print(Ast_Node* node) { @@ -66,6 +71,9 @@ void print(Ast_Node* node) { case (Ast_Node_Type_Symbol): { printf("%s", node->value.symbol->identifier); } break; + case (Ast_Node_Type_Keyword): { + printf(":%s", node->value.keyword->identifier); + } break; case (Ast_Node_Type_Function): { printf("[lambda]"); } break; @@ -73,18 +81,22 @@ void print(Ast_Node* node) { printf("[built-in-function %s]", node->value.built_in_function->identifier); } break; case (Ast_Node_Type_Cons_Cell): { + Ast_Node* head = node; printf("("); - if (node->value.cons_cell->first) - print(node->value.cons_cell->first); - else - printf("nil"); - printf(" . "); + while (1) { + print(head->value.cons_cell->first); + head = head->value.cons_cell->rest; + if (head->type != Ast_Node_Type_Cons_Cell) + break; + printf(" "); + } + + if (head->type != Ast_Node_Type_Nil) { + printf(" . "); + print(head); + } - if (node->value.cons_cell->rest) - print(node->value.cons_cell->rest); - else - printf("nil"); printf(")"); } break; } diff --git a/main.c b/main.c index 366950a..65f8f37 100644 --- a/main.c +++ b/main.c @@ -1,55 +1,19 @@ #include #include #include - -#define new(type) (type*)malloc(sizeof(type)) +#include /* needed for va_list */ +#include #include "./helpers.c" #include "./ast.c" +#include "./error.c" #include "./io.c" +#include "./assert.c" #include "./parse.c" #include "./built_ins.c" #include "./env.c" #include "./eval.c" - -void test() { - // (+ 4 10) - Ast_Node* nil = create_ast_node_nil(); - Ast_Node* ten = create_ast_node_number(10); - Ast_Node* four = create_ast_node_number(4); - Ast_Node* plus = create_ast_node_symbol("+"); - Ast_Node* minus = create_ast_node_symbol("-"); - - Ast_Node* form1 = create_ast_node_pair( - plus, - create_ast_node_pair( - ten, - create_ast_node_pair( - four, - nil))); - - Ast_Node* form2 = create_ast_node_pair( - plus, - create_ast_node_pair( - four, - create_ast_node_pair( - four, - nil))); - - Ast_Node* root = create_ast_node_pair( - minus, - create_ast_node_pair( - form1, - create_ast_node_pair( - form2, - nil))); - - print(root); - printf("\n"); - Ast_Node* result = eval_expr(root, new(Environment)); - print(result); - printf("\n"); -} +#include "./testing.c" int main (int argc, char *argv[]) { /* if (argc > 1) { */ @@ -61,7 +25,7 @@ int main (int argc, char *argv[]) { /* printf("No source provided.\n"); */ /* } */ - /* test(); */ - print(parse_expression("(+ 4 7)")); + run_all_tests(); + return 0; } diff --git a/parse.c b/parse.c index ebb9b5b..f1db90d 100644 --- a/parse.c +++ b/parse.c @@ -8,5 +8,5 @@ Ast_Node* parse_expression(char* expr) { // initial state is code Parser_State current_parser_state = Parser_State_Code; - + return NULL; } diff --git a/testing.c b/testing.c new file mode 100644 index 0000000..9942060 --- /dev/null +++ b/testing.c @@ -0,0 +1,259 @@ +#define console_normal "\x1B[0m" +#define console_red "\x1B[31m" +#define console_green "\x1B[32m" + +#define epsilon 2.2204460492503131E-16 + +#define testresult int +#define pass 1 +#define fail 0 + +#define print_assert_equal_fail(variable, value, type, format) \ + printf("\n\tAssertion failed for '" #variable "'" \ + "\n\t in %s:%d" \ + "\n\texpected: " format \ + "\n\tgot: " format "\n", \ + __FILE__, __LINE__, (type)value, (type)variable) + +#define print_assert_not_equal_fail(variable, value, type, format) \ + printf("\n\tAssertion failed for '" #variable "'" \ + "\n\t in %s:%d" \ + "\n\texpected not: " format \ + "\n\tgot anyways: " format "\n", \ + __FILE__, __LINE__, (type)value, (type)variable) + +#define assert_no_error(error) \ + if (error) { \ + print_assert_not_equal_fail(error, 0, int, "%d"); \ + printf("\t%s%s%s\n", console_red, error->message,console_normal); \ + return fail; \ + } \ + +#define assert_equal_int(variable, value) \ + if (variable != value) { \ + print_assert_equal_fail(variable, value, int, "%d"); \ + return fail; \ + } + +#define assert_not_equal_int(variable, value) \ + if (variable == value) { \ + print_assert_not_equal_fail(variable, value, int, "%d"); \ + return fail; \ + } + +#define assert_equal_double(variable, value) \ + if (fabs((double)variable - (double)value) > epsilon) { \ + print_assert_equal_fail(variable, value, double, "%f"); \ + return fail; \ + } + +#define assert_not_equal_double(variable, value) \ + if (fabs((double)variable - (double)value) <= epsilon) { \ + print_assert_not_equal_fail(variable, value, double, "%f"); \ + return fail; \ + } + +#define assert_null(variable) \ + assert_equal_int(variable, NULL) + +#define assert_not_null(variable) \ + assert_not_equal_int(variable, NULL) + + +#define invoke_test(name) \ + printf("" #name ":"); \ + for(int i = 0; i < 45 - strlen(#name); ++i) \ + printf(" "); \ + if (name() == pass) \ + printf("%spassed%s\n", console_green, console_normal); \ + else { \ + printf("%sfailed%s\n", console_red, console_normal); \ + if(error) { \ + log_error("Error while running test" #name); \ + free(error); \ + error = NULL; \ + } \ + } \ + + +testresult test_built_in_add() { + Ast_Node* plus = create_ast_node_symbol("+"); + Ast_Node* ten = create_ast_node_number(10); + Ast_Node* four = create_ast_node_number(4); + Ast_Node* nil = create_ast_node_nil(); + Ast_Node* form = create_ast_node_pair( + plus, + create_ast_node_pair( + ten, + create_ast_node_pair( + four, + nil))); + + Ast_Node* result = eval_expr(form, new(Environment)); + + assert_no_error(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 14); + + return pass; +} + +testresult test_built_in_substract() { + Ast_Node* minus = create_ast_node_symbol("-"); + Ast_Node* ten = create_ast_node_number(10); + Ast_Node* four = create_ast_node_number(4); + Ast_Node* nil = create_ast_node_nil(); + Ast_Node* form = create_ast_node_pair( + minus, + create_ast_node_pair( + ten, + create_ast_node_pair( + four, + nil))); + + Ast_Node* result = eval_expr(form, new(Environment)); + + assert_no_error(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 6); + + return pass; +} + + +testresult test_built_in_multiply() { + Ast_Node* times = create_ast_node_symbol("*"); + Ast_Node* ten = create_ast_node_number(10); + Ast_Node* four = create_ast_node_number(4); + Ast_Node* nil = create_ast_node_nil(); + Ast_Node* form = create_ast_node_pair( + times, + create_ast_node_pair( + ten, + create_ast_node_pair( + four, + nil))); + + Ast_Node* result = eval_expr(form, new(Environment)); + + assert_no_error(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 40); + + return pass; +} + + +testresult test_built_in_divide() { + Ast_Node* over = create_ast_node_symbol("/"); + Ast_Node* ten = create_ast_node_number(20); + Ast_Node* four = create_ast_node_number(4); + Ast_Node* nil = create_ast_node_nil(); + Ast_Node* form = create_ast_node_pair( + over, + create_ast_node_pair( + ten, + create_ast_node_pair( + four, + nil))); + + Ast_Node* result = eval_expr(form, new(Environment)); + + assert_null(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 5); + + return pass; +} + + +testresult test_built_in_if() { + Ast_Node* _if = create_ast_node_symbol("if"); + Ast_Node* cond = create_ast_node_number(1); + Ast_Node* four = create_ast_node_number(4); + Ast_Node* five = create_ast_node_number(5); + Ast_Node* nil = create_ast_node_nil(); + Ast_Node* form = create_ast_node_pair( + _if, + create_ast_node_pair( + cond, + create_ast_node_pair( + four, + create_ast_node_pair( + five, + nil)))); + Ast_Node* result; + + // test *then* case + result = eval_expr(form, new(Environment)); + + assert_no_error(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 4); + + // test *else* case + cond->value.number->value = 0; + result = eval_expr(form, new(Environment)); + + assert_null(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 5); + + return pass; +} + +testresult test_built_in_and() { + Ast_Node* _and = create_ast_node_symbol("and"); + Ast_Node* cond1 = create_ast_node_number(1); + Ast_Node* cond2 = create_ast_node_string("asd"); + Ast_Node* cond3 = create_ast_node_number(4); + Ast_Node* nil = create_ast_node_nil(); + Ast_Node* form = create_ast_node_pair( + _and, + create_ast_node_pair( + cond1, + create_ast_node_pair( + cond2, + create_ast_node_pair( + cond3, + nil)))); + Ast_Node* result; + + // a true case + result = eval_expr(form, new(Environment)); + + assert_no_error(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 1); + + // a false case + cond1->value.number->value = 0; + result = eval_expr(form, new(Environment)); + + assert_no_error(error); + assert_not_null(result); + assert_equal_int(result->type, Ast_Node_Type_Number); + assert_equal_double(result->value.number->value, 0); + + return pass; +} + + +void run_all_tests() { + log_level = Log_Level_None; + + invoke_test(test_built_in_add); + invoke_test(test_built_in_substract); + invoke_test(test_built_in_multiply); + invoke_test(test_built_in_divide); + invoke_test(test_built_in_if); + invoke_test(test_built_in_and); + +} diff --git a/todo.org b/todo.org index f29294b..177e31a 100644 --- a/todo.org +++ b/todo.org @@ -1,35 +1,41 @@ * Build-in forms - - + - - - - - * - - / - - > - - < - - = +** 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] +** TODO > +** TODO < +** TODO = + +** DONE if + CLOSED: [2018-09-18 Di 12:14] +** TODO and +** TODO or +** TODO not + +** TODO first (car) +** TODO rest (cdr) +** TODO pair (cons) + +** TODO load (import) +** TODO define + +** TODO lambda +** TODO progn + +** TODO eval +** TODO quote + +** TODO print +** TODO read + +** TODO help - - if - - and - - or - - not - - - first (car) - - rest (cdr) - - pair (cons) - - - load (import) - - define - - - lambda - - progn - - - eval - - quote - - - print - - read - - - help * Types - Symbol - Number