✨ bool arithmetic.
parent
ab34370e39
commit
f9996d5910
|
@ -25,7 +25,7 @@ int main(int argc, char** argv)
|
|||
|
||||
exec_module(&exec, &module);
|
||||
|
||||
if (!err_is_ok(&module.ccm.err)
|
||||
if (!err_is_ok(&module.ccm.err)
|
||||
|| !err_is_ok(&exec.err))
|
||||
{
|
||||
err_print_stack_trace(&module.ccm.err);
|
||||
|
@ -45,6 +45,6 @@ int main(int argc, char** argv)
|
|||
free_module:
|
||||
module_free(&module);
|
||||
}
|
||||
|
||||
|
||||
return status;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
MODULE ::= EXPR*
|
||||
EXPR ::=
|
||||
| TERM
|
||||
| OR
|
||||
| ASSERT
|
||||
ASSERT ::= (assert_eq|assert_ne) tuple
|
||||
OR ::= AND (or AND)*
|
||||
AND ::= TERM (and TERM)*
|
||||
TERM ::= FACTOR ((add|sub) FACTOR)*
|
||||
FACTOR ::= USUB ((mul|div|mod) USUB)*
|
||||
USUB ::= sub* POW
|
||||
USUB ::= sub* NOT
|
||||
NOT ::= not* POW
|
||||
POW ::= LITERAL (pow LITERAL)?
|
||||
LITERAL ::=
|
||||
| BUILTIN
|
||||
|
@ -13,4 +16,4 @@ LITERAL ::=
|
|||
| opar EXPR cpar
|
||||
TUPLE ::=
|
||||
| opar EXPR+ cpar
|
||||
BUILTIN ::= num
|
||||
BUILTIN ::= num | bool
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
G(OP_PUSH), G(OP_POP), \
|
||||
G(OP_ADD), G(OP_SUB), G(OP_USUB), G(OP_MUL), \
|
||||
G(OP_DIV), G(OP_POW), G(OP_MOD), G(OP_MK_TUPLE), \
|
||||
G(OP_ASSERT_EQ), G(OP_ASSERT_NE)
|
||||
G(OP_ASSERT_EQ), G(OP_ASSERT_NE), G(OP_BRF), G(OP_BR), \
|
||||
G(OP_NOT), G(OP_NOP)
|
||||
|
||||
CCM_ENUM_H(Opcode, OPCODES);
|
||||
|
||||
|
|
32
lib/ccm.c
32
lib/ccm.c
|
@ -99,6 +99,27 @@ CCM ccm_to_tuple(ccm_t* self, vec_t* values, int line)
|
|||
return self->values.size - 1;
|
||||
}
|
||||
|
||||
int ccm_is_boolean(ccm_t* self, CCM value)
|
||||
{
|
||||
assert(self);
|
||||
return ((value_t*) self->values.data[value])->type
|
||||
== TYPE_BOOLEAN;
|
||||
}
|
||||
|
||||
int ccm_from_boolean(ccm_t* self, CCM value)
|
||||
{
|
||||
assert(self);
|
||||
return ((value_t*) self->values.data[value])->data.boolean;
|
||||
}
|
||||
|
||||
CCM ccm_to_boolean(ccm_t* self, int value, int line)
|
||||
{
|
||||
value_t* boolean = malloc(sizeof(value_t));
|
||||
value_init_boolean(boolean, value, line);
|
||||
vec_push(&self->values, boolean);
|
||||
return self->values.size - 1;
|
||||
}
|
||||
|
||||
void ccm_push(ccm_t* self, CCM value)
|
||||
{
|
||||
assert(self);
|
||||
|
@ -208,3 +229,14 @@ void ccm_pow(ccm_t* self)
|
|||
|
||||
ccm_push(self, ccm_to_num(self, powf(lhs, rhs), line));
|
||||
}
|
||||
|
||||
void ccm_not(ccm_t* self)
|
||||
{
|
||||
assert(self);
|
||||
CCM ccm_lhs = ccm_pop(self);
|
||||
int line = ((value_t*) self->values.data[ccm_lhs])->line;
|
||||
|
||||
int lhs = ccm_from_boolean(self, ccm_lhs);
|
||||
|
||||
ccm_push(self, ccm_to_boolean(self, !lhs, line));
|
||||
}
|
||||
|
|
|
@ -27,6 +27,10 @@ int ccm_is_tuple(ccm_t* self, CCM value);
|
|||
vec_t* ccm_from_tuple(ccm_t* self, CCM value);
|
||||
CCM ccm_to_tuple(ccm_t* self, vec_t* values, int line);
|
||||
|
||||
int ccm_is_boolean(ccm_t* self, CCM value);
|
||||
int ccm_from_boolean(ccm_t* self, CCM value);
|
||||
CCM ccm_to_boolean(ccm_t* self, int value, int line);
|
||||
|
||||
void ccm_push(ccm_t* self, CCM value);
|
||||
CCM ccm_pop(ccm_t* self);
|
||||
CCM ccm_top(ccm_t* self, int depth);
|
||||
|
@ -38,5 +42,6 @@ void ccm_mul(ccm_t* self);
|
|||
void ccm_div(ccm_t* self);
|
||||
void ccm_mod(ccm_t* self);
|
||||
void ccm_pow(ccm_t* self);
|
||||
void ccm_not(ccm_t* self);
|
||||
|
||||
#endif
|
||||
|
|
110
lib/compiler.c
110
lib/compiler.c
|
@ -26,13 +26,26 @@ void compiler_compile(compiler_t* self,
|
|||
return;
|
||||
}
|
||||
|
||||
ccm_t* ccm = &self->module->ccm;
|
||||
|
||||
switch (node->kind)
|
||||
{
|
||||
case NODE_AND: {
|
||||
compiler_compile_and(self, node, prog);
|
||||
} break;
|
||||
case NODE_OR: {
|
||||
compiler_compile_or(self, node, prog);
|
||||
} break;
|
||||
case NODE_NOT: {
|
||||
compiler_compile(self, node->children.data[0], prog);
|
||||
prog_add_instr(prog, OP_NOT, CCM_NO_PARAM);
|
||||
} break;
|
||||
|
||||
case NODE_ASSERT_EQ: {
|
||||
compiler_compile(self, node->children.data[0], prog);
|
||||
prog_add_instr(prog, OP_ASSERT_EQ, CCM_NO_PARAM);
|
||||
} break;
|
||||
|
||||
|
||||
case NODE_ASSERT_NE: {
|
||||
compiler_compile(self, node->children.data[0], prog);
|
||||
prog_add_instr(prog, OP_ASSERT_NE, CCM_NO_PARAM);
|
||||
|
@ -71,6 +84,13 @@ void compiler_compile(compiler_t* self,
|
|||
prog_add_instr(prog, OP_MK_TUPLE, node->children.size);
|
||||
} break;
|
||||
|
||||
case NODE_BOOL: {
|
||||
int val = strcmp(node->value, "true") == 0;
|
||||
CCM value = ccm_to_boolean(ccm, val, node->line);
|
||||
size_t id = prog_add_new_constant(prog, value);
|
||||
prog_add_instr(prog, OP_PUSH, id);
|
||||
} break;
|
||||
|
||||
case NODE_SUB: {
|
||||
compiler_compile(self, node->children.data[0], prog);
|
||||
|
||||
|
@ -114,3 +134,91 @@ void compiler_compile(compiler_t* self,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compiler_compile_or(compiler_t* self,
|
||||
node_t* node,
|
||||
prog_t* prog)
|
||||
{
|
||||
ccm_t* ccm = &self->module->ccm;
|
||||
vec_t to_true;
|
||||
vec_init(&to_true);
|
||||
|
||||
for (size_t i=0; i<node->children.size; i++)
|
||||
{
|
||||
compiler_compile(self, node->children.data[i], prog);
|
||||
prog_add_instr(prog, OP_NOT, 0);
|
||||
size_t addr = prog_add_instr(prog, OP_BRF, 0);
|
||||
vec_push(&to_true, (void*) addr);
|
||||
}
|
||||
|
||||
// FALSE
|
||||
prog_add_instr(prog, OP_PUSH, prog_add_new_constant(
|
||||
prog,
|
||||
ccm_to_boolean(ccm, 0, node->line)
|
||||
));
|
||||
|
||||
size_t to_end = prog_add_instr(prog, OP_BR, 0);
|
||||
|
||||
// TRUE
|
||||
size_t true_point = prog->instrs.size;
|
||||
prog_add_instr(prog, OP_PUSH, prog_add_new_constant(
|
||||
prog,
|
||||
ccm_to_boolean(ccm, 1, node->line)
|
||||
));
|
||||
|
||||
for (size_t i=0; i<to_true.size; i++)
|
||||
{
|
||||
size_t k = ((size_t) to_true.data[i]);
|
||||
((instr_t*) prog->instrs.data[k])->param = true_point;
|
||||
}
|
||||
|
||||
size_t end_point = prog->instrs.size;
|
||||
((instr_t*) prog->instrs.data[to_end])->param = end_point;
|
||||
|
||||
vec_free(&to_true);
|
||||
}
|
||||
|
||||
void compiler_compile_and(compiler_t* self,
|
||||
node_t* node,
|
||||
prog_t* prog)
|
||||
{
|
||||
ccm_t* ccm = &self->module->ccm;
|
||||
vec_t to_false;
|
||||
vec_init(&to_false);
|
||||
|
||||
for (size_t i=0; i<node->children.size; i++)
|
||||
{
|
||||
compiler_compile(self, node->children.data[i], prog);
|
||||
size_t addr = prog_add_instr(prog, OP_BRF, 0);
|
||||
vec_push(&to_false, (void*) addr);
|
||||
}
|
||||
|
||||
// TRUE
|
||||
prog_add_instr(prog, OP_PUSH, prog_add_new_constant(
|
||||
prog,
|
||||
ccm_to_boolean(ccm, 1, node->line)
|
||||
));
|
||||
|
||||
size_t to_end = prog_add_instr(prog, OP_BR, 0);
|
||||
|
||||
// FALSE
|
||||
size_t false_point = prog_add_instr(
|
||||
prog,
|
||||
OP_PUSH,
|
||||
prog_add_new_constant(
|
||||
prog,
|
||||
ccm_to_boolean(ccm, 0, node->line)
|
||||
)
|
||||
);
|
||||
|
||||
for (size_t i=0; i<to_false.size; i++)
|
||||
{
|
||||
size_t k = ((size_t) to_false.data[i]);
|
||||
((instr_t*) prog->instrs.data[k])->param = false_point;
|
||||
}
|
||||
|
||||
size_t end_point = prog->instrs.size;
|
||||
((instr_t*) prog->instrs.data[to_end])->param = end_point;
|
||||
|
||||
vec_free(&to_false);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,15 @@ typedef struct {
|
|||
void compiler_init(compiler_t* self, module_t* module);
|
||||
void compiler_free(compiler_t* self);
|
||||
|
||||
void compiler_compile(compiler_t* self,
|
||||
node_t* node,
|
||||
void compiler_compile(compiler_t* self,
|
||||
node_t* node,
|
||||
prog_t* prog);
|
||||
|
||||
void compiler_compile_or(compiler_t* self,
|
||||
node_t* node,
|
||||
prog_t* prog);
|
||||
|
||||
void compiler_compile_and(compiler_t* self,
|
||||
node_t* node,
|
||||
prog_t* prog);
|
||||
#endif
|
||||
|
|
29
lib/exec.c
29
lib/exec.c
|
@ -44,6 +44,33 @@ void exec_instr(exec_t* self,
|
|||
|
||||
switch (op)
|
||||
{
|
||||
case OP_NOT: {
|
||||
ccm_not(ccm);
|
||||
self->pc++;
|
||||
} break;
|
||||
|
||||
case OP_NOP: {
|
||||
self->pc++;
|
||||
} break;
|
||||
|
||||
case OP_BR: {
|
||||
self->pc = param;
|
||||
} break;
|
||||
|
||||
case OP_BRF: {
|
||||
CCM value = ccm_pop(ccm);
|
||||
int val = ccm_from_boolean(ccm, value);
|
||||
|
||||
if (val)
|
||||
{
|
||||
self->pc++;
|
||||
}
|
||||
else
|
||||
{
|
||||
self->pc = param;
|
||||
}
|
||||
} break;
|
||||
|
||||
case OP_ASSERT_NE:
|
||||
case OP_ASSERT_EQ: {
|
||||
CCM val = ccm_pop(ccm);
|
||||
|
@ -64,7 +91,7 @@ void exec_instr(exec_t* self,
|
|||
|
||||
char rhs[CCM_STRLEN];
|
||||
value_str(values->data[1], rhs, CCM_STRLEN);
|
||||
char const* operator = oracle ? "==" : "!=";
|
||||
char const* operator = oracle ? "==" : "!=";
|
||||
|
||||
err_push(
|
||||
&self->err,
|
||||
|
|
|
@ -159,6 +159,11 @@ node_t* lexer_try_new_next(lexer_t* self)
|
|||
|
||||
CCM_KEYWORD("assert_eq", NODE_ASSERT_EQ, 0);
|
||||
CCM_KEYWORD("assert_ne", NODE_ASSERT_NE, 0);
|
||||
CCM_KEYWORD("true", NODE_BOOL, 1);
|
||||
CCM_KEYWORD("false", NODE_BOOL, 1);
|
||||
CCM_KEYWORD("and", NODE_AND, 0);
|
||||
CCM_KEYWORD("or", NODE_OR, 0);
|
||||
CCM_KEYWORD("not", NODE_NOT, 0);
|
||||
|
||||
if (self->cursor < (ssize_t) strlen(self->source))
|
||||
{
|
||||
|
|
|
@ -53,7 +53,6 @@ int module_load(module_t* self, char const* path)
|
|||
err_push(&self->err, lexer.line, "invalid module");
|
||||
goto free_parser;
|
||||
}
|
||||
|
||||
compiler_t compiler;
|
||||
compiler_init(&compiler, self);
|
||||
|
||||
|
|
|
@ -9,7 +9,8 @@ G(NODE_MODULE), \
|
|||
G(NODE_NUM), G(NODE_OPAR), G(NODE_CPAR), \
|
||||
G(NODE_POW), G(NODE_ADD), G(NODE_SUB), G(NODE_MUL), \
|
||||
G(NODE_DIV), G(NODE_MOD), G(NODE_COMMA), G(NODE_TUPLE), \
|
||||
G(NODE_ASSERT_EQ), G(NODE_ASSERT_NE)
|
||||
G(NODE_ASSERT_EQ), G(NODE_ASSERT_NE), G(NODE_BOOL), \
|
||||
G(NODE_AND), G(NODE_OR), G(NODE_NOT)
|
||||
|
||||
CCM_ENUM_H(NodeKind, NODE_KIND);
|
||||
|
||||
|
|
106
lib/parser.c
106
lib/parser.c
|
@ -92,14 +92,14 @@ node_t* parser_try_new_module(parser_t* self)
|
|||
node_t* parser_try_new_expr(parser_t* self)
|
||||
{
|
||||
assert(self);
|
||||
|
||||
|
||||
if (lexer_peek_kind(self->lexer, NODE_ASSERT_EQ, 0)
|
||||
|| lexer_peek_kind(self->lexer, NODE_ASSERT_NE, 0))
|
||||
{
|
||||
return CCM_TRY(parser_try_new_assert);
|
||||
}
|
||||
|
||||
return CCM_TRY(parser_try_new_term);
|
||||
return CCM_TRY(parser_try_new_or);
|
||||
}
|
||||
|
||||
node_t* parser_try_new_assert(parser_t* self)
|
||||
|
@ -122,7 +122,7 @@ node_t* parser_try_new_assert(parser_t* self)
|
|||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
node_t* tuple = CCM_TRY(parser_try_new_tuple);
|
||||
|
||||
|
@ -138,6 +138,66 @@ node_t* parser_try_new_assert(parser_t* self)
|
|||
return node;
|
||||
}
|
||||
|
||||
node_t* parser_try_new_or(parser_t* self)
|
||||
{
|
||||
assert(self);
|
||||
node_t* lhs = CCM_TRY(parser_try_new_and);
|
||||
if (!lhs) { return NULL; }
|
||||
|
||||
while (lexer_peek_kind(self->lexer, NODE_OR, 0))
|
||||
{
|
||||
lexer_consume_next(self->lexer, NODE_OR);
|
||||
node_t* node = malloc(sizeof(node_t));
|
||||
node_init(node, NODE_OR, "", lhs->line);
|
||||
|
||||
node_push_new_child(node, lhs);
|
||||
node_t* rhs = CCM_TRY(parser_try_new_and);
|
||||
|
||||
if (!rhs)
|
||||
{
|
||||
node_free(lhs);
|
||||
free(lhs);
|
||||
node_free(node);
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
node_push_new_child(node, rhs);
|
||||
lhs = node;
|
||||
}
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
node_t* parser_try_new_and(parser_t* self)
|
||||
{
|
||||
assert(self);
|
||||
node_t* lhs = CCM_TRY(parser_try_new_term);
|
||||
if (!lhs) { return NULL; }
|
||||
|
||||
while (lexer_peek_kind(self->lexer, NODE_AND, 0))
|
||||
{
|
||||
lexer_consume_next(self->lexer, NODE_AND);
|
||||
node_t* node = malloc(sizeof(node_t));
|
||||
node_init(node, NODE_AND, "", lhs->line);
|
||||
|
||||
node_push_new_child(node, lhs);
|
||||
node_t* rhs = CCM_TRY(parser_try_new_term);
|
||||
|
||||
if (!rhs)
|
||||
{
|
||||
node_free(lhs);
|
||||
free(lhs);
|
||||
node_free(node);
|
||||
free(node);
|
||||
return NULL;
|
||||
}
|
||||
node_push_new_child(node, rhs);
|
||||
lhs = node;
|
||||
}
|
||||
|
||||
return lhs;
|
||||
}
|
||||
|
||||
node_t* parser_try_new_term(parser_t* self)
|
||||
{
|
||||
assert(self);
|
||||
|
@ -223,6 +283,38 @@ node_t* parser_try_new_usub(parser_t* self)
|
|||
return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CCM_TRY(parser_try_new_not);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node_t* parser_try_new_not(parser_t* self)
|
||||
{
|
||||
assert(self);
|
||||
|
||||
if (lexer_peek_kind(self->lexer, NODE_NOT, 0))
|
||||
{
|
||||
lexer_consume_next(self->lexer, NODE_NOT);
|
||||
node_t* node = malloc(sizeof(node_t));
|
||||
node_init(node, NODE_NOT, "", self->lexer->line);
|
||||
|
||||
node_t* rhs = CCM_TRY(parser_try_new_not);
|
||||
|
||||
if (!rhs)
|
||||
{
|
||||
node_free(node);
|
||||
free(node);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
node_push_new_child(node, rhs);
|
||||
|
||||
return node;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CCM_TRY(parser_try_new_pow);
|
||||
}
|
||||
|
@ -363,9 +455,13 @@ node_t* parser_try_new_builtin(parser_t* self)
|
|||
assert(self);
|
||||
|
||||
node_t* node = lexer_try_new_next(self->lexer);
|
||||
parser_ensure(self, node, NODE_NUM);
|
||||
|
||||
if (node && node->kind == NODE_NUM)
|
||||
if (node &&
|
||||
(
|
||||
node->kind == NODE_NUM
|
||||
|| node->kind == NODE_BOOL
|
||||
)
|
||||
)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
|
|
@ -24,9 +24,12 @@ int parser_ensure(parser_t* self, node_t* node, NodeKind kind);
|
|||
node_t* parser_try_new_module(parser_t* self);
|
||||
node_t* parser_try_new_expr(parser_t* self);
|
||||
node_t* parser_try_new_assert(parser_t* self);
|
||||
node_t* parser_try_new_or(parser_t* self);
|
||||
node_t* parser_try_new_and(parser_t* self);
|
||||
node_t* parser_try_new_term(parser_t* self);
|
||||
node_t* parser_try_new_factor(parser_t* self);
|
||||
node_t* parser_try_new_usub(parser_t* self);
|
||||
node_t* parser_try_new_not(parser_t* self);
|
||||
node_t* parser_try_new_pow(parser_t* self);
|
||||
node_t* parser_try_new_literal(parser_t* self);
|
||||
node_t* parser_try_new_tuple(parser_t* self);
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
#define TYPES(G) \
|
||||
G(TYPE_NUM), \
|
||||
G(TYPE_TUPLE)
|
||||
G(TYPE_TUPLE), \
|
||||
G(TYPE_BOOLEAN)
|
||||
|
||||
CCM_ENUM_H(Type, TYPES);
|
||||
|
||||
|
|
20
lib/value.c
20
lib/value.c
|
@ -17,6 +17,14 @@ void value_init_new_tuple(value_t* self, vec_t* values, int line)
|
|||
self->line = line;
|
||||
}
|
||||
|
||||
void value_init_boolean(value_t* self, int boolean, int line)
|
||||
{
|
||||
assert(self);
|
||||
self->data.boolean = boolean;
|
||||
self->type = TYPE_BOOLEAN;
|
||||
self->line = line;
|
||||
}
|
||||
|
||||
value_t* value_new_clone(value_t* self)
|
||||
{
|
||||
assert(self);
|
||||
|
@ -28,6 +36,9 @@ value_t* value_new_clone(value_t* self)
|
|||
case TYPE_NUM: {
|
||||
value_init_num(value, self->data.num, self->line);
|
||||
} break;
|
||||
case TYPE_BOOLEAN: {
|
||||
value_init_boolean(value, self->data.boolean, self->line);
|
||||
} break;
|
||||
case TYPE_TUPLE: {
|
||||
vec_t* vec = malloc(sizeof(vec_t));
|
||||
vec_init(vec);
|
||||
|
@ -76,6 +87,11 @@ size_t value_str(value_t* self, char* buffer, size_t size)
|
|||
self->data.num);
|
||||
} break;
|
||||
|
||||
case TYPE_BOOLEAN: {
|
||||
sz += snprintf(buffer + sz, size - sz, "%s",
|
||||
self->data.boolean ? "true" : "false");
|
||||
} break;
|
||||
|
||||
case TYPE_TUPLE: {
|
||||
sz += snprintf(buffer + sz, size - sz, "(");
|
||||
|
||||
|
@ -117,6 +133,10 @@ int value_equals(value_t* self, value_t* rhs)
|
|||
return self->data.num == rhs->data.num;
|
||||
} break;
|
||||
|
||||
case TYPE_BOOLEAN: {
|
||||
return self->data.boolean == rhs->data.boolean;
|
||||
} break;
|
||||
|
||||
case TYPE_TUPLE: {
|
||||
if (self->data.tuple->size != rhs->data.tuple->size)
|
||||
{
|
||||
|
|
|
@ -9,6 +9,7 @@ typedef struct {
|
|||
union {
|
||||
double num;
|
||||
vec_t* tuple;
|
||||
int boolean;
|
||||
} data;
|
||||
|
||||
Type type;
|
||||
|
@ -17,6 +18,7 @@ typedef struct {
|
|||
|
||||
void value_init_num(value_t* self, double num, int line);
|
||||
void value_init_new_tuple(value_t* self, vec_t* values, int line);
|
||||
void value_init_boolean(value_t* self, int boolean, int line);
|
||||
value_t* value_new_clone(value_t* self);
|
||||
void value_free(value_t* self);
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
assert_eq (true, true)
|
||||
assert_eq (false, false)
|
||||
|
||||
assert_eq (true, true and true)
|
||||
assert_eq (false, true and false)
|
||||
assert_eq (false, false and true)
|
||||
assert_eq (false, false and false)
|
||||
|
||||
assert_eq (true, true or true)
|
||||
assert_eq (true, true or false)
|
||||
assert_eq (true, false or true)
|
||||
assert_eq (false, false or false)
|
||||
|
||||
assert_eq (true, not false)
|
||||
assert_eq (false, not true)
|
||||
|
||||
assert_eq (true, not not true)
|
||||
assert_eq (false, not not not true)
|
||||
|
||||
|
Loading…
Reference in New Issue