diff --git a/CHANGELOG.md b/CHANGELOG.md index bd1d01aa..9e8490e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ - Fixed type signatures of `reduce` and `reduceWithIndex`. https://github.com/rescript-association/rescript-core/pull/49 - Add `panic`/`Error.panic`. https://github.com/rescript-association/rescript-core/pull/72 - The globally available `null` value now originates from `Nullable` and not `Null`, just like the globally available `undefined` value does. https://github.com/rescript-association/rescript-core/pull/88 +- Add `Int.range` and `Int.rangeWithOptions`, https://github.com/rescript-association/rescript-core/pull/52 ### Documentation diff --git a/src/Core__Int.mjs b/src/Core__Int.mjs index 98b0dc45..1ad4aea3 100644 --- a/src/Core__Int.mjs +++ b/src/Core__Int.mjs @@ -1,5 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE +import * as Pervasives from "rescript/lib/es6/pervasives.js"; +import * as Core__Array from "./Core__Array.mjs"; function fromString(radix, x) { var maybeInt = radix !== undefined ? parseInt(x, radix) : parseInt(x); @@ -10,6 +12,41 @@ function fromString(radix, x) { } } +function rangeWithOptions(start, end, options) { + var isInverted = start > end; + var n = options.step; + var step; + if (n !== undefined) { + if (n !== 0) { + step = n; + } else { + if (start !== end) { + throw new RangeError("Incorrect range arguments"); + } + step = n; + } + } else { + step = isInverted ? -1 : 1; + } + var length; + if (isInverted === step >= 0) { + length = 0; + } else if (step === 0) { + length = options.inclusive === true ? 1 : 0; + } else { + var range = isInverted ? start - end | 0 : end - start | 0; + var range$1 = options.inclusive === true ? range + 1 | 0 : range; + length = Math.ceil(range$1 / Pervasives.abs(step)) | 0; + } + return Core__Array.fromInitializer(length, (function (i) { + return start + Math.imul(i, step) | 0; + })); +} + +function range(start, end) { + return rangeWithOptions(start, end, {}); +} + var Constants = { minValue: -2147483648, maxValue: 2147483647 @@ -18,5 +55,7 @@ var Constants = { export { Constants , fromString , + range , + rangeWithOptions , } /* No side effect */ diff --git a/src/Core__Int.res b/src/Core__Int.res index 29172623..a4f0e42e 100644 --- a/src/Core__Int.res +++ b/src/Core__Int.res @@ -35,3 +35,29 @@ let fromString = (~radix=?, x) => { } external mod: (int, int) => int = "%modint" + +type rangeOptions = {step?: int, inclusive?: bool} +let rangeWithOptions = (start, end, options) => { + let isInverted = start > end + + let step = switch options.step { + | None => isInverted ? -1 : 1 + | Some(0) if start !== end => + Core__Error.raise(Core__Error.RangeError.make("Incorrect range arguments")) + | Some(n) => n + } + + let length = if isInverted === (step >= 0) { + 0 // infinite because step goes in opposite direction of end + } else if step == 0 { + options.inclusive === Some(true) ? 1 : 0 + } else { + let range = isInverted ? start - end : end - start + let range = options.inclusive === Some(true) ? range + 1 : range + ceil(float(range) /. float(abs(step)))->Core__Float.toInt + } + + Core__Array.fromInitializer(~length, i => start + i * step) +} + +let range = (start, end) => rangeWithOptions(start, end, {}) diff --git a/src/Core__Int.resi b/src/Core__Int.resi index 68498f18..d1a8bcfb 100644 --- a/src/Core__Int.resi +++ b/src/Core__Int.resi @@ -268,3 +268,57 @@ Int.mod(7, 4) == 3 ``` */ external mod: (int, int) => int = "%modint" + +/** +`range(start, end)` returns an int array of the sequence of integers in the +range `[start, end)`. That is, including `start` but excluding `end`. + +If `start < end` the sequence will be increasing in steps of 1. + +If `start > end` the sequence will be decreasing in steps of -1. + +This is equivalent to `rangeWithOptions` with `inclusive` set to `false` and +`step` set to `1` if `start < end` and `-1` otherwise. + +## Examples + +```rescript +Int.range(3, 6) == [3, 4, 5] +Int.range(-3, -1) == [-3, -2] +Int.range(3, 1) == [3, 2] +``` +*/ +let range: (int, int) => array + +/** +The options for `rangeWithOptions`. +*/ +type rangeOptions = {step?: int, inclusive?: bool} + +/** +`rangeWithOptions(start, end, options)` is like `range`, but with `step` and +`inclusive` options configurable. + +If `step` is set, the sequence will increase or decrease by that amount for each +step. If `start < end` and `step` is negative, or vice versa, an empty array is +returned since the sequence would otherwise never reach or exceed the end value +and hence be infinite. If `step` is `0` and `start !=` end, a `RangeError` is +raised as the sequence would never reach or exceed the end value and hence be +infinite. + +If `inclusive` is set to `true`, the sequence will include `end` if `step` is +set such that the sequence includes it. + +## Examples + +```rescript +Int.rangeWithOptions(3, 7, {step: 2}) == [3, 5] +Int.rangeWithOptions(3, 7, {step: 2, inclusive: true}) == [3, 5, 7] +Int.rangeWithOptions(3, 6, {step: -2}) // RangeError +``` + +## Exceptions + +- Raises `RangeError` if `step == 0 && start != end`. +*/ +let rangeWithOptions: (int, int, rangeOptions) => array diff --git a/test/IntTests.mjs b/test/IntTests.mjs new file mode 100644 index 00000000..30daefe0 --- /dev/null +++ b/test/IntTests.mjs @@ -0,0 +1,466 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Test from "./Test.mjs"; +import * as Curry from "rescript/lib/es6/curry.js"; +import * as Js_exn from "rescript/lib/es6/js_exn.js"; +import * as Caml_obj from "rescript/lib/es6/caml_obj.js"; +import * as Core__Int from "../src/Core__Int.mjs"; +import * as Pervasives from "rescript/lib/es6/pervasives.js"; +import * as Caml_js_exceptions from "rescript/lib/es6/caml_js_exceptions.js"; + +var eq = Caml_obj.equal; + +function $$catch(f) { + try { + Curry._1(f, undefined); + return Pervasives.failwith("no exception raised"); + } + catch (raw_err){ + var err = Caml_js_exceptions.internalToOCamlException(raw_err); + if (err.RE_EXN_ID === Js_exn.$$Error) { + return err._1; + } + throw err; + } +} + +Test.run([ + [ + "IntTests.res", + 13, + 20, + 50 + ], + "range - positive, increasing" + ], Core__Int.range(3, 6), eq, [ + 3, + 4, + 5 + ]); + +Test.run([ + [ + "IntTests.res", + 14, + 20, + 50 + ], + "range - negative, increasing" + ], Core__Int.range(-3, -1), eq, [ + -3, + -2 + ]); + +Test.run([ + [ + "IntTests.res", + 15, + 20, + 51 + ], + "range - cross-zero, incresing" + ], Core__Int.range(-1, 2), eq, [ + -1, + 0, + 1 + ]); + +Test.run([ + [ + "IntTests.res", + 16, + 20, + 42 + ], + "range - start == end" + ], Core__Int.range(3, 3), eq, []); + +Test.run([ + [ + "IntTests.res", + 17, + 20, + 50 + ], + "range - positive, decreasing" + ], Core__Int.range(3, 1), eq, [ + 3, + 2 + ]); + +Test.run([ + [ + "IntTests.res", + 18, + 20, + 50 + ], + "range - negative, decreasing" + ], Core__Int.range(-1, -3), eq, [ + -1, + -2 + ]); + +Test.run([ + [ + "IntTests.res", + 21, + 13, + 62 + ], + "rangeWithOptions - positive, increasing, step 2" + ], Core__Int.rangeWithOptions(3, 6, { + step: 2 + }), eq, [ + 3, + 5 + ]); + +Test.run([ + [ + "IntTests.res", + 27, + 13, + 62 + ], + "rangeWithOptions + positive, increasing, step 2" + ], Core__Int.rangeWithOptions(3, 7, { + step: 2 + }), eq, [ + 3, + 5 + ]); + +Test.run([ + [ + "IntTests.res", + 33, + 13, + 62 + ], + "rangeWithOptions + positive, increasing, step 2" + ], Core__Int.rangeWithOptions(3, 8, { + step: 2 + }), eq, [ + 3, + 5, + 7 + ]); + +Test.run([ + [ + "IntTests.res", + 39, + 13, + 62 + ], + "rangeWithOptions - negative, increasing, step 2" + ], Core__Int.rangeWithOptions(-6, -3, { + step: 2 + }), eq, [ + -6, + -4 + ]); + +Test.run([ + [ + "IntTests.res", + 45, + 13, + 62 + ], + "rangeWithOptions - positive, increasing, step 0" + ], $$catch(function (param) { + return Core__Int.rangeWithOptions(3, 6, { + step: 0 + }); + }), eq, new RangeError("Incorrect range arguments")); + +Test.run([ + [ + "IntTests.res", + 51, + 13, + 54 + ], + "rangeWithOptions - start == end, step 0" + ], Core__Int.rangeWithOptions(3, 3, { + step: 0 + }), eq, []); + +Test.run([ + [ + "IntTests.res", + 57, + 13, + 63 + ], + "rangeWithOptions + positive, increasing, step -1" + ], Core__Int.rangeWithOptions(3, 6, { + step: -1 + }), eq, []); + +Test.run([ + [ + "IntTests.res", + 63, + 13, + 62 + ], + "rangeWithOptions + positive, decreasing, step 1" + ], Core__Int.rangeWithOptions(6, 3, { + step: 1 + }), eq, []); + +Test.run([ + [ + "IntTests.res", + 69, + 13, + 63 + ], + "rangeWithOptions + positive, decreasing, step -2" + ], Core__Int.rangeWithOptions(6, 3, { + step: -2 + }), eq, [ + 6, + 4 + ]); + +Test.run([ + [ + "IntTests.res", + 75, + 13, + 63 + ], + "rangeWithOptions + positive, increasing, step -2" + ], Core__Int.rangeWithOptions(6, 2, { + step: -2 + }), eq, [ + 6, + 4 + ]); + +Test.run([ + [ + "IntTests.res", + 81, + 13, + 63 + ], + "rangeWithOptions + positive, increasing, step -2" + ], Core__Int.rangeWithOptions(6, 1, { + step: -2 + }), eq, [ + 6, + 4, + 2 + ]); + +Test.run([ + [ + "IntTests.res", + 87, + 13, + 63 + ], + "rangeWithOptions + negative, decreasing, step -2" + ], Core__Int.rangeWithOptions(-3, -6, { + step: -2 + }), eq, [ + -3, + -5 + ]); + +Test.run([ + [ + "IntTests.res", + 93, + 13, + 73 + ], + "rangeWithOptions - positive, increasing, step 2, inclusive" + ], Core__Int.rangeWithOptions(3, 6, { + step: 2, + inclusive: true + }), eq, [ + 3, + 5 + ]); + +Test.run([ + [ + "IntTests.res", + 99, + 13, + 73 + ], + "rangeWithOptions + positive, increasing, step 2, inclusive" + ], Core__Int.rangeWithOptions(3, 7, { + step: 2, + inclusive: true + }), eq, [ + 3, + 5, + 7 + ]); + +Test.run([ + [ + "IntTests.res", + 105, + 13, + 73 + ], + "rangeWithOptions + positive, increasing, step 2, inclusive" + ], Core__Int.rangeWithOptions(3, 8, { + step: 2, + inclusive: true + }), eq, [ + 3, + 5, + 7 + ]); + +Test.run([ + [ + "IntTests.res", + 111, + 13, + 73 + ], + "rangeWithOptions - negative, increasing, step 2, inclusive" + ], Core__Int.rangeWithOptions(-6, -3, { + step: 2, + inclusive: true + }), eq, [ + -6, + -4 + ]); + +Test.run([ + [ + "IntTests.res", + 117, + 13, + 73 + ], + "rangeWithOptions - positive, increasing, step 0, inclusive" + ], $$catch(function (param) { + return Core__Int.rangeWithOptions(3, 6, { + step: 0, + inclusive: true + }); + }), eq, new RangeError("Incorrect range arguments")); + +Test.run([ + [ + "IntTests.res", + 123, + 13, + 65 + ], + "rangeWithOptions - start == end, step 0, inclusive" + ], Core__Int.rangeWithOptions(3, 3, { + step: 0, + inclusive: true + }), eq, [3]); + +Test.run([ + [ + "IntTests.res", + 129, + 13, + 74 + ], + "rangeWithOptions + positive, increasing, step -1, inclusive" + ], Core__Int.rangeWithOptions(3, 6, { + step: -1, + inclusive: true + }), eq, []); + +Test.run([ + [ + "IntTests.res", + 135, + 13, + 73 + ], + "rangeWithOptions + positive, decreasing, step 1, inclusive" + ], Core__Int.rangeWithOptions(6, 3, { + step: 1, + inclusive: true + }), eq, []); + +Test.run([ + [ + "IntTests.res", + 141, + 13, + 74 + ], + "rangeWithOptions + positive, decreasing, step -2, inclusive" + ], Core__Int.rangeWithOptions(6, 3, { + step: -2, + inclusive: true + }), eq, [ + 6, + 4 + ]); + +Test.run([ + [ + "IntTests.res", + 147, + 13, + 74 + ], + "rangeWithOptions + positive, increasing, step -2, inclusive" + ], Core__Int.rangeWithOptions(6, 2, { + step: -2, + inclusive: true + }), eq, [ + 6, + 4, + 2 + ]); + +Test.run([ + [ + "IntTests.res", + 153, + 13, + 74 + ], + "rangeWithOptions + positive, increasing, step -2, inclusive" + ], Core__Int.rangeWithOptions(6, 1, { + step: -2, + inclusive: true + }), eq, [ + 6, + 4, + 2 + ]); + +Test.run([ + [ + "IntTests.res", + 159, + 13, + 74 + ], + "rangeWithOptions + negative, decreasing, step -2, inclusive" + ], Core__Int.rangeWithOptions(-3, -6, { + step: -2, + inclusive: true + }), eq, [ + -3, + -5 + ]); + +export { + eq , + $$catch , +} +/* Not a pure module */ diff --git a/test/IntTests.res b/test/IntTests.res new file mode 100644 index 00000000..373cf8c9 --- /dev/null +++ b/test/IntTests.res @@ -0,0 +1,163 @@ +open RescriptCore + +let eq = (a, b) => a == b + +let catch = f => + try { + let _ = f() + failwith("no exception raised") + } catch { + | Exn.Error(err) => err + } + +Test.run(__POS_OF__("range - positive, increasing"), Int.range(3, 6), eq, [3, 4, 5]) +Test.run(__POS_OF__("range - negative, increasing"), Int.range(-3, -1), eq, [-3, -2]) +Test.run(__POS_OF__("range - cross-zero, incresing"), Int.range(-1, 2), eq, [-1, 0, 1]) +Test.run(__POS_OF__("range - start == end"), Int.range(3, 3), eq, []) +Test.run(__POS_OF__("range - positive, decreasing"), Int.range(3, 1), eq, [3, 2]) +Test.run(__POS_OF__("range - negative, decreasing"), Int.range(-1, -3), eq, [-1, -2]) + +Test.run( + __POS_OF__("rangeWithOptions - positive, increasing, step 2"), + Int.rangeWithOptions(3, 6, {step: 2}), + eq, + [3, 5], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step 2"), + Int.rangeWithOptions(3, 7, {step: 2}), + eq, + [3, 5], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step 2"), + Int.rangeWithOptions(3, 8, {step: 2}), + eq, + [3, 5, 7], +) +Test.run( + __POS_OF__("rangeWithOptions - negative, increasing, step 2"), + Int.rangeWithOptions(-6, -3, {step: 2}), + eq, + [-6, -4], +) +Test.run( + __POS_OF__("rangeWithOptions - positive, increasing, step 0"), + catch(() => Int.rangeWithOptions(3, 6, {step: 0})), + eq, + Error.RangeError.make("Incorrect range arguments"), +) +Test.run( + __POS_OF__("rangeWithOptions - start == end, step 0"), + Int.rangeWithOptions(3, 3, {step: 0}), + eq, + [], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step -1"), + Int.rangeWithOptions(3, 6, {step: -1}), + eq, + [], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, decreasing, step 1"), + Int.rangeWithOptions(6, 3, {step: 1}), + eq, + [], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, decreasing, step -2"), + Int.rangeWithOptions(6, 3, {step: -2}), + eq, + [6, 4], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step -2"), + Int.rangeWithOptions(6, 2, {step: -2}), + eq, + [6, 4], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step -2"), + Int.rangeWithOptions(6, 1, {step: -2}), + eq, + [6, 4, 2], +) +Test.run( + __POS_OF__("rangeWithOptions + negative, decreasing, step -2"), + Int.rangeWithOptions(-3, -6, {step: -2}), + eq, + [-3, -5], +) +Test.run( + __POS_OF__("rangeWithOptions - positive, increasing, step 2, inclusive"), + Int.rangeWithOptions(3, 6, {step: 2, inclusive: true}), + eq, + [3, 5], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step 2, inclusive"), + Int.rangeWithOptions(3, 7, {step: 2, inclusive: true}), + eq, + [3, 5, 7], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step 2, inclusive"), + Int.rangeWithOptions(3, 8, {step: 2, inclusive: true}), + eq, + [3, 5, 7], +) +Test.run( + __POS_OF__("rangeWithOptions - negative, increasing, step 2, inclusive"), + Int.rangeWithOptions(-6, -3, {step: 2, inclusive: true}), + eq, + [-6, -4], +) +Test.run( + __POS_OF__("rangeWithOptions - positive, increasing, step 0, inclusive"), + catch(() => Int.rangeWithOptions(3, 6, {step: 0, inclusive: true})), + eq, + Error.RangeError.make("Incorrect range arguments"), +) +Test.run( + __POS_OF__("rangeWithOptions - start == end, step 0, inclusive"), + Int.rangeWithOptions(3, 3, {step: 0, inclusive: true}), + eq, + [3], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step -1, inclusive"), + Int.rangeWithOptions(3, 6, {step: -1, inclusive: true}), + eq, + [], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, decreasing, step 1, inclusive"), + Int.rangeWithOptions(6, 3, {step: 1, inclusive: true}), + eq, + [], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, decreasing, step -2, inclusive"), + Int.rangeWithOptions(6, 3, {step: -2, inclusive: true}), + eq, + [6, 4], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step -2, inclusive"), + Int.rangeWithOptions(6, 2, {step: -2, inclusive: true}), + eq, + [6, 4, 2], +) +Test.run( + __POS_OF__("rangeWithOptions + positive, increasing, step -2, inclusive"), + Int.rangeWithOptions(6, 1, {step: -2, inclusive: true}), + eq, + [6, 4, 2], +) +Test.run( + __POS_OF__("rangeWithOptions + negative, decreasing, step -2, inclusive"), + Int.rangeWithOptions(-3, -6, {step: -2, inclusive: true}), + eq, + [-3, -5], +) diff --git a/test/TestSuite.mjs b/test/TestSuite.mjs index 64ebfbc1..77733822 100644 --- a/test/TestSuite.mjs +++ b/test/TestSuite.mjs @@ -1,7 +1,8 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -import * as ErrorTests from "./ErrorTests.mjs"; +import * as IntTests from "./IntTests.mjs"; import * as ArrayTests from "./ArrayTests.mjs"; +import * as ErrorTests from "./ErrorTests.mjs"; import * as PromiseTest from "./PromiseTest.mjs"; var TestError = PromiseTest.TestError; @@ -22,7 +23,9 @@ var Concurrently = PromiseTest.Concurrently; var panicTest = ErrorTests.panicTest; -var eq = ArrayTests.eq; +var eq = IntTests.eq; + +var $$catch = IntTests.$$catch; export { TestError , @@ -35,5 +38,6 @@ export { Concurrently , panicTest , eq , + $$catch , } -/* ArrayTests Not a pure module */ +/* IntTests Not a pure module */ diff --git a/test/TestSuite.res b/test/TestSuite.res index e093f677..4185534f 100644 --- a/test/TestSuite.res +++ b/test/TestSuite.res @@ -1,3 +1,4 @@ include PromiseTest include ErrorTests include ArrayTests +include IntTests