From b23eb26ffee4844985177841bfa97eb7ede5c3da Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Tue, 27 Aug 2024 22:31:13 +0100 Subject: [PATCH] Add primitive logic operations: "and", "or", "not" --- CMakeLists.txt | 1 + src/common.cpp | 5 ++ src/compiler.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++++++ src/compiler.hpp | 3 ++ src/error.hpp | 1 + test/logic.vli | 23 +++++++++ 6 files changed, 164 insertions(+) create mode 100644 test/logic.vli diff --git a/CMakeLists.txt b/CMakeLists.txt index f47438e..12f8951 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ set(CPP_TESTS set(LISP_TESTS numeric + logic ) diff --git a/src/common.cpp b/src/common.cpp index 43a6f09..8296250 100644 --- a/src/common.cpp +++ b/src/common.cpp @@ -512,6 +512,8 @@ Result Int64::sub_inv(const Float& rhs) const { Result Int64::div(const Object& rhs) const { return rhs.div_inv(*this); } Result Int64::div_inv(const Int64& rhs) const { + if (value() == 0) return ERROR(DivisionByZero); + if (rhs.value() % value() == 0) { return Value(TRY(Int64::create(rhs.value() / value()))); } @@ -520,6 +522,7 @@ Result Int64::div_inv(const Int64& rhs) const { } Result Int64::div_inv(const Float& rhs) const { + if (value() == 0) return ERROR(DivisionByZero); return Value(TRY(Float::create(rhs.value() / float(value())))); } @@ -550,10 +553,12 @@ Result Float::sub_inv(const Int64& rhs) const { Result Float::div(const Object& rhs) const { return rhs.div_inv(*this); } Result Float::div_inv(const Float& rhs) const { + if (value() == 0) return ERROR(DivisionByZero); return Value(TRY(Float::create(rhs.value() / value()))); } Result Float::div_inv(const Int64& rhs) const { + if (value() == 0) return ERROR(DivisionByZero); return Value(TRY(Float::create(float(rhs.value()) / value()))); } Result Int64::cmp(const Float& rhs) const { diff --git a/src/compiler.cpp b/src/compiler.cpp index 0e3d42b..faa7738 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -433,6 +433,131 @@ Result Compiler::compile_if(Context& context, Symbol& op, return std::move(ex); } +Result Compiler::compile_and(Context& context, Symbol& op, + Pair& expr) { + Value param = TRY(expr.rest()); + Expression ex = TRY(Expression::create()); + if (param.is() || !param.is()) { + return ERROR(CompilationError); + } + + uint64_t prev_reg = 0; + uint64_t result = context.alloc_reg(); + int64_t true_c = TRY(context.add_const(TRY(Bool::create(true)))); + int64_t false_c = TRY(context.add_const(TRY(Bool::create(false)))); + TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)result}, {1, false_c})); + + bool is_first = true; + + while (!param.is()) { + if (!param.is()) { + return ERROR(CompilationError); + } + + Pair& param_pair = *param.to(); + auto rest = TRY(param_pair.rest()); + Value param_val = TRY(param_pair.first()); + + auto param_ex = TRY(compile_expr(context, param_val)); + + if (!is_first) { + TRY(ex.add_opcode(Oc::Equal, {0, (int64_t)prev_reg}, {1, (int64_t)true_c}, + {0, (int64_t)0})); + TRY(ex.add_opcode(Oc::Jump, {0, (int64_t)param_ex.code.size() + 2})); + } + prev_reg = param_ex.reg; + TRY(ex.add_code(param_ex.code)); + + param = std::move(rest); + is_first = false; + } + TRY(ex.add_opcode(Oc::Equal, {0, (int64_t)prev_reg}, {1, (int64_t)true_c}, + {0, (int64_t)0})); + TRY(ex.add_opcode(Oc::Jump, {0, (int64_t)2})); + TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)result}, {1, true_c})); + + context.maxreg = result + 1; + ex.reg = result; + return std::move(ex); +} + +Result Compiler::compile_or(Context& context, Symbol& op, + Pair& expr) { + Value param = TRY(expr.rest()); + Expression ex = TRY(Expression::create()); + if (param.is() || !param.is()) { + return ERROR(CompilationError); + } + + uint64_t prev_reg = 0; + uint64_t result = context.alloc_reg(); + int64_t true_c = TRY(context.add_const(TRY(Bool::create(true)))); + int64_t false_c = TRY(context.add_const(TRY(Bool::create(false)))); + TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)result}, {1, true_c})); + + bool is_first = true; + + while (!param.is()) { + if (!param.is()) { + return ERROR(CompilationError); + } + + Pair& param_pair = *param.to(); + auto rest = TRY(param_pair.rest()); + Value param_val = TRY(param_pair.first()); + + auto param_ex = TRY(compile_expr(context, param_val)); + + if (!is_first) { + TRY(ex.add_opcode(Oc::Equal, {0, (int64_t)prev_reg}, {1, (int64_t)true_c}, + {0, (int64_t)1})); + TRY(ex.add_opcode(Oc::Jump, {0, (int64_t)param_ex.code.size() + 2})); + } + prev_reg = param_ex.reg; + TRY(ex.add_code(param_ex.code)); + + param = std::move(rest); + is_first = false; + } + TRY(ex.add_opcode(Oc::Equal, {0, (int64_t)prev_reg}, {1, (int64_t)true_c}, + {0, (int64_t)1})); + TRY(ex.add_opcode(Oc::Jump, {0, (int64_t)2})); + TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)result}, {1, false_c})); + + context.maxreg = result + 1; + ex.reg = result; + return std::move(ex); +} + +Result Compiler::compile_not(Context& context, Symbol& op, + Pair& expr) { + Value first = TRY(expr.rest()); + Expression ex = TRY(Expression::create()); + if (first.is() || !first.is()) { + return ERROR(CompilationError); + } + + Pair& first_pair = *first.to(); + + auto first_expr = TRY(first_pair.first()); + + uint64_t result = context.alloc_reg(); + int64_t true_c = TRY(context.add_const(TRY(Bool::create(true)))); + int64_t false_c = TRY(context.add_const(TRY(Bool::create(false)))); + TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)result}, {1, true_c})); + + auto comp = TRY(compile_expr(context, first_expr)); + TRY(ex.add_code(comp.code)); + + TRY(ex.add_opcode(Oc::Equal, {0, (int64_t)comp.reg}, {1, (int64_t)true_c}, + {0, (int64_t)1})); + TRY(ex.add_opcode(Oc::Mov, {0, (int64_t)result}, {1, false_c})); + + context.maxreg = result + 1; + ex.reg = result; + return std::move(ex); +} + Result Compiler::compile_fn(Context& context, Symbol& op, Pair& expr) { Context ctx = TRY(Context::create(context)); @@ -618,6 +743,12 @@ Result Compiler::compile_list(Context& context, Pair& expr) { return compile_comparison(context, sym, expr); } else if (TRY(sym.cmp("if")) == 0) { return compile_if(context, sym, expr); + } else if (TRY(sym.cmp("and")) == 0) { + return compile_and(context, sym, expr); + } else if (TRY(sym.cmp("or")) == 0) { + return compile_or(context, sym, expr); + } else if (TRY(sym.cmp("not")) == 0) { + return compile_not(context, sym, expr); } else if (TRY(sym.cmp("fn")) == 0) { return compile_fn(context, sym, expr); } else { diff --git a/src/compiler.hpp b/src/compiler.hpp index 57110f0..bf39865 100644 --- a/src/compiler.hpp +++ b/src/compiler.hpp @@ -38,6 +38,9 @@ class Compiler { Result compile_symbol(Context& context, Symbol& value); Result compile_bool(Context& context, Bool& value); Result compile_if(Context& context, Symbol& op, Pair& expr); + Result compile_and(Context& context, Symbol& op, Pair& expr); + Result compile_or(Context& context, Symbol& op, Pair& expr); + Result compile_not(Context& context, Symbol& op, Pair& expr); Result compile_fn(Context& context, Symbol& op, Pair& expr); Result compile_body(Context& context, Pair& expr); Result compile_function_call(Context& context, Pair& expr); diff --git a/src/error.hpp b/src/error.hpp index 4180bc2..5133030 100644 --- a/src/error.hpp +++ b/src/error.hpp @@ -18,6 +18,7 @@ enum class ErrorCode { IOError, Interrupt, AssertionFailed, + DivisionByZero, }; void seterr(const char* err); diff --git a/test/logic.vli b/test/logic.vli new file mode 100644 index 0000000..644fa1e --- /dev/null +++ b/test/logic.vli @@ -0,0 +1,23 @@ +;; -*- mode: lisp; -*- + +(assert (= (not true) false)) +(assert (= (not false) true)) + +(assert (and true)) +(assert (= (and false) false)) +(assert (and true true)) +(assert (= (and true false) false)) +(assert (= (and false true) false)) + +(assert (and true)) +(assert (= (or false) false)) +(assert (or true true)) +(assert (or true false)) +(assert (or false true)) +(assert (= (or false false) false)) + +;; "and" is short-circuiting and should stop evaluation on first "false" +(assert (= (and false (/ 1 0)) false)) + +;; "and" is short-circuiting and should stop evaluation on first "true" +(assert (or true (/ 1 0)))