added error types and stuff
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
((nil . ((eval . (flycheck-mode 0))
|
((c-mode . ((eval . (flycheck-mode 0))
|
||||||
(eval . (rainbow-mode 0)))))
|
(eval . (rainbow-mode 0)))))
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ typedef enum {
|
|||||||
Ast_Node_Type_Number,
|
Ast_Node_Type_Number,
|
||||||
Ast_Node_Type_String,
|
Ast_Node_Type_String,
|
||||||
|
|
||||||
Ast_Node_Type_Cons_Cell,
|
Ast_Node_Type_Pair,
|
||||||
Ast_Node_Type_Function,
|
Ast_Node_Type_Function,
|
||||||
Ast_Node_Type_Built_In_Function,
|
Ast_Node_Type_Built_In_Function,
|
||||||
} Ast_Node_Type;
|
} Ast_Node_Type;
|
||||||
@@ -32,16 +32,16 @@ typedef struct {
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
struct Ast_Node* first;
|
struct Ast_Node* first;
|
||||||
struct Ast_Node* rest;
|
struct Ast_Node* rest;
|
||||||
} Cons_Cell;
|
} Pair;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Cons_Cell* regular_params;
|
Pair* regular_params;
|
||||||
Cons_Cell* keyword_params;
|
Pair* keyword_params;
|
||||||
} Lambda_List;
|
} Lambda_List;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
Lambda_List* parameters;
|
Lambda_List* parameters;
|
||||||
Cons_Cell* form;
|
Pair* form;
|
||||||
} Function;
|
} Function;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -55,7 +55,7 @@ struct Ast_Node {
|
|||||||
Keyword* keyword;
|
Keyword* keyword;
|
||||||
Number* number;
|
Number* number;
|
||||||
String* string;
|
String* string;
|
||||||
Cons_Cell* cons_cell;
|
Pair* pair;
|
||||||
Function* function;
|
Function* function;
|
||||||
Built_In_Function* built_in_function;
|
Built_In_Function* built_in_function;
|
||||||
} value;
|
} value;
|
||||||
@@ -67,7 +67,7 @@ typedef struct Ast_Node Ast_Node;
|
|||||||
Ast_Node* create_ast_node_nil() {
|
Ast_Node* create_ast_node_nil() {
|
||||||
Ast_Node* node = new(Ast_Node);
|
Ast_Node* node = new(Ast_Node);
|
||||||
node->type = Ast_Node_Type_Nil;
|
node->type = Ast_Node_Type_Nil;
|
||||||
node->value.cons_cell = NULL;
|
node->value.pair = NULL;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,9 +106,9 @@ Ast_Node* create_ast_node_built_in_function(char* identifier) {
|
|||||||
|
|
||||||
Ast_Node* create_ast_node_pair(Ast_Node* first, Ast_Node* rest) {
|
Ast_Node* create_ast_node_pair(Ast_Node* first, Ast_Node* rest) {
|
||||||
Ast_Node* node = new(Ast_Node);
|
Ast_Node* node = new(Ast_Node);
|
||||||
node->type = Ast_Node_Type_Cons_Cell;
|
node->type = Ast_Node_Type_Pair;
|
||||||
node->value.cons_cell = new(Cons_Cell);
|
node->value.pair = new(Pair);
|
||||||
node->value.cons_cell->first = first;
|
node->value.pair->first = first;
|
||||||
node->value.cons_cell->rest = rest;
|
node->value.pair->rest = rest;
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -1,52 +1,52 @@
|
|||||||
Ast_Node* built_in_add(Ast_Node* operands) {
|
Ast_Node* built_in_add(Ast_Node* operands) {
|
||||||
double sum = 0;
|
double sum = 0;
|
||||||
while (operands->type == Ast_Node_Type_Cons_Cell) {
|
while (operands->type == Ast_Node_Type_Pair) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
sum += operands->value.cons_cell->first->value.number->value;
|
sum += operands->value.pair->first->value.number->value;
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
return create_ast_node_number(sum);
|
return create_ast_node_number(sum);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ast_Node* built_in_substract(Ast_Node* operands) {
|
Ast_Node* built_in_substract(Ast_Node* operands) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
double difference = operands->value.cons_cell->first->value.number->value;
|
double difference = operands->value.pair->first->value.number->value;
|
||||||
|
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
while (operands->type == Ast_Node_Type_Cons_Cell) {
|
while (operands->type == Ast_Node_Type_Pair) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
|
|
||||||
difference -= operands->value.cons_cell->first->value.number->value;
|
difference -= operands->value.pair->first->value.number->value;
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
}
|
}
|
||||||
return create_ast_node_number(difference);
|
return create_ast_node_number(difference);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ast_Node* built_in_multiply(Ast_Node* operands) {
|
Ast_Node* built_in_multiply(Ast_Node* operands) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
double product = operands->value.cons_cell->first->value.number->value;
|
double product = operands->value.pair->first->value.number->value;
|
||||||
|
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
while (operands->type == Ast_Node_Type_Cons_Cell) {
|
while (operands->type == Ast_Node_Type_Pair) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
|
|
||||||
product *= operands->value.cons_cell->first->value.number->value;
|
product *= operands->value.pair->first->value.number->value;
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
}
|
}
|
||||||
return create_ast_node_number(product);
|
return create_ast_node_number(product);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ast_Node* built_in_divide(Ast_Node* operands) {
|
Ast_Node* built_in_divide(Ast_Node* operands) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
double quotient = operands->value.cons_cell->first->value.number->value;
|
double quotient = operands->value.pair->first->value.number->value;
|
||||||
|
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
while (operands->type == Ast_Node_Type_Cons_Cell) {
|
while (operands->type == Ast_Node_Type_Pair) {
|
||||||
assert_type(operands->value.cons_cell->first, Ast_Node_Type_Number);
|
assert_type(operands->value.pair->first, Ast_Node_Type_Number);
|
||||||
|
|
||||||
quotient /= operands->value.cons_cell->first->value.number->value;
|
quotient /= operands->value.pair->first->value.number->value;
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
}
|
}
|
||||||
return create_ast_node_number(quotient);
|
return create_ast_node_number(quotient);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,43 @@
|
|||||||
|
typedef enum {
|
||||||
|
Error_Type_Ill_Formed_List,
|
||||||
|
Error_Type_Wrong_Number_Of_Arguments,
|
||||||
|
Error_Type_Type_Missmatch,
|
||||||
|
Error_Type_Symbol_Not_Defined,
|
||||||
|
Error_Type_Not_A_Function,
|
||||||
|
Error_Type_Not_Yet_Implemented,
|
||||||
|
Error_Type_Unknown_Error,
|
||||||
|
} Error_Type;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* message;
|
Error_Type type;
|
||||||
Ast_Node* location;
|
Ast_Node* location;
|
||||||
} Error;
|
} Error;
|
||||||
|
|
||||||
Error* error;
|
Error* error;
|
||||||
|
|
||||||
|
void delete_error() {
|
||||||
|
if (error) {
|
||||||
|
free(error);
|
||||||
|
error = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void create_error(Error_Type type, Ast_Node* location) {
|
||||||
|
delete_error();
|
||||||
|
|
||||||
|
error = new(Error);
|
||||||
|
error->type = type;
|
||||||
|
error->location = 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_Unknown_Error: return "Unknown Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,24 +7,29 @@
|
|||||||
Ast_Node* eval_expr(Ast_Node* node, Environment* env);
|
Ast_Node* eval_expr(Ast_Node* node, Environment* env);
|
||||||
int is_truthy (Ast_Node* expression, Environment* env);
|
int is_truthy (Ast_Node* expression, Environment* env);
|
||||||
|
|
||||||
|
int list_length(Ast_Node* node) {
|
||||||
|
if (node->type != Ast_Node_Type_Pair) {
|
||||||
|
create_error(Error_Type_Type_Missmatch, node);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
void eval_operands(Ast_Node* operands, Environment* env) {
|
void eval_operands(Ast_Node* operands, Environment* env) {
|
||||||
while (!error) {
|
while (!error) {
|
||||||
if (operands->type == Ast_Node_Type_Cons_Cell) {
|
if (operands->type == Ast_Node_Type_Pair) {
|
||||||
operands->value.cons_cell->first = eval_expr(operands->value.cons_cell->first, env);
|
operands->value.pair->first = eval_expr(operands->value.pair->first, env);
|
||||||
operands = operands->value.cons_cell->rest;
|
operands = operands->value.pair->rest;
|
||||||
} else {
|
} else {
|
||||||
return;
|
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) {
|
Ast_Node* eval_expr(Ast_Node* node, Environment* env) {
|
||||||
#define report_error(_message) \
|
#define report_error(_type) \
|
||||||
error = new(Error); \
|
create_error(_type, node); \
|
||||||
error->message = _message; \
|
|
||||||
error->location = node; \
|
|
||||||
return NULL
|
return NULL
|
||||||
|
|
||||||
Ast_Node* ret = new(Ast_Node);
|
Ast_Node* ret = new(Ast_Node);
|
||||||
@@ -38,15 +43,15 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) {
|
|||||||
case Ast_Node_Type_String:
|
case Ast_Node_Type_String:
|
||||||
return node;
|
return node;
|
||||||
|
|
||||||
case Ast_Node_Type_Cons_Cell: {
|
case Ast_Node_Type_Pair: {
|
||||||
Ast_Node* operator = eval_expr(node->value.cons_cell->first, env);
|
Ast_Node* operator = eval_expr(node->value.pair->first, env);
|
||||||
Ast_Node* operands = node->value.cons_cell->rest;
|
Ast_Node* operands = node->value.pair->rest;
|
||||||
|
|
||||||
// check for built ins functions
|
// check for built ins functions
|
||||||
if (operator->type == Ast_Node_Type_Built_In_Function) {
|
if (operator->type == Ast_Node_Type_Built_In_Function) {
|
||||||
char* operator_name = operator->value.built_in_function->identifier;
|
char* operator_name = operator->value.built_in_function->identifier;
|
||||||
if (string_equal("quote", operator_name)) {
|
if (string_equal("quote", operator_name)) {
|
||||||
return node->value.cons_cell->rest;
|
return node->value.pair->rest;
|
||||||
} else if (string_equal("+", operator_name)) {
|
} else if (string_equal("+", operator_name)) {
|
||||||
eval_operands(operands, env);
|
eval_operands(operands, env);
|
||||||
return built_in_add(operands);
|
return built_in_add(operands);
|
||||||
@@ -60,25 +65,24 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) {
|
|||||||
eval_operands(operands, env);
|
eval_operands(operands, env);
|
||||||
return built_in_divide(operands);
|
return built_in_divide(operands);
|
||||||
} else if (string_equal("if", operator_name)) {
|
} else if (string_equal("if", operator_name)) {
|
||||||
if (Ast_Node_Type_Cons_Cell != operands->value.cons_cell->rest->type ||
|
if (list_length(operands) != 3) {
|
||||||
Ast_Node_Type_Cons_Cell != operands->value.cons_cell->rest->value.cons_cell->rest->type ||
|
report_error(Error_Type_Wrong_Number_Of_Arguments);
|
||||||
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* condition = operands->value.pair->first;
|
||||||
Ast_Node* else_part = then_part->value.cons_cell->rest;
|
Ast_Node* then_part = operands->value.pair->rest;
|
||||||
|
Ast_Node* else_part = then_part->value.pair->rest;
|
||||||
|
|
||||||
if (is_truthy(operands->value.cons_cell->first, env))
|
if (is_truthy(condition, env))
|
||||||
return eval_expr(then_part->value.cons_cell->first, env);
|
return eval_expr(then_part->value.pair->first, env);
|
||||||
else
|
else
|
||||||
return eval_expr(else_part->value.cons_cell->first, env);
|
return eval_expr(else_part->value.pair->first, env);
|
||||||
} else if (string_equal("not", operator_name)) {
|
/* } else if (string_equal("not", operator_name)) { */
|
||||||
} else if (string_equal("and", operator_name)) {
|
|
||||||
} else if (string_equal("or", operator_name)) {
|
/* } else if (string_equal("and", operator_name)) { */
|
||||||
|
/* } else if (string_equal("or", operator_name)) { */
|
||||||
} else {
|
} else {
|
||||||
report_error("The operator is not yet implemented");
|
report_error(Error_Type_Not_Yet_Implemented);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +92,7 @@ Ast_Node* eval_expr(Ast_Node* node, Environment* env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
report_error("Unknown error");
|
report_error(Error_Type_Unknown_Error);
|
||||||
}
|
}
|
||||||
#undef report_error
|
#undef report_error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
#define console_normal "\x1B[0m"
|
||||||
|
#define console_red "\x1B[31m"
|
||||||
|
#define console_green "\x1B[32m"
|
||||||
|
#define console_cyan "\x1B[36m"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
Log_Level_None,
|
Log_Level_None,
|
||||||
|
|
||||||
@@ -24,11 +29,6 @@ void log_message(Log_Level type, char* message) {
|
|||||||
printf("%s: %s\n",prefix, message);
|
printf("%s: %s\n",prefix, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void log_error(char* message) {
|
|
||||||
log_message(Log_Level_Critical, message);
|
|
||||||
log_message(Log_Level_Critical, error->message);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void panic(char* message) {
|
void panic(char* message) {
|
||||||
log_message(Log_Level_Critical, message);
|
log_message(Log_Level_Critical, message);
|
||||||
@@ -38,22 +38,14 @@ void panic(char* message) {
|
|||||||
|
|
||||||
char* Ast_Node_Type_to_string(Ast_Node_Type type) {
|
char* Ast_Node_Type_to_string(Ast_Node_Type type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case(Ast_Node_Type_Nil):
|
case(Ast_Node_Type_Nil): return "nil";
|
||||||
return "nil";
|
case(Ast_Node_Type_Number): return "number";
|
||||||
case(Ast_Node_Type_Number):
|
case(Ast_Node_Type_String): return "string";
|
||||||
return "number";
|
case(Ast_Node_Type_Symbol): return "symbol";
|
||||||
case(Ast_Node_Type_String):
|
case(Ast_Node_Type_Keyword): return "keyword";
|
||||||
return "string";
|
case(Ast_Node_Type_Function): return "function";
|
||||||
case(Ast_Node_Type_Symbol):
|
case(Ast_Node_Type_Built_In_Function): return "built-in function";
|
||||||
return "symbol";
|
case(Ast_Node_Type_Pair): return "pair";
|
||||||
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";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,14 +72,14 @@ void print(Ast_Node* node) {
|
|||||||
case (Ast_Node_Type_Built_In_Function): {
|
case (Ast_Node_Type_Built_In_Function): {
|
||||||
printf("[built-in-function %s]", node->value.built_in_function->identifier);
|
printf("[built-in-function %s]", node->value.built_in_function->identifier);
|
||||||
} break;
|
} break;
|
||||||
case (Ast_Node_Type_Cons_Cell): {
|
case (Ast_Node_Type_Pair): {
|
||||||
Ast_Node* head = node;
|
Ast_Node* head = node;
|
||||||
printf("(");
|
printf("(");
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
print(head->value.cons_cell->first);
|
print(head->value.pair->first);
|
||||||
head = head->value.cons_cell->rest;
|
head = head->value.pair->rest;
|
||||||
if (head->type != Ast_Node_Type_Cons_Cell)
|
if (head->type != Ast_Node_Type_Pair)
|
||||||
break;
|
break;
|
||||||
printf(" ");
|
printf(" ");
|
||||||
}
|
}
|
||||||
@@ -101,3 +93,13 @@ void print(Ast_Node* node) {
|
|||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void log_error() {
|
||||||
|
printf("\t%s%s%s\n", console_red,
|
||||||
|
Error_Type_to_string(error->type),
|
||||||
|
console_normal);
|
||||||
|
printf("\t in: %s", console_cyan);
|
||||||
|
print(error->location);
|
||||||
|
printf("%s\n", console_normal);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
#define console_normal "\x1B[0m"
|
|
||||||
#define console_red "\x1B[31m"
|
|
||||||
#define console_green "\x1B[32m"
|
|
||||||
|
|
||||||
#define epsilon 2.2204460492503131E-16
|
#define epsilon 2.2204460492503131E-16
|
||||||
|
|
||||||
#define testresult int
|
#define testresult int
|
||||||
@@ -22,10 +18,12 @@
|
|||||||
"\n\tgot anyways: " format "\n", \
|
"\n\tgot anyways: " format "\n", \
|
||||||
__FILE__, __LINE__, (type)value, (type)variable)
|
__FILE__, __LINE__, (type)value, (type)variable)
|
||||||
|
|
||||||
|
/* print_assert_not_equal_fail(error, 0, int, "%d"); \ */
|
||||||
#define assert_no_error(error) \
|
#define assert_no_error(error) \
|
||||||
if (error) { \
|
if (error) { \
|
||||||
print_assert_not_equal_fail(error, 0, int, "%d"); \
|
printf("\n\tExpected no error to occur," \
|
||||||
printf("\t%s%s%s\n", console_red, error->message,console_normal); \
|
" but an error occured anyways:\n"); \
|
||||||
|
log_error(); \
|
||||||
return fail; \
|
return fail; \
|
||||||
} \
|
} \
|
||||||
|
|
||||||
@@ -69,7 +67,6 @@
|
|||||||
else { \
|
else { \
|
||||||
printf("%sfailed%s\n", console_red, console_normal); \
|
printf("%sfailed%s\n", console_red, console_normal); \
|
||||||
if(error) { \
|
if(error) { \
|
||||||
log_error("Error while running test" #name); \
|
|
||||||
free(error); \
|
free(error); \
|
||||||
error = NULL; \
|
error = NULL; \
|
||||||
} \
|
} \
|
||||||
|
|||||||
@@ -14,9 +14,18 @@
|
|||||||
|
|
||||||
** DONE if
|
** DONE if
|
||||||
CLOSED: [2018-09-18 Di 12:14]
|
CLOSED: [2018-09-18 Di 12:14]
|
||||||
** TODO and
|
** TODO and (short circuiting)
|
||||||
** TODO or
|
** TODO or
|
||||||
** TODO not
|
** TODO not
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(not 1) ;; == (not . (1 . nil))
|
||||||
|
(not (expression 1 3)) ;; == (not . ((expression . (1 . (3 . nil))) . nil))
|
||||||
|
(not) ;; == (not . nil)
|
||||||
|
(not 1 2) ;; == (not . (1 . (2 . nil)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
#+RESULTS:
|
||||||
|
: t
|
||||||
|
|
||||||
** TODO first (car)
|
** TODO first (car)
|
||||||
** TODO rest (cdr)
|
** TODO rest (cdr)
|
||||||
|
|||||||
Reference in New Issue
Block a user