Skip to content

Commit

Permalink
% (#132)
Browse files Browse the repository at this point in the history
  • Loading branch information
Akuli authored Jan 24, 2023
1 parent 0feca20 commit da62b41
Show file tree
Hide file tree
Showing 14 changed files with 90 additions and 20 deletions.
6 changes: 1 addition & 5 deletions examples/primes.jou
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
from "stdlib/io.jou" import printf

# TODO: no % operator yet, but it is planned :)
def mod(value: int, modulus: int) -> int:
return value - (value / modulus) * modulus

def is_prime(n: int) -> bool:
if n < 2:
return False

for divisor = 2; divisor < n; divisor++:
if mod(n, divisor) == 0:
if n % divisor == 0:
return False
return True

Expand Down
2 changes: 2 additions & 0 deletions src/build_cfg.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ static const Variable *build_binop(
case AST_EXPR_SUB: k = CF_INT_SUB; break;
case AST_EXPR_MUL: k = CF_INT_MUL; break;
case AST_EXPR_DIV: k = (is_signed ? CF_INT_SDIV : CF_INT_UDIV); break;
case AST_EXPR_MOD: k = (is_signed ? CF_INT_SMOD : CF_INT_UMOD); break;
case AST_EXPR_EQ: k = got_pointers?CF_PTR_EQ:CF_INT_EQ; break;
case AST_EXPR_NE: k = got_pointers?CF_PTR_EQ:CF_INT_EQ; negate=true; break;
case AST_EXPR_LT: k = CF_INT_LT; break;
Expand Down Expand Up @@ -483,6 +484,7 @@ static const Variable *build_expression(struct State *st, const AstExpression *e
case AST_EXPR_SUB:
case AST_EXPR_MUL:
case AST_EXPR_DIV:
case AST_EXPR_MOD:
case AST_EXPR_EQ:
case AST_EXPR_NE:
case AST_EXPR_GT:
Expand Down
28 changes: 26 additions & 2 deletions src/codegen.c
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,29 @@ static LLVMValueRef codegen_constant(const struct State *st, const Constant *c)
return make_a_string_constant(st, c->data.str);
}
assert(0);
}
}

static LLVMValueRef build_signed_mod(LLVMBuilderRef builder, LLVMValueRef lhs, LLVMValueRef rhs)
{
// Jou's % operator ensures that a%b has same sign as b:
// jou_mod(a, b) = llvm_mod(llvm_mod(a, b) + b, b)
LLVMValueRef llmod = LLVMBuildSRem(builder, lhs, rhs, "smod_tmp");
LLVMValueRef sum = LLVMBuildAdd(builder, llmod, rhs, "smod_tmp");
return LLVMBuildSRem(builder, sum, rhs, "smod");
}

static LLVMValueRef build_signed_div(LLVMBuilderRef builder, LLVMValueRef lhs, LLVMValueRef rhs)
{
/*
LLVM's provides two divisions. One truncates, the other is an "exact div"
that requires there is no remainder. Jou uses floor division which is
neither of the two, but is quite easy to implement:
floordiv(a, b) = exact_div(a - jou_mod(a, b), b)
*/
LLVMValueRef top = LLVMBuildSub(builder, lhs, build_signed_mod(builder, lhs, rhs), "sdiv_tmp");
return LLVMBuildExactSDiv(builder, top, rhs, "sdiv");
}

static void codegen_instruction(const struct State *st, const CfInstruction *ins)
{
Expand Down Expand Up @@ -226,8 +248,10 @@ static void codegen_instruction(const struct State *st, const CfInstruction *ins
case CF_INT_ADD: setdest(LLVMBuildAdd(st->builder, getop(0), getop(1), "int_add")); break;
case CF_INT_SUB: setdest(LLVMBuildSub(st->builder, getop(0), getop(1), "int_sub")); break;
case CF_INT_MUL: setdest(LLVMBuildMul(st->builder, getop(0), getop(1), "int_mul")); break;
case CF_INT_SDIV: setdest(LLVMBuildSDiv(st->builder, getop(0), getop(1), "int_sdiv")); break;
case CF_INT_UDIV: setdest(LLVMBuildUDiv(st->builder, getop(0), getop(1), "int_udiv")); break;
case CF_INT_UMOD: setdest(LLVMBuildURem(st->builder, getop(0), getop(1), "int_umod")); break;
case CF_INT_SDIV: setdest(build_signed_div(st->builder, getop(0), getop(1))); break;
case CF_INT_SMOD: setdest(build_signed_mod(st->builder, getop(0), getop(1))); break;
case CF_INT_EQ: setdest(LLVMBuildICmp(st->builder, LLVMIntEQ, getop(0), getop(1), "int_eq")); break;
// TODO: unsigned less-than
case CF_INT_LT: setdest(LLVMBuildICmp(st->builder, LLVMIntSLT, getop(0), getop(1), "int_lt")); break;
Expand Down
1 change: 1 addition & 0 deletions src/free.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ static void free_expression(const AstExpression *expr)
case AST_EXPR_SUB:
case AST_EXPR_MUL:
case AST_EXPR_DIV:
case AST_EXPR_MOD:
case AST_EXPR_EQ:
case AST_EXPR_NE:
case AST_EXPR_GT:
Expand Down
3 changes: 3 additions & 0 deletions src/jou_compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ struct AstExpression {
AST_EXPR_NEG,
AST_EXPR_MUL,
AST_EXPR_DIV,
AST_EXPR_MOD,
AST_EXPR_EQ,
AST_EXPR_NE,
// We need all of gt,ge,lt,le (>,>=,<,<=) because a<b and b>a do different
Expand Down Expand Up @@ -419,6 +420,8 @@ struct CfInstruction {
CF_INT_MUL,
CF_INT_SDIV, // signed division, example with 8 bits: 255 / 2 = (-1) / 2 = 0
CF_INT_UDIV, // unsigned division: 255 / 2 = 127
CF_INT_SMOD, // remainder of signed division
CF_INT_UMOD, // remainder of unsigned division
CF_INT_EQ,
CF_INT_LT,
CF_INT_CAST,
Expand Down
5 changes: 4 additions & 1 deletion src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ static AstExpression build_operator_expression(const Token *t, int arity, const
} else if (is_operator(t, "/")) {
assert(arity == 2);
result.kind = AST_EXPR_DIV;
} else if (is_operator(t, "%")) {
assert(arity == 2);
result.kind = AST_EXPR_MOD;
} else if (is_keyword(t, "and")) {
assert(arity == 2);
result.kind = AST_EXPR_AND;
Expand Down Expand Up @@ -436,7 +439,7 @@ static AstExpression parse_expression_with_unary_operators(const Token **tokens)
static AstExpression parse_expression_with_mul_and_div(const Token **tokens)
{
AstExpression result = parse_expression_with_unary_operators(tokens);
while (is_operator(*tokens, "*") || is_operator(*tokens, "/"))
while (is_operator(*tokens, "*") || is_operator(*tokens, "/") || is_operator(*tokens, "%"))
add_to_binop(tokens, &result, parse_expression_with_unary_operators);
return result;
}
Expand Down
5 changes: 5 additions & 0 deletions src/print.c
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ static void print_ast_expression(const AstExpression *expr, struct TreePrinter t
case AST_EXPR_SUB: puts("sub"); n=2; break;
case AST_EXPR_MUL: puts("mul"); n=2; break;
case AST_EXPR_DIV: puts("div"); n=2; break;
case AST_EXPR_MOD: puts("mod"); n=2; break;
case AST_EXPR_AND: puts("and"); n=2; break;
case AST_EXPR_OR: puts("or"); n=2; break;
}
Expand Down Expand Up @@ -410,6 +411,8 @@ static void print_cf_instruction(const CfInstruction *ins, int indent)
case CF_INT_MUL:
case CF_INT_SDIV:
case CF_INT_UDIV:
case CF_INT_SMOD:
case CF_INT_UMOD:
case CF_INT_EQ:
case CF_INT_LT:
case CF_PTR_EQ:
Expand All @@ -419,6 +422,8 @@ static void print_cf_instruction(const CfInstruction *ins, int indent)
case CF_INT_MUL: printf("imul "); break;
case CF_INT_SDIV: printf("sdiv "); break;
case CF_INT_UDIV: printf("udiv "); break;
case CF_INT_SMOD: printf("smod "); break;
case CF_INT_UMOD: printf("umod "); break;
case CF_INT_EQ: printf("ieq "); break;
case CF_INT_LT: printf("ilt "); break;
case CF_PTR_EQ: printf("ptreq "); break;
Expand Down
4 changes: 2 additions & 2 deletions src/tokenize.c
Original file line number Diff line number Diff line change
Expand Up @@ -271,15 +271,15 @@ static char read_char_literal(struct State *st)
return result;
}

static const char operatorChars[] = "=<>!.,()[]{};:+-*/&";
static const char operatorChars[] = "=<>!.,()[]{};:+-*/&%";

static const char *read_operator(struct State *st)
{
const char *operators[] = {
// Longer operators first, so that '==' does not parse as '=' '='
"...", "===", "!==",
"==", "!=", "->", "<=", ">=", "++", "--",
".", ",", ":", ";", "=", "(", ")", "{", "}", "[", "]", "&", "*", "/", "+", "-", "<", ">",
".", ",", ":", ";", "=", "(", ")", "{", "}", "[", "]", "&", "%", "*", "/", "+", "-", "<", ">",
NULL,
};

Expand Down
6 changes: 5 additions & 1 deletion src/typecheck.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ static const Type *check_binop(
case AST_EXPR_SUB: do_what = "subtract"; break;
case AST_EXPR_MUL: do_what = "multiply"; break;
case AST_EXPR_DIV: do_what = "divide"; break;
case AST_EXPR_MOD: do_what = "take remainder with"; break;

case AST_EXPR_EQ:
case AST_EXPR_NE:
Expand Down Expand Up @@ -214,7 +215,7 @@ static const Type *check_binop(
if (got_integers) {
cast_type = get_integer_type(
max(lhstypes->type->data.width_in_bits, rhstypes->type->data.width_in_bits),
lhstypes->type->kind == TYPE_SIGNED_INTEGER || lhstypes->type->kind == TYPE_SIGNED_INTEGER
lhstypes->type->kind == TYPE_SIGNED_INTEGER || rhstypes->type->kind == TYPE_SIGNED_INTEGER
);
}
if (got_pointers) {
Expand All @@ -228,6 +229,7 @@ static const Type *check_binop(
case AST_EXPR_SUB:
case AST_EXPR_MUL:
case AST_EXPR_DIV:
case AST_EXPR_MOD:
return cast_type;
case AST_EXPR_EQ:
case AST_EXPR_NE:
Expand Down Expand Up @@ -263,6 +265,7 @@ static const char *short_expression_description(const AstExpression *expr)
case AST_EXPR_SUB:
case AST_EXPR_MUL:
case AST_EXPR_DIV:
case AST_EXPR_MOD:
case AST_EXPR_NEG:
return "the result of a calculation";

Expand Down Expand Up @@ -560,6 +563,7 @@ static ExpressionTypes *typecheck_expression(TypeContext *ctx, const AstExpressi
case AST_EXPR_SUB:
case AST_EXPR_MUL:
case AST_EXPR_DIV:
case AST_EXPR_MOD:
case AST_EXPR_EQ:
case AST_EXPR_NE:
case AST_EXPR_GT:
Expand Down
10 changes: 5 additions & 5 deletions tests/should_succeed/divide.jou
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ def main() -> int:
# With signed division, 8-bit 255/2 would be (-1)/2 which truncates to 0.
printf("%d / %d = %d\n", 0xff as byte, 2 as byte, (0xff as byte)/(2 as byte)) # Output: 255 / 2 = 127

# Dividing ints is signed 32-bit division.
printf("%d / %d = %d\n", 7, 2, 7/2) # Output: 7 / 2 = 3
printf("%d / %d = %d\n", 0-7, 2, (0-7)/2) # Output: -7 / 2 = -3
printf("%d / %d = %d\n", 7, 0-2, 7/(0-2)) # Output: 7 / -2 = -3
printf("%d / %d = %d\n", 0-7, 0-2, (0-7)/(0-2)) # Output: -7 / -2 = 3
# Dividing ints is signed 32-bit floor division.
printf("%d / %d = %d\n", 7, 2, 7/2) # Output: 7 / 2 = 3
printf("%d / %d = %d\n", -7, 2, (-7)/2) # Output: -7 / 2 = -4
printf("%d / %d = %d\n", 7, -2, 7/(-2)) # Output: 7 / -2 = -4
printf("%d / %d = %d\n", -7, -2, (-7)/(-2)) # Output: -7 / -2 = 3

# Output: 6 3 2 1
x = side_effect(6)/side_effect(3)/side_effect(2)
Expand Down
3 changes: 1 addition & 2 deletions tests/should_succeed/for.jou
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ def main() -> int:

# Output: 0...4...8.
for i = 0; i < 10; i = i+1:
# TODO: % operator
if 4*(i/4) != i:
if i % 4 != 0:
printf(".")
continue
printf("yooooooo\n") # Warning: this code will never run
Expand Down
32 changes: 32 additions & 0 deletions tests/should_succeed/mod.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from "stdlib/io.jou" import printf

def main() -> int:
# Output: 222222
printf(
"%d%d%d%d%d%d\n",
7 % 5,
(-3) % 5,
7 % (5 as byte),
(-3) % (5 as byte),
(7 as byte) % 5,
(7 as byte) % (5 as byte),
)

# Mod with negative number --> negative result
# Output: -3 -3 -3 -3 -3
printf(
"%d %d %d %d %d\n",
7 % (-5),
2 % (-5),
(-3) % (-5),
(7 as byte) % (-5),
(2 as byte) % (-5),
)

# Consistency between % and /. No output expected.
for a = -5; a < 5; a++:
for b = -5; b < 5; b++:
if b != 0 and a/b*b + a%b != a:
printf("remainder doesn't work correctly when dividing %d by %d (div=%d, rem=%d)\n", a, b, a/b, a%b)

return 0
3 changes: 1 addition & 2 deletions tests/should_succeed/while.jou
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ def main() -> int:
# Output: 0...4...8.
i = 0
while i < 10:
# TODO: % operator
if 4*(i/4) != i:
if i % 4 != 0:
printf(".")
i = i+1
continue
Expand Down
2 changes: 2 additions & 0 deletions tests/wrong_type/mod.jou
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def main() -> int:
x = "hey %s" % 1 # Error: wrong types: cannot take remainder with byte* and int

0 comments on commit da62b41

Please sign in to comment.