Skip to content
7 changes: 4 additions & 3 deletions spy/backend/c/cwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ def fmt_expr_BinOp(self, binop: ast.BinOp) -> C.Expr:
FQN('operator::i8_add'): '+',
FQN('operator::i8_sub'): '-',
FQN('operator::i8_mul'): '*',
FQN('operator::i8_floordiv'): '/',
FQN('operator::i8_lshift'): '<<',
FQN('operator::i8_rshift'): '>>',
FQN('operator::i8_and'): '&',
Expand All @@ -299,7 +298,6 @@ def fmt_expr_BinOp(self, binop: ast.BinOp) -> C.Expr:
FQN('operator::u8_add'): '+',
FQN('operator::u8_sub'): '-',
FQN('operator::u8_mul'): '*',
FQN('operator::u8_floordiv'): '/',
FQN('operator::u8_lshift'): '<<',
FQN('operator::u8_rshift'): '>>',
FQN('operator::u8_and'): '&',
Expand All @@ -315,7 +313,6 @@ def fmt_expr_BinOp(self, binop: ast.BinOp) -> C.Expr:
FQN('operator::i32_add'): '+',
FQN('operator::i32_sub'): '-',
FQN('operator::i32_mul'): '*',
FQN('operator::i32_floordiv'): '/',
FQN('operator::i32_lshift'): '<<',
FQN('operator::i32_rshift'): '>>',
FQN('operator::i32_and'): '&',
Expand All @@ -342,13 +339,17 @@ def fmt_expr_BinOp(self, binop: ast.BinOp) -> C.Expr:
# operator.h. They are listed here to make emphasize that they are not
# omitted from above by mistake:
# FQN('operator::i8_div')
# FQN('operator::i8_floordiv')
# FQN('operator::i8_mod')
# FQN('operator::u8_div')
# FQN('operator::u8_floordiv')
# FQN('operator::u8_mod')
# FQN('operator::i32_div')
# FQN('operator::i32_floordiv')
# FQN('operator::i32_mod')
# FQN('operator::f64_div')
# FQN('operator::f64_floordiv')
# FQN('operator::f64_mod')
}

FQN2UnaryOp = {
Expand Down
73 changes: 68 additions & 5 deletions spy/libspy/include/spy/operator.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,60 @@ static inline double spy_operator$i32_div(int32_t x, int32_t y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "division by zero",
__FILE__, __LINE__);
return nan("");
}
return (double)x / y;
}

static inline double spy_operator$i8_mod(int8_t x, int8_t y) {
static inline int8_t spy_operator$i8_floordiv(int8_t x, int8_t y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "integer division or modulo by zero",
__FILE__, __LINE__);
}
int8_t q = x / y;
int8_t r = x % y;

if ((r != 0) && ((x ^ y) < 0)) {
q -= 1;
}

return q;
}

static inline uint8_t spy_operator$u8_floordiv(uint8_t x, uint8_t y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "integer division or modulo by zero",
__FILE__, __LINE__);
}
return x / y;
}

static inline int32_t spy_operator$i32_floordiv(int32_t x, int32_t y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "integer division or modulo by zero",
__FILE__, __LINE__);
}
int32_t q = x / y;
int32_t r = x % y;

if ((r != 0) && ((x ^ y) < 0)) {
q -= 1;
}

return q;
}

static inline int8_t spy_operator$i8_mod(int8_t x, int8_t y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "integer modulo by zero",
__FILE__, __LINE__);
}
return x % y;
int8_t r = x % y;

if ((r != 0) && ((x ^ y) < 0)) {
r += y;
}

return r;
}

static inline double spy_operator$u8_mod(uint8_t x, uint8_t y) {
Expand All @@ -93,12 +136,18 @@ static inline double spy_operator$u8_mod(uint8_t x, uint8_t y) {
return x % y;
}

static inline double spy_operator$i32_mod(int32_t x, int32_t y) {
static inline int32_t spy_operator$i32_mod(int32_t x, int32_t y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "integer modulo by zero",
__FILE__, __LINE__);
}
return x % y;
int32_t r = x % y;

if ((r != 0) && ((x ^ y) < 0)) {
r += y;
}

return r;
}

static inline double spy_operator$f64_div(double x, double y) {
Expand All @@ -117,6 +166,20 @@ static inline double spy_operator$f64_floordiv(double x, double y) {
return floor(x / y);
}

static inline double spy_operator$f64_mod(double x, double y) {
if (y == 0) {
spy_panic("ZeroDivisionError", "float modulo by zero",
__FILE__, __LINE__);
}
double r = fmod(x, y);

if (r != 0.00 && (y < 0.00) != (r < 0.00)) {
r += y;
}

return r;
}

static inline bool spy_operator$bool_eq(bool x, bool y) {
return x == y;
}
Expand Down
32 changes: 32 additions & 0 deletions spy/tests/compiler/test_float.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,50 @@ def sub(x: f64, y: f64) -> f64: return x - y
def mul(x: f64, y: f64) -> f64: return x * y
def div(x: f64, y: f64) -> f64: return x / y
def floordiv(x: f64, y: f64) -> f64: return x // y
def mod(x: f64, y: f64) -> f64: return x % y
def neg(x: f64) -> f64: return -x
""")
assert mod.add(1.5, 2.6) == 4.1
assert mod.sub(1.5, 0.2) == 1.3
assert mod.mul(1.5, 0.5) == 0.75
assert mod.div(1.5, 2.0) == 0.75
assert mod.floordiv(10.0, 3.0) == 3.0
assert mod.mod(10.5, 2.5) == 0.5
assert mod.neg(-2.5) == 2.5

def test_zero_division_error(self):
mod = self.compile(
"""
def div(x: f64, y: f64) -> f64: return x / y
def floordiv(x: f64, y: f64) -> f64: return x // y
def mod(x: f64, y: f64) -> f64: return x % y
""")
with SPyError.raises("W_ZeroDivisionError", match="float division by zero"):
mod.div(1.5, 0.0)
with SPyError.raises("W_ZeroDivisionError", match="float floor division by zero"):
mod.floordiv(10.0, 0.0)
with SPyError.raises("W_ZeroDivisionError", match="float modulo by zero"):
mod.mod(10.5, 0.0)

def test_division_mixed_signs(self):
mod = self.compile(
"""
def floordiv(x: f64, y: f64) -> f64: return x // y
def mod(x: f64, y: f64) -> f64: return x % y
""")
assert mod.floordiv(3.5, 1.5) == 2.0
assert mod.floordiv(3.5, -1.5) == -3.0
assert mod.floordiv(-3.5, 1.5) == -3.0
assert mod.floordiv(-3.5, -1.5) == 2.0
assert mod.mod(3.5, 1.5) == 0.5
assert mod.mod(3.5, -1.5) == -1.0
assert mod.mod(-3.5, 1.5) == 1.0
assert mod.mod(-3.5, -1.5) == -0.5
assert mod.mod(5.0, float('inf')) == 5.0
assert mod.mod(-5.0, float('inf')) == float('inf')
assert mod.mod(5.0, float('-inf')) == float('-inf')
assert mod.mod(-5.0, float('-inf')) == -5.0


def test_CompareOp(self):
mod = self.compile("""
Expand Down
29 changes: 27 additions & 2 deletions spy/tests/compiler/test_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,39 @@ def floordiv(x: T, y: T) -> T: return x // y
assert mod.mod(10, 3) == 1
assert mod.div(11, 2) == 5.5
assert mod.floordiv(11, 2) == 5

def test_zero_division_error(self, int_type):
mod = self.compile(f"""
T = {int_type}
def mod(x: T, y: T) -> T: return x % y
def div(x: T, y: T) -> f64: return x / y
def floordiv(x: T, y: T) -> T: return x // y
""")
with SPyError.raises("W_ZeroDivisionError", match="integer modulo by zero"):
mod.mod(10, 0)
with SPyError.raises("W_ZeroDivisionError", match="division by zero"):
mod.div(11, 0)
with SPyError.raises("W_ZeroDivisionError"):
# TODO: add appropriate floor div handling for c backend
with SPyError.raises("W_ZeroDivisionError", match="integer division or modulo by zero"):
mod.floordiv(11, 0)

def test_division_mixed_signs(self, int_type):
if int_type == "u8":
pytest.skip("Skipping for negative operands in floordiv test")

mod = self.compile(f"""
T = {int_type}
def floordiv(x: T, y: T) -> T: return x // y
def mod(x: T, y: T) -> T: return x % y
""")
assert mod.floordiv(7, 3) == 2
assert mod.floordiv(-7, 3) == -3
assert mod.floordiv(7, -3) == -3
assert mod.floordiv(-7, -3) == 2
assert mod.mod(7, 3) == 1
assert mod.mod(-7, 3) == 2
assert mod.mod(7, -3) == -2
assert mod.mod(-7, -3) == -1

def test_neg(self, int_type):
src = f"""
T = {int_type}
Expand Down
1 change: 1 addition & 0 deletions spy/vm/modules/operator/binop.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
MM.register('*', 'f64', 'f64', OP.w_f64_mul)
MM.register('/', 'f64', 'f64', OP.w_f64_div)
MM.register('//', 'f64', 'f64', OP.w_f64_floordiv)
MM.register('%', 'f64', 'f64', OP.w_f64_mod)
MM.register('==', 'f64', 'f64', OP.w_f64_eq)
MM.register('!=', 'f64', 'f64', OP.w_f64_ne)
MM.register('<' , 'f64', 'f64', OP.w_f64_lt)
Expand Down
6 changes: 6 additions & 0 deletions spy/vm/modules/operator/opimpl_f64.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ def w_f64_floordiv(vm: 'SPyVM', w_a: W_F64, w_b: W_F64) -> W_F64:
raise SPyError("W_ZeroDivisionError", "float floor division by zero")
return _f64_op(vm, w_a, w_b, lambda a, b: a // b)

@OP.builtin_func
def w_f64_mod(vm: 'SPyVM', w_a: W_F64, w_b: W_F64) -> W_F64:
if w_b.value == 0:
raise SPyError("W_ZeroDivisionError", "float modulo by zero")
return _f64_op(vm, w_a, w_b, lambda a, b: a % b)

@OP.builtin_func
def w_f64_eq(vm: 'SPyVM', w_a: W_F64, w_b: W_F64) -> W_Bool:
return _f64_op(vm, w_a, w_b, lambda a, b: a == b)
Expand Down