Skip to content

Commit 3953f43

Browse files
committed
libexpr: add modulo operator
1 parent 47e2381 commit 3953f43

File tree

8 files changed

+70
-1
lines changed

8 files changed

+70
-1
lines changed

doc/manual/redirects.js

+1
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ const redirects = {
208208
"builtin-listToAttrs": "language/builtins.html#builtins-listToAttrs",
209209
"builtin-map": "language/builtins.html#builtins-map",
210210
"builtin-match": "language/builtins.html#builtins-match",
211+
"builtin-mod": "language/builtins.html#builtins-mod",
211212
"builtin-mul": "language/builtins.html#builtins-mul",
212213
"builtin-parseDrvName": "language/builtins.html#builtins-parseDrvName",
213214
"builtin-path": "language/builtins.html#builtins-path",

doc/manual/source/language/operators.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
| List concatenation | *list* `++` *list* | right | 5 |
1010
| [Multiplication][arithmetic] | *number* `*` *number* | left | 6 |
1111
| [Division][arithmetic] | *number* `/` *number* | left | 6 |
12+
| [Modulo][arithmetic] | *number* `%` *number* | left | 6 |
1213
| [Subtraction][arithmetic] | *number* `-` *number* | left | 7 |
1314
| [Addition][arithmetic] | *number* `+` *number* | left | 7 |
1415
| [String concatenation] | *string* `+` *string* | left | 7 |
@@ -88,6 +89,7 @@ Pure integer operations will always return integers, whereas any operation invol
8889

8990
Evaluation of the following numeric operations throws an evaluation error:
9091
- Division by zero
92+
- Modulo by zero
9193
- Integer overflow, that is, any operation yielding a result outside of the representable range of [Nix language integers](./syntax.md#number-literal)
9294

9395
See also [Comparison] and [Equality].

src/libexpr-tests/error_traces.cc

+18
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,24 @@ namespace nix {
10441044
}
10451045

10461046

1047+
TEST_F(ErrorTraceTest, mod) {
1048+
ASSERT_TRACE2("mod \"foo\" 1",
1049+
TypeError,
1050+
HintFmt("expected an integer but found %s: %s", "a string", Uncolored(ANSI_MAGENTA "\"foo\"" ANSI_NORMAL)),
1051+
HintFmt("while evaluating the first operand of the modulo"));
1052+
1053+
ASSERT_TRACE2("mod 1 \"foo\"",
1054+
TypeError,
1055+
HintFmt("expected an integer but found %s: %s", "a string", Uncolored(ANSI_MAGENTA "\"foo\"" ANSI_NORMAL)),
1056+
HintFmt("while evaluating the second operand of the modulo"));
1057+
1058+
ASSERT_TRACE1("mod \"foo\" 0",
1059+
EvalError,
1060+
HintFmt("modulo by zero"));
1061+
1062+
}
1063+
1064+
10471065
TEST_F(ErrorTraceTest, bitAnd) {
10481066
ASSERT_TRACE2("bitAnd 1.1 2",
10491067
TypeError,

src/libexpr-tests/primops.cc

+24
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,30 @@ namespace nix {
503503
ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError);
504504
}
505505

506+
TEST_F(PrimOpTest, modPosPos) {
507+
auto v = eval("builtins.mod 5 2");
508+
ASSERT_THAT(v, IsIntEq(1));
509+
}
510+
511+
TEST_F(PrimOpTest, modPosNeg) {
512+
auto v = eval("builtins.mod 5 (-2)");
513+
ASSERT_THAT(v, IsIntEq(1));
514+
}
515+
516+
TEST_F(PrimOpTest, modNegPos) {
517+
auto v = eval("builtins.mod (-5) 2");
518+
ASSERT_THAT(v, IsIntEq(-1));
519+
}
520+
521+
TEST_F(PrimOpTest, modNegNeg) {
522+
auto v = eval("builtins.mod (-5) (-2)");
523+
ASSERT_THAT(v, IsIntEq(-1));
524+
}
525+
526+
TEST_F(PrimOpTest, modZero) {
527+
ASSERT_THROW(eval("builtins.mod 5 0"), EvalError);
528+
}
529+
506530
TEST_F(PrimOpTest, bitOr) {
507531
auto v = eval("builtins.bitOr 1 2");
508532
ASSERT_THAT(v, IsIntEq(3));

src/libexpr/eval.cc

+1
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ EvalState::EvalState(
238238
.lessThan = symbols.create("__lessThan"),
239239
.mul = symbols.create("__mul"),
240240
.div = symbols.create("__div"),
241+
.mod = symbols.create("__mod"),
241242
.or_ = symbols.create("or"),
242243
.findFile = symbols.create("__findFile"),
243244
.nixPath = symbols.create("__nixPath"),

src/libexpr/nixexpr.hh

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
8080
struct Expr
8181
{
8282
struct AstSymbols {
83-
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
83+
Symbol sub, lessThan, mul, div, mod, or_, findFile, nixPath, body;
8484
};
8585

8686

src/libexpr/parser.y

+2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
173173
%left NOT
174174
%left '+' '-'
175175
%left '*' '/'
176+
%left '%'
176177
%right CONCAT
177178
%nonassoc '?'
178179
%nonassoc NEGATE
@@ -259,6 +260,7 @@ expr_op
259260
| expr_op '-' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.sub), {$1, $3}); }
260261
| expr_op '*' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.mul), {$1, $3}); }
261262
| expr_op '/' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.div), {$1, $3}); }
263+
| expr_op '%' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.mod), {$1, $3}); }
262264
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(state->at(@2), $1, $3); }
263265
| expr_app
264266
;

src/libexpr/primops.cc

+21
Original file line numberDiff line numberDiff line change
@@ -3951,6 +3951,27 @@ static RegisterPrimOp primop_div({
39513951
.fun = prim_div,
39523952
});
39533953

3954+
static void prim_mod(EvalState & state, const PosIdx pos, Value * * args, Value & v)
3955+
{
3956+
auto i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the modulo");
3957+
3958+
if (i2.value == 0)
3959+
state.error<EvalError>("modulo by zero").atPos(pos).debugThrow();
3960+
3961+
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first operand of the modulo");
3962+
3963+
v.mkInt(i1.value % i2.value);
3964+
}
3965+
3966+
static RegisterPrimOp primop_mod({
3967+
.name = "__mod",
3968+
.args = {"e1", "e2"},
3969+
.doc = R"(
3970+
Return the remainder of the division of the numbers *e1* and *e2*.
3971+
)",
3972+
.fun = prim_mod,
3973+
});
3974+
39543975
static void prim_bitAnd(EvalState & state, const PosIdx pos, Value * * args, Value & v)
39553976
{
39563977
auto i1 = state.forceInt(*args[0], pos, "while evaluating the first argument passed to builtins.bitAnd");

0 commit comments

Comments
 (0)