diff --git a/doc/stdlib.md b/doc/stdlib.md index 2a356e8..fdce380 100644 --- a/doc/stdlib.md +++ b/doc/stdlib.md @@ -6,5 +6,6 @@ categories: - [Arithmetic](stdlib_arithmetic.md) - [Boolean Logic](stdlib_boolean.md) - [Comparison](stdlib_comparison.md) +- [Data Structures](stdlib_data.md) - [Miscellaneous Functions](stdlib_misc.md) - [Keithlisp Internals](stdlib_internals.md) diff --git a/doc/stdlib_data.md b/doc/stdlib_data.md new file mode 100644 index 0000000..5be753b --- /dev/null +++ b/doc/stdlib_data.md @@ -0,0 +1,47 @@ +# Data Structures +These functions operate on various data structures in Keithlisp. + +## `cons` +``` +(cons car cdr) +``` +`cons` allocates and returns a new cons with the given `car` and `cdr`. + +## `car` +``` +(car cons) +``` +`car` returns the `car` portion of `cons`, or nil if the argument is not +a cons. + +## `cdr` +``` +(cdr cons) +``` +`cdr` returns the `cdr` portion of `cons`, or nil if the argument is not +a cons. + +## `list` +``` +(list &rest elements) +``` +`list` allocates and returns a new list containing `elements`. + +## `length` +``` +(length list) +``` +`length` returns the length of `list`, or nil if it is not a valid list. +If `list` is circular, `length` may never return. + +## `nth` +``` +(nth index list) +``` +Returns the `index`th element in `list`. `index` must be an int. + +## `nthcdr` +``` +(nthcdr index list) +``` +Returns the cdr of the `index`th cons in `list`. `index` must be an int. diff --git a/doc/stdlib_misc.md b/doc/stdlib_misc.md index fd76fe1..cb21e59 100644 --- a/doc/stdlib_misc.md +++ b/doc/stdlib_misc.md @@ -24,9 +24,23 @@ interpreted as a signed 2's-complement integer. creates the association if it does not already exist. It then returns `value`. +## `unset!` +``` +(unset! atom) +``` +`unset` deletes the topmost entry for `atom` in the syms-alist, and +returns the value `atom` previously held. + ## `fun` ``` (fun atom) ``` `fun` returns the function associated with `atom` in the funs-alist, or nil if no such function exists. + +## `print` +``` +(print &rest values) +``` +`print` outputs each of its arguments to standard output, separated by +spaces and followed by a newline. diff --git a/lisp_types.h b/lisp_types.h index 498d303..4125d74 100644 --- a/lisp_types.h +++ b/lisp_types.h @@ -1,6 +1,8 @@ #ifndef _LISP_TYPES_H #define _LISP_TYPES_H +#include + #define LISP_T_CONS 0 #define LISP_T_ATOM 1 #define LISP_T_INT 2 @@ -24,6 +26,7 @@ struct lisp_value { lisp_native_fun funptr; } value; char type; + bool gc; } __attribute__((packed)); struct lisp_cons { diff --git a/main.c b/main.c index 522bcc5..7a5a925 100644 --- a/main.c +++ b/main.c @@ -134,11 +134,11 @@ lisp_cons* lisp_alist_del(lisp_cons** alist, lisp_value key) { return NULL; } -void test_print_value(lisp_value value) { +void print_value(lisp_value value) { if (lisp_is_nil(value)) printf("nil"); else if (value.type == LISP_T_CONS) - test_print_cons(value.value.cons); + print_cons(value.value.cons); else if (value.type == LISP_T_ATOM) { #ifdef LISP_USE_ATOMS_ALIST lisp_cons* pair = lisp_alist_get(atoms_alist, value); @@ -158,17 +158,17 @@ void test_print_value(lisp_value value) { } else if (value.type == LISP_T_FUNPTR) printf("", value.value.funptr); } -void test_print_cons(lisp_cons* cons) { +void print_cons(lisp_cons* cons) { printf("("); while (cons != NULL) { - test_print_value(cons->car); + print_value(cons->car); if (cons->cdr.type == LISP_T_CONS) { cons = cons->cdr.value.cons; if (cons != NULL) printf(" "); } else { printf(" . "); - test_print_value(cons->cdr); + print_value(cons->cdr); cons = NULL; } } @@ -372,42 +372,43 @@ int main() { init_native_funs(); - printf("Init: %4liB used\n", dbg_malloc_mem_usage); + size_t init_mem_usage = dbg_malloc_mem_usage; + printf("Init: %4liB used\n", init_mem_usage); while (true) { lisp_cons* tokens = NULL; lisp_value value; - printf("> "); + printf("%4liB> ", dbg_malloc_mem_usage-init_mem_usage); lisp_tokenize_init(); if (lisp_parse_recursive(&tokens, &fetch_tokens, &value)) { //test_print_value(value); //printf("\n"); lisp_value result; lisp_evaluate_value(value, &result); - test_print_value(result); + print_value(result); printf("\n"); - printf("Freeing sexpr...\n"); + //printf("Freeing sexpr...\n"); if (value.type == LISP_T_CONS) recursive_free(value.value.cons); else if (value.type == LISP_T_STRING) dbg_free(value.value.string); - printf("Freeing tokens...\n"); + //printf("Freeing tokens...\n"); recursive_free(tokens); if (lisp_is_nil(value)) break; } else { printf("PARSE ERROR!\n"); - test_print_cons(tokens); + print_cons(tokens); printf("\n"); } } - printf("Freeing syms-alist...\n"); + //printf("Freeing syms-alist...\n"); recursive_free(syms_alist); - printf("Freeing funs-alist...\n"); + //printf("Freeing funs-alist...\n"); recursive_free(funs_alist); - printf("Freeing atoms-alist...\n"); + //printf("Freeing atoms-alist...\n"); recursive_free(atoms_alist); printf("Done: %4liB used (should be 0)\n", dbg_malloc_mem_usage); printf("Peak: %4liB used\n", dbg_malloc_peak_usage); diff --git a/main.h b/main.h index 01bbcf8..6b286c9 100644 --- a/main.h +++ b/main.h @@ -50,8 +50,8 @@ lisp_cons* lisp_alist_get(lisp_cons* alist, lisp_value key); lisp_cons* lisp_alist_put(lisp_cons** alist, lisp_cons* pair); lisp_cons* lisp_alist_del(lisp_cons** alist, lisp_value key); -void test_print_cons(lisp_cons* cons); -void test_print_value(lisp_value value); +void print_cons(lisp_cons* cons); +void print_value(lisp_value value); lisp_atom lisp_defun_native(lisp_string* lstr, lisp_native_fun funptr); diff --git a/native_funs.c b/native_funs.c index 19298d6..6af2e5f 100644 --- a/native_funs.c +++ b/native_funs.c @@ -1,6 +1,6 @@ #include "native_funs.h" -void lisp_add(lisp_cons* cons, lisp_value* value) { +void lispf_add(lisp_cons* cons, lisp_value* value) { long _int = 0; float _float = 0.0f; bool is_float = false; @@ -29,7 +29,7 @@ void lisp_add(lisp_cons* cons, lisp_value* value) { value->value._int = _int; } } -void lisp_sub(lisp_cons* cons, lisp_value* value) { +void lispf_sub(lisp_cons* cons, lisp_value* value) { long _int = 0; float _float = 0.0f; bool is_float = false; @@ -82,7 +82,7 @@ void lisp_sub(lisp_cons* cons, lisp_value* value) { value->value._int = _int; } } -void lisp_mul(lisp_cons* cons, lisp_value* value) { +void lispf_mul(lisp_cons* cons, lisp_value* value) { long _int = 1; float _float = 1; bool is_float = false; @@ -111,7 +111,7 @@ void lisp_mul(lisp_cons* cons, lisp_value* value) { value->value._int = _int; } } -void lisp_div(lisp_cons* cons, lisp_value* value) { +void lispf_div(lisp_cons* cons, lisp_value* value) { float _float = 0; if (cons->car.type == LISP_T_FLOAT) @@ -135,7 +135,7 @@ void lisp_div(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_FLOAT; value->value._float = _float; } -void lisp_intdiv(lisp_cons* cons, lisp_value* value) { +void lispf_intdiv(lisp_cons* cons, lisp_value* value) { long _int = 0; if (cons->car.type == LISP_T_FLOAT) @@ -160,7 +160,7 @@ void lisp_intdiv(lisp_cons* cons, lisp_value* value) { value->value._int = _int; } -void lisp_not(lisp_cons* cons, lisp_value* value) { +void lispf_not(lisp_cons* cons, lisp_value* value) { if (lisp_is_nil(cons->car)) { value->type = LISP_T_ATOM; value->value.atom = atom_t; @@ -169,7 +169,7 @@ void lisp_not(lisp_cons* cons, lisp_value* value) { value->value.cons = NULL; } } -void lisp_or(lisp_cons* cons, lisp_value* value) { +void lispf_or(lisp_cons* cons, lisp_value* value) { while (cons != NULL) { if (!lisp_is_nil(cons->car)) { *value = cons->car; @@ -180,7 +180,7 @@ void lisp_or(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_CONS; value->value.cons = NULL; } -void lisp_and(lisp_cons* cons, lisp_value* value) { +void lispf_and(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_CONS; value->value.cons = NULL; while (cons != NULL) { @@ -191,7 +191,7 @@ void lisp_and(lisp_cons* cons, lisp_value* value) { } } -void lisp_eq(lisp_cons* cons, lisp_value* value) { +void lispf_eq(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car; cons = cons->cdr.value.cons; while (cons != NULL) { @@ -205,7 +205,7 @@ void lisp_eq(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_ATOM; value->value.atom = atom_t; } -void lisp_num_eq(lisp_cons* cons, lisp_value* value) { +void lispf_num_eq(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car; if (cmp_value.type != LISP_T_INT && cmp_value.type != LISP_T_FLOAT) { value->type = LISP_T_CONS; @@ -235,7 +235,7 @@ void lisp_num_eq(lisp_cons* cons, lisp_value* value) { } *value = cmp_value; } -void lisp_num_lt(lisp_cons* cons, lisp_value* value) { +void lispf_num_lt(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car; if (cmp_value.type != LISP_T_INT && cmp_value.type != LISP_T_FLOAT) { value->type = LISP_T_CONS; @@ -266,7 +266,7 @@ void lisp_num_lt(lisp_cons* cons, lisp_value* value) { } *value = cmp_value; } -void lisp_num_gt(lisp_cons* cons, lisp_value* value) { +void lispf_num_gt(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car; if (cmp_value.type != LISP_T_INT && cmp_value.type != LISP_T_FLOAT) { value->type = LISP_T_CONS; @@ -297,7 +297,7 @@ void lisp_num_gt(lisp_cons* cons, lisp_value* value) { } *value = cmp_value; } -void lisp_num_le(lisp_cons* cons, lisp_value* value) { +void lispf_num_le(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car; if (cmp_value.type != LISP_T_INT && cmp_value.type != LISP_T_FLOAT) { value->type = LISP_T_CONS; @@ -328,7 +328,7 @@ void lisp_num_le(lisp_cons* cons, lisp_value* value) { } *value = cmp_value; } -void lisp_num_ge(lisp_cons* cons, lisp_value* value) { +void lispf_num_ge(lisp_cons* cons, lisp_value* value) { lisp_value cmp_value = cons->car; if (cmp_value.type != LISP_T_INT && cmp_value.type != LISP_T_FLOAT) { value->type = LISP_T_CONS; @@ -360,10 +360,87 @@ void lisp_num_ge(lisp_cons* cons, lisp_value* value) { *value = cmp_value; } -void lisp_type_of(lisp_cons* cons, lisp_value* value) { +void lispf_cons(lisp_cons* cons, lisp_value* value) { + value->type = LISP_T_CONS; + value->value.cons = dbg_malloc(sizeof(lisp_cons)); + value->value.cons->car = cons->car; + value->value.cons->cdr = cons->cdr.value.cons->car; +} +void lispf_car(lisp_cons* cons, lisp_value* value) { + if (cons->car.type != LISP_T_CONS || lisp_is_nil(cons->car)) + return; + *value = cons->car.value.cons->car; +} +void lispf_cdr(lisp_cons* cons, lisp_value* value) { + if (cons->car.type != LISP_T_CONS || lisp_is_nil(cons->car)) + return; + *value = cons->car.value.cons->cdr; +} + +void lispf_list(lisp_cons* cons, lisp_value* value) { + lisp_cons* head = NULL; + lisp_cons* tail = NULL; + while (cons != NULL) { + lisp_cons* element = dbg_malloc(sizeof(lisp_cons)); + element->car = cons->car; + element->cdr.type = LISP_T_CONS; + element->cdr.value.cons = NULL; + + if (head == NULL) + head = element; + if (tail != NULL) + tail->cdr.value.cons = element; + + tail = element; + + cons = cons->cdr.value.cons; + } + value->type = LISP_T_CONS; + value->value.cons = head; +} +void lispf_length(lisp_cons* cons, lisp_value* value) { + if (cons->car.type != LISP_T_CONS) + return; + lisp_cons* head = cons->car.value.cons; + value->type = LISP_T_INT; + value->value._int = 0; + while (head != NULL) { + value->value._int++; + if (head->cdr.type != LISP_T_CONS) + head = NULL; + else + head = head->cdr.value.cons; + } +} +void lispf_nth(lisp_cons* cons, lisp_value* value) { + if (cons->car.type != LISP_T_INT) + return; + int index = cons->car.value._int; + lisp_cons* head = cons->cdr.value.cons->car.value.cons; + while (index > 0) { + if (head == NULL) + return; + head = head->cdr.value.cons; + index--; + } + *value = head->car; +} +void lispf_nthcdr(lisp_cons* cons, lisp_value* value) { + if (cons->car.type != LISP_T_INT) + return; + int index = cons->car.value._int; + lisp_cons* head = cons->cdr.value.cons->car.value.cons; + while (index > 0) { + if (head == NULL) + return; + head = head->cdr.value.cons; + index--; + } + *value = head->cdr; +} + +void lispf_type_of(lisp_cons* cons, lisp_value* value) { if (lisp_is_nil(cons->car)) { - value->type = LISP_T_CONS; - value->value.cons = NULL; return; } value->type = LISP_T_ATOM; @@ -389,12 +466,12 @@ void lisp_type_of(lisp_cons* cons, lisp_value* value) { } } -void lisp_addr_of(lisp_cons* cons, lisp_value* value) { +void lispf_addr_of(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_INT; value->value._int = (long) cons->car.value.cons; } -void lisp_set(lisp_cons* cons, lisp_value* value) { +void lispf_set(lisp_cons* cons, lisp_value* value) { // extract key and value lisp_value key = cons->car; *value = cons->cdr.value.cons->car; @@ -415,61 +492,90 @@ void lisp_set(lisp_cons* cons, lisp_value* value) { new_alist->cdr.type = LISP_T_CONS; new_alist->cdr.value.cons = syms_alist; syms_alist = new_alist; - return; } -void lisp_fun(lisp_cons* cons, lisp_value* value) { - lisp_cons* pair = lisp_alist_get(funs_alist, cons->car); - if (pair == NULL) { - value->type = LISP_T_CONS; - value->value.cons = NULL; +void lispf_unset(lisp_cons* cons, lisp_value* value) { + lisp_cons* pair = lisp_alist_del(&syms_alist, cons->car); + if (pair == NULL) + return; + *value = pair->cdr; + dbg_free(pair); +} + +void lispf_fun(lisp_cons* cons, lisp_value* value) { + lisp_cons* pair = lisp_alist_get(funs_alist, cons->car); + if (pair == NULL) return; - } *value = pair->cdr; } -void lisp_internals_syms_alist(lisp_cons* cons, lisp_value* value) { +void lispf_print(lisp_cons* cons, lisp_value* value) { + while (cons != NULL) { + if (cons->car.type == LISP_T_STRING) + lisp_string_print(cons->car.value.string); + else + print_value(cons->car); + cons = cons->cdr.value.cons; + if (cons != NULL) + printf(" "); + } + printf("\n"); +} + +void lispf_internals_syms_alist(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_CONS; value->value.cons = syms_alist; } -void lisp_internals_funs_alist(lisp_cons* cons, lisp_value* value) { +void lispf_internals_funs_alist(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_CONS; value->value.cons = funs_alist; } -void lisp_internals_atoms_alist(lisp_cons* cons, lisp_value* value) { +void lispf_internals_atoms_alist(lisp_cons* cons, lisp_value* value) { value->type = LISP_T_CONS; value->value.cons = atoms_alist; } void init_native_funs() { // arithmetic - lisp_defun_native(lisp_string_create("+"), &lisp_add); - lisp_defun_native(lisp_string_create("-"), &lisp_sub); - lisp_defun_native(lisp_string_create("*"), &lisp_mul); - lisp_defun_native(lisp_string_create("/"), &lisp_div); - lisp_defun_native(lisp_string_create("int/"), &lisp_intdiv); + lisp_defun_native(lisp_string_create("+"), &lispf_add); + lisp_defun_native(lisp_string_create("-"), &lispf_sub); + lisp_defun_native(lisp_string_create("*"), &lispf_mul); + lisp_defun_native(lisp_string_create("/"), &lispf_div); + lisp_defun_native(lisp_string_create("int/"), &lispf_intdiv); // boolean logic - lisp_defun_native(lisp_string_create("not"), &lisp_not); - lisp_defun_native(lisp_string_create("or"), &lisp_or); - lisp_defun_native(lisp_string_create("and"), &lisp_and); + lisp_defun_native(lisp_string_create("not"), &lispf_not); + lisp_defun_native(lisp_string_create("or"), &lispf_or); + lisp_defun_native(lisp_string_create("and"), &lispf_and); // comparison - lisp_defun_native(lisp_string_create("eq"), &lisp_eq); - lisp_defun_native(lisp_string_create("="), &lisp_num_eq); - lisp_defun_native(lisp_string_create("<"), &lisp_num_lt); - lisp_defun_native(lisp_string_create(">"), &lisp_num_gt); - lisp_defun_native(lisp_string_create("<="), &lisp_num_le); - lisp_defun_native(lisp_string_create(">="), &lisp_num_ge); + lisp_defun_native(lisp_string_create("eq"), &lispf_eq); + lisp_defun_native(lisp_string_create("="), &lispf_num_eq); + lisp_defun_native(lisp_string_create("<"), &lispf_num_lt); + lisp_defun_native(lisp_string_create(">"), &lispf_num_gt); + lisp_defun_native(lisp_string_create("<="), &lispf_num_le); + lisp_defun_native(lisp_string_create(">="), &lispf_num_ge); + + // data structures + lisp_defun_native(lisp_string_create("cons"), &lispf_cons); + lisp_defun_native(lisp_string_create("car"), &lispf_car); + lisp_defun_native(lisp_string_create("cdr"), &lispf_cdr); + + lisp_defun_native(lisp_string_create("list"), &lispf_list); + lisp_defun_native(lisp_string_create("length"), &lispf_length); + lisp_defun_native(lisp_string_create("nth"), &lispf_nth); + lisp_defun_native(lisp_string_create("nthcdr"), &lispf_nthcdr); // etc - lisp_defun_native(lisp_string_create("type-of"), &lisp_type_of); - lisp_defun_native(lisp_string_create("addr-of"), &lisp_addr_of); - lisp_defun_native(lisp_string_create("set"), &lisp_set); - lisp_defun_native(lisp_string_create("fun"), &lisp_fun); + lisp_defun_native(lisp_string_create("type-of"), &lispf_type_of); + lisp_defun_native(lisp_string_create("addr-of"), &lispf_addr_of); + lisp_defun_native(lisp_string_create("set"), &lispf_set); + lisp_defun_native(lisp_string_create("unset!"), &lispf_unset); + lisp_defun_native(lisp_string_create("fun"), &lispf_fun); + lisp_defun_native(lisp_string_create("print"), &lispf_print); // internals - lisp_defun_native(lisp_string_create("syms-alist!"), &lisp_internals_syms_alist); - lisp_defun_native(lisp_string_create("funs-alist!"), &lisp_internals_funs_alist); - lisp_defun_native(lisp_string_create("atoms-alist!"), &lisp_internals_atoms_alist); + lisp_defun_native(lisp_string_create("syms-alist!"), &lispf_internals_syms_alist); + lisp_defun_native(lisp_string_create("funs-alist!"), &lispf_internals_funs_alist); + lisp_defun_native(lisp_string_create("atoms-alist!"), &lispf_internals_atoms_alist); } diff --git a/native_funs.h b/native_funs.h index 204b45f..bcfa27f 100644 --- a/native_funs.h +++ b/native_funs.h @@ -8,31 +8,41 @@ #include "lisp_string.h" #include "main.h" -void lisp_add(lisp_cons* cons, lisp_value* value); -void lisp_sub(lisp_cons* cons, lisp_value* value); -void lisp_mul(lisp_cons* cons, lisp_value* value); -void lisp_div(lisp_cons* cons, lisp_value* value); -void lisp_intdiv(lisp_cons* cons, lisp_value* value); +void lispf_add(lisp_cons* cons, lisp_value* value); +void lispf_sub(lisp_cons* cons, lisp_value* value); +void lispf_mul(lisp_cons* cons, lisp_value* value); +void lispf_div(lisp_cons* cons, lisp_value* value); +void lispf_intdiv(lisp_cons* cons, lisp_value* value); -void lisp_not(lisp_cons* cons, lisp_value* value); -void lisp_or(lisp_cons* cons, lisp_value* value); -void lisp_and(lisp_cons* cons, lisp_value* value); +void lispf_not(lisp_cons* cons, lisp_value* value); +void lispf_or(lisp_cons* cons, lisp_value* value); +void lispf_and(lisp_cons* cons, lisp_value* value); -void lisp_eq(lisp_cons* cons, lisp_value* value); -void lisp_num_eq(lisp_cons* cons, lisp_value* value); -void lisp_num_lt(lisp_cons* cons, lisp_value* value); -void lisp_num_gt(lisp_cons* cons, lisp_value* value); -void lisp_num_le(lisp_cons* cons, lisp_value* value); -void lisp_num_ge(lisp_cons* cons, lisp_value* value); +void lispf_eq(lisp_cons* cons, lisp_value* value); +void lispf_num_eq(lisp_cons* cons, lisp_value* value); +void lispf_num_lt(lisp_cons* cons, lisp_value* value); +void lispf_num_gt(lisp_cons* cons, lisp_value* value); +void lispf_num_le(lisp_cons* cons, lisp_value* value); +void lispf_num_ge(lisp_cons* cons, lisp_value* value); -void lisp_type_of(lisp_cons* cons, lisp_value* value); -void lisp_addr_of(lisp_cons* cons, lisp_value* value); -void lisp_set(lisp_cons* cons, lisp_value* value); -void lisp_fun(lisp_cons* cons, lisp_value* value); +void lispf_cons(lisp_cons* cons, lisp_value* value); +void lispf_car(lisp_cons* cons, lisp_value* value); +void lispf_cdr(lisp_cons* cons, lisp_value* value); -void lisp_internals_syms_alist(lisp_cons* cons, lisp_value* value); -void lisp_internals_funs_alist(lisp_cons* cons, lisp_value* value); -void lisp_internals_atoms_alist(lisp_cons* cons, lisp_value* value); +void lispf_list(lisp_cons* cons, lisp_value* value); +void lispf_length(lisp_cons* cons, lisp_value* value); +void lispf_nth(lisp_cons* cons, lisp_value* value); +void lispf_nthcdr(lisp_cons* cons, lisp_value* value); + +void lispf_type_of(lisp_cons* cons, lisp_value* value); +void lispf_addr_of(lisp_cons* cons, lisp_value* value); +void lispf_set(lisp_cons* cons, lisp_value* value); +void lispf_fun(lisp_cons* cons, lisp_value* value); +void lispf_print(lisp_cons* cons, lisp_value* value); + +void lispf_internals_syms_alist(lisp_cons* cons, lisp_value* value); +void lispf_internals_funs_alist(lisp_cons* cons, lisp_value* value); +void lispf_internals_atoms_alist(lisp_cons* cons, lisp_value* value); void init_native_funs(); #endif