From 624e5057f656fa48e27d6cc9cc04c63557dc6410 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 22 Aug 2025 13:35:17 -0400 Subject: [PATCH 01/24] js-numbers.js: #1799 - Extra arg removed for BigInteger.prototype.{round,roundEven,tan,atan,cos,sin,expt,acos,asin} - bnpExp() should take errbacks arg and use it if exponent too large - bnModPowInt() should take errbacks arg and pass it along to bnpExp() - bnPow() should take errbacks arg and pass it along to bnpExp() --- src/js/base/js-numbers.js | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index bd0fe9523..1ffa28021 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2889,7 +2889,11 @@ define("pyret-base/js/js-numbers", function() { function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) - function bnpExp(e,z) { + function bnpExp(e, z, errbacks) { + if (e > 0xffffffff) { + errbacks.throwDomainError('expt: exponent ' + e + ' too large'); + } + if (e < 1) return BigInteger.ONE; if(e > 0xffffffff || e < 1) return BigInteger.ONE; var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; g.copyTo(r); @@ -2902,10 +2906,10 @@ define("pyret-base/js/js-numbers", function() { } // (public) this^e % m, 0 <= e < 2^32 - function bnModPowInt(e,m) { + function bnModPowInt(e, m, errbacks) { var z; if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.bnpExp(e,z); + return this.bnpExp(e, z, errbacks); } // protected @@ -3274,7 +3278,9 @@ define("pyret-base/js/js-numbers", function() { NullExp.prototype.sqrTo = nSqrTo; // (public) this^e - function bnPow(e) { return this.bnpExp(e,new NullExp()); } + function bnPow(e, errbacks) { + return this.bnpExp(e,new NullExp(), errbacks); + } // (protected) r = lower n words of "this * a", a.t <= n // "this" should be the larger one if appropriate. @@ -3774,48 +3780,48 @@ define("pyret-base/js/js-numbers", function() { // round: -> pyretnum // Round to the nearest integer. - BigInteger.prototype.round = function(n, errbacks) { + BigInteger.prototype.round = function(errbacks) { return this; }; - BigInteger.prototype.roundEven = function(n, errbacks) { + BigInteger.prototype.roundEven = function(errbacks) { return this; }; // log: -> pyretnum // Produce the log. - BigInteger.prototype.log = function(n, errbacks) { + BigInteger.prototype.log = function(errbacks) { return log(this.toFixnum(), errbacks); }; // tan: -> pyretnum // Produce the tan. - BigInteger.prototype.tan = function(n, errbacks) { + BigInteger.prototype.tan = function(errbacks) { return tan(this.toFixnum(), errbacks); }; // atan: -> pyretnum // Produce the arc tangent. - BigInteger.prototype.atan = function(n, errbacks) { + BigInteger.prototype.atan = function(errbacks) { return atan(this.toFixnum(), errbacks); }; // cos: -> pyretnum // Produce the cosine. - BigInteger.prototype.cos = function(n, errbacks) { + BigInteger.prototype.cos = function(errbacks) { return cos(this.toFixnum(), errbacks); }; // sin: -> pyretnum // Produce the sine. - BigInteger.prototype.sin = function(n, errbacks) { + BigInteger.prototype.sin = function(errbacks) { return sin(this.toFixnum(), errbacks); }; // expt: pyretnum -> pyretnum // Produce the power to the input. BigInteger.prototype.expt = function(n, errbacks) { - return bnPow.call(this, n); + return bnPow.call(this, n, errbacks); }; // exp: -> pyretnum @@ -3829,13 +3835,13 @@ define("pyret-base/js/js-numbers", function() { // acos: -> pyretnum // Produce the arc cosine. - BigInteger.prototype.acos = function(n, errbacks) { + BigInteger.prototype.acos = function(errbacks) { return acos(this.toFixnum(), errbacks); }; // asin: -> pyretnum // Produce the arc sine. - BigInteger.prototype.asin = function(n, errbacks) { + BigInteger.prototype.asin = function(errbacks) { return asin(this.toFixnum(), errbacks); }; From c5cd7e12abcbb95ffb7b9369bc212844013a2d40 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 22 Aug 2025 16:04:26 -0400 Subject: [PATCH 02/24] bnpExp(): Don't assume args are JS nums #1799 --- src/js/base/js-numbers.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 1ffa28021..0c4ce7260 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2890,11 +2890,12 @@ define("pyret-base/js/js-numbers", function() { // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) function bnpExp(e, z, errbacks) { - if (e > 0xffffffff) { + if (greaterThan(e, 0xffffffff, errbacks)) { errbacks.throwDomainError('expt: exponent ' + e + ' too large'); } - if (e < 1) return BigInteger.ONE; - if(e > 0xffffffff || e < 1) return BigInteger.ONE; + if (lessThan(e, 1, errbacks)) { + return BigInteger.ONE; + } var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; g.copyTo(r); while(--i >= 0) { From 15e61eb11d7f8e0cda83d025ac653839aca21ea4 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 22 Aug 2025 16:10:02 -0400 Subject: [PATCH 03/24] add tests for when num-expt and num-log raise exception #1799 --- tests/pyret/tests/test-numbers.arr | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index 512052f03..1cc54fb33 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -162,6 +162,7 @@ check: num-log(0) raises "NumPositive" num-log(1) is 0 num-log(num-exp(1)) satisfies around(1, 0.0001) + num-log(num-expt(10, 36789)) raises "roughnum overflow error" 2 is num-exact(2) 1 / 3 is num-exact(1 / 3) @@ -187,6 +188,7 @@ check: num-expt(3, 2) is 9 num-expt("nan", 2) raises "Number" num-expt(2, "nan") raises "Number" + num-expt(7, num-expt(10, 36789)) raises "too large" num-ceiling(2.5) is 3 num-ceiling("nan") raises "Number" From 78d9794f273cd2c9dc6fd1e1da0b248c8ea9689c Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 22 Aug 2025 17:49:44 -0400 Subject: [PATCH 04/24] js-numbers: Improve definition of log so it doesn't give up quickly #1799. In fact, it doesn't give up at all, because any argument that doesn't blow memory necessarily has a tractable logarithm. --- src/js/base/js-numbers.js | 16 +++++++++++++++- tests/pyret/tests/test-numbers.arr | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 0c4ce7260..e4d3d52cc 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -813,6 +813,8 @@ define("pyret-base/js/js-numbers", function() { // NB: all of these trig-gy generic functions should now return roughnum rather than float // (except for an arg of 0, etc) + var ln10 = Math.log(10) + // log: pyretnum -> pyretnum var log = function(n, errbacks) { if ( eqv(n, 1, errbacks) ) { @@ -824,7 +826,19 @@ define("pyret-base/js/js-numbers", function() { if (typeof(n) === 'number') { return Roughnum.makeInstance(Math.log(n), errbacks); } - return n.log(errbacks); + var nFix = n.toFixnum(); + if (typeof(nFix) === 'number' && nFix !== Infinity) { + return Roughnum.makeInstance(Math.log(nFix), errbacks); + } + var nStr = n.round(errbacks).toString(); + var nLen = nStr.length; + var firstFewLen = 10 + if ((nLen % 2) !== 0) { + firstFewLen = 11; + } + var nFirstFew = parseInt(nStr.substring(0, firstFewLen)); + var nLog = Math.log(nFirstFew) + (nLen - 10) * ln10; + return Roughnum.makeInstance(nLog, errbacks); }; // tan: pyretnum -> pyretnum diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index 1cc54fb33..9efa6da08 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -162,7 +162,7 @@ check: num-log(0) raises "NumPositive" num-log(1) is 0 num-log(num-exp(1)) satisfies around(1, 0.0001) - num-log(num-expt(10, 36789)) raises "roughnum overflow error" + num-log(num-expt(10, 36789)) is-roughly ~84709.803986 2 is num-exact(2) 1 / 3 is num-exact(1 / 3) From 27b30f886075c7358142c6cb6426f4b1aa8d3b57 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 22 Aug 2025 18:36:13 -0400 Subject: [PATCH 05/24] use firstFewLen throughout (rather than specific value) #1799 --- src/js/base/js-numbers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index e4d3d52cc..bc4e9f593 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -837,7 +837,7 @@ define("pyret-base/js/js-numbers", function() { firstFewLen = 11; } var nFirstFew = parseInt(nStr.substring(0, firstFewLen)); - var nLog = Math.log(nFirstFew) + (nLen - 10) * ln10; + var nLog = Math.log(nFirstFew) + (nLen - firstFewLen) * ln10; return Roughnum.makeInstance(nLog, errbacks); }; From 10ba82d0751f835c2322272aeeeca49af1c36d90 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 25 Aug 2025 15:14:44 -0400 Subject: [PATCH 06/24] fromFixnum(): fix for argument fixnum is less than about 1e-7, brownplt/code.pyret.org#556 --- src/js/base/js-numbers.js | 29 +++++++++++++++++++++++++---- tests/pyret/tests/test-json.arr | 3 +++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index bd0fe9523..515d9e946 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -173,17 +173,36 @@ define("pyret-base/js/js-numbers", function() { } else { // used to return float, now rational var stringRep = x.toString(); - var match = stringRep.match(/^(.*)\.(.*)$/); + var match = stringRep.match(genScientificPattern); + var factor1 = 1; + if (match) { + var divideP = false; + stringRep = match[1]; + var exponentPart = match[2]; + if (exponentPart.match('^-')) { + divideP = true; + exponentPart = exponentPart.substring(1); + } + var exponentValue = makeBignum("1" + zfill(Number(exponentPart))); + if (divideP) { + factor1 = divide(1, exponentValue); + } + else { + factor1 = exponentValue; + } + } + match = stringRep.match(/^(.*)\.(.*)$/); + var factor2; if (match) { var afterDecimal = parseInt(match[2]); var factorToInt = Math.pow(10, match[2].length); var extraFactor = _integerGcd(factorToInt, afterDecimal); var multFactor = factorToInt / extraFactor; - return Rational.makeInstance(Math.round(x*multFactor), Math.round(factorToInt/extraFactor), errbacks); + factor2 = Rational.makeInstance(Math.round(x*multFactor), Math.round(factorToInt/extraFactor), errbacks); } else { - return Rational.makeInstance(x, 1, errbacks); + factor2 = Rational.makeInstance(Number(stringRep), 1, errbacks); } - + return multiply(factor1, factor2); } }; @@ -2036,6 +2055,8 @@ define("pyret-base/js/js-numbers", function() { var scientificPattern = new RegExp("^([+-]?\\d*\\.?\\d*)[Ee]([+]?\\d+)$"); + var genScientificPattern = new RegExp("^([+-]?\\d*\\.?\\d*)[Ee]([+-]?\\d+)$"); + // fromString: string -> (pyretnum | false) var fromString = function(x, errbacks) { if (x.match(digitRegexp)) { diff --git a/tests/pyret/tests/test-json.arr b/tests/pyret/tests/test-json.arr index b05f14d25..3e5fb46ae 100644 --- a/tests/pyret/tests/test-json.arr +++ b/tests/pyret/tests/test-json.arr @@ -19,6 +19,9 @@ check "conversion": p('[5, null, {"hello": "world"}]') is J.j-arr([list: J.j-num(5), J.j-null, J.j-obj([SD.string-dict: "hello", J.j-str("world")])]) + + p('1E-7').native() is 1e-7 + p('5E-19').native() is 5e-19 end check "native": From d2375203c8b1590f8d8e35a58497d2e3567722f2 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Mon, 25 Aug 2025 20:32:25 -0400 Subject: [PATCH 07/24] - test-numbers.js: ensure errbacks correctly propagated for num-{sin,cos,tan} #1799 - num-{acos,asin,atan} don't have opportunity to trigger errbacks in method, so incorrectness (since corrected) was benign - num-{exp,expt} now show proper error (previous error was incorrect convergence, didn't even trigger errbacks) - num-log now always converges for convergent input (errbacks correctly propagated now, but impossible to trigger) --- tests/pyret/tests/test-numbers.arr | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index 9efa6da08..ecd41e3fe 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -71,19 +71,24 @@ check: num-abs(0) is 0 num-abs(1) is 1 + very-bignum = num-expt(10, 23456) + # These are just sanity end the js-nums library has more rigorous tests # for the accuracy of the trig functions. Here we just make sure the # Pyret functions are bound to plausible underlying operations num-sin(0) is 0 num-sin(3.14 / 2) satisfies around(1, 0.1) num-sin(3.14) satisfies around(0, 0.1) + num-sin(very-bignum) raises "roughnum overflow" num-cos(0) is 1 num-cos(3.14 / 2) satisfies around(0, 0.1) num-cos(3.14) satisfies around(-1, 0.1) + num-cos(very-bignum) raises "roughnum overflow" num-tan(0) is 0 num-tan(3.14 / 4) satisfies around(1, 0.01) + num-tan(very-bignum) raises "roughnum overflow" num-asin(0) is 0 num-asin(1) satisfies around(1.57, 0.01) From 892bb5fd022562272565d7d7f80e85ed940b338b Mon Sep 17 00:00:00 2001 From: Joe Politz Date: Fri, 29 Aug 2025 13:25:37 -0700 Subject: [PATCH 08/24] Add condition to skip deploy trigger on PRs --- .github/workflows/build-and-test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index bbb43157d..6ec85a92f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -32,3 +32,4 @@ jobs: needs: build uses: ./.github/workflows/deploy-trigger.yml secrets: inherit + if: github.event_name != 'pull_request' From 175b9342e23d6db9098d3c00550d913d9bf95d31 Mon Sep 17 00:00:00 2001 From: Joe Politz Date: Fri, 29 Aug 2025 13:25:55 -0700 Subject: [PATCH 09/24] Add condition to skip deployment on pull requests --- .github/workflows/deploy-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-release.yml b/.github/workflows/deploy-release.yml index 79dc80273..487e47bbd 100644 --- a/.github/workflows/deploy-release.yml +++ b/.github/workflows/deploy-release.yml @@ -6,6 +6,7 @@ on: jobs: deploy: + if: github.event_name != 'pull_request' runs-on: ubuntu-latest steps: - name: Trigger pyret-release-update in drydock From 92aee48168c2f236c750e5baf8498892af631949 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sat, 30 Aug 2025 02:52:57 -0400 Subject: [PATCH 10/24] fromFixnum() simplified, with better var names, and comments --- src/js/base/js-numbers.js | 60 +++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 515d9e946..971c43eca 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -173,36 +173,48 @@ define("pyret-base/js/js-numbers", function() { } else { // used to return float, now rational var stringRep = x.toString(); - var match = stringRep.match(genScientificPattern); - var factor1 = 1; - if (match) { - var divideP = false; - stringRep = match[1]; - var exponentPart = match[2]; + var exponentMatch = stringRep.match(genScientificPattern); + var exponentFactor = 1; + if (exponentMatch) { + // x is in scientific notation -- i.e. it has an E; + // find the exponent part, and change x to just the base part; + // calculate exponentFactor, which is the integer 10^exponent + var divideP = false; // divideP==true iff exponent is negative + stringRep = exponentMatch[1]; + x = Number(stringRep); + var exponentPart = exponentMatch[2]; if (exponentPart.match('^-')) { divideP = true; exponentPart = exponentPart.substring(1); } - var exponentValue = makeBignum("1" + zfill(Number(exponentPart))); - if (divideP) { - factor1 = divide(1, exponentValue); - } - else { - factor1 = exponentValue; - } - } - match = stringRep.match(/^(.*)\.(.*)$/); - var factor2; - if (match) { - var afterDecimal = parseInt(match[2]); - var factorToInt = Math.pow(10, match[2].length); - var extraFactor = _integerGcd(factorToInt, afterDecimal); - var multFactor = factorToInt / extraFactor; - factor2 = Rational.makeInstance(Math.round(x*multFactor), Math.round(factorToInt/extraFactor), errbacks); + exponentFactor = makeBignum("1" + zfill(Number(exponentPart))); + if (divideP) exponentFactor = divide(1, exponentFactor); + } + var decimalMatch = stringRep.match(/^.*\.(.*)$/); + var baseFactor; + if (decimalMatch) { + // we convert the after-decimal part to + // afterDecimalNumerator / afterDecimalDenominator + // where these are guaranteed integers + var afterDecimalNumerator = parseInt(decimalMatch[1]); + var afterDecimalDenominator = Math.pow(10, decimalMatch[1].length); + // x can now be the vulgar fraction + // (x * afterDecimalDenominator) / afterDecimalDenominator + // since x * afterDecimalDenominator is guaranteed to be an integer; + // however, we can simplify this multiplier; + // first find gcd(afterDecimalNumerator, afterDecimalDenominator) + var afterDecimalGCD = _integerGcd(afterDecimalNumerator, afterDecimalDenominator); + // the mulitplier is afterDecimalDenominator / afterDecimalGCD + var simplifiedMultipler = afterDecimalDenominator / afterDecimalGCD; + // multiply x by simplifiedMultipler to get an (integer) numerator; + // simplifiedMultipler itself is the denominator + baseFactor = Rational.makeInstance(Math.round(x * simplifiedMultipler), + Math.round(simplifiedMultipler), errbacks); } else { - factor2 = Rational.makeInstance(Number(stringRep), 1, errbacks); + // x is already integer + baseFactor = Rational.makeInstance(x, 1, errbacks); } - return multiply(factor1, factor2); + return multiply(exponentFactor, baseFactor); } }; From 901046fef7b7c03949e9df0de93b11cdedb1e5be Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sat, 30 Aug 2025 09:33:03 -0400 Subject: [PATCH 11/24] - simplify js-numbers.js -> log() and add comments - test-numbers: use Racket value for log of large number in check block --- src/js/base/js-numbers.js | 23 +++++++++++++++++++---- tests/pyret/tests/test-numbers.arr | 2 +- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index bc4e9f593..df44e7dee 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -830,12 +830,27 @@ define("pyret-base/js/js-numbers", function() { if (typeof(nFix) === 'number' && nFix !== Infinity) { return Roughnum.makeInstance(Math.log(nFix), errbacks); } + // at this point, n must be a very large positive number; + // we can safely ignore its fractional part; + // we furthermore need only the integer part's first few digits + // although we must remember the number of digits ignored var nStr = n.round(errbacks).toString(); + // say integer N = yyy...yyyxxx...xxx + // where the number of x's is nx; + // So N ~= yyy...yyy * 10^nx + // We'll first find the common (base 10) log of N + // log10(N) ~= log10(yyy...yyy * 10^nx) + // = log10(yyy...yyy) + nx + // Now to convert this to the natural log + // ln(N) = log10(N) / log10(e) + // = log10(N) * ln(10) + // ~= [log10(yyy...yyy) + nx] * ln(10) + // = log10(yyy...yyy) * ln(10) + nx * ln(10) + // = ln(yyy...yyy) + nx * ln(10) + // JS gives us ln(yyy...yyy) and ln(10) so we have a good + // approximation for ln(N) var nLen = nStr.length; - var firstFewLen = 10 - if ((nLen % 2) !== 0) { - firstFewLen = 11; - } + var firstFewLen = 10; var nFirstFew = parseInt(nStr.substring(0, firstFewLen)); var nLog = Math.log(nFirstFew) + (nLen - firstFewLen) * ln10; return Roughnum.makeInstance(nLog, errbacks); diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index ecd41e3fe..6307b612a 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -167,7 +167,7 @@ check: num-log(0) raises "NumPositive" num-log(1) is 0 num-log(num-exp(1)) satisfies around(1, 0.0001) - num-log(num-expt(10, 36789)) is-roughly ~84709.803986 + num-log(num-expt(10, 36789)) is-roughly ~84709.80298615794 // value from Racket 2 is num-exact(2) 1 / 3 is num-exact(1 / 3) From 29534695602191d576c9b0bcb4e4dadc800582eb Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 3 Sep 2025 12:26:39 -0400 Subject: [PATCH 12/24] Bump firstFewLen to 20 --- src/js/base/js-numbers.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index df44e7dee..ff801b7f9 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -831,10 +831,13 @@ define("pyret-base/js/js-numbers", function() { return Roughnum.makeInstance(Math.log(nFix), errbacks); } // at this point, n must be a very large positive number; + // n > 1e308, i.e, has at least 308 digits; // we can safely ignore its fractional part; - // we furthermore need only the integer part's first few digits - // although we must remember the number of digits ignored var nStr = n.round(errbacks).toString(); + var nLen = nStr.length; + // we furthermore need only the integer part's first few digits + // although we must remember the number of digits ignored; + var firstFewLen = 20; // has to be < 308 // say integer N = yyy...yyyxxx...xxx // where the number of x's is nx; // So N ~= yyy...yyy * 10^nx @@ -849,8 +852,6 @@ define("pyret-base/js/js-numbers", function() { // = ln(yyy...yyy) + nx * ln(10) // JS gives us ln(yyy...yyy) and ln(10) so we have a good // approximation for ln(N) - var nLen = nStr.length; - var firstFewLen = 10; var nFirstFew = parseInt(nStr.substring(0, firstFewLen)); var nLog = Math.log(nFirstFew) + (nLen - firstFewLen) * ln10; return Roughnum.makeInstance(nLog, errbacks); From ae831ebfc6e6727f439c8bee240cbc469c950eb7 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 4 Sep 2025 01:26:08 -0400 Subject: [PATCH 13/24] test-numbers.js: fix Pyret comment --- tests/pyret/tests/test-numbers.arr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index 6307b612a..1e6c7029d 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -167,7 +167,7 @@ check: num-log(0) raises "NumPositive" num-log(1) is 0 num-log(num-exp(1)) satisfies around(1, 0.0001) - num-log(num-expt(10, 36789)) is-roughly ~84709.80298615794 // value from Racket + num-log(num-expt(10, 36789)) is-roughly ~84709.80298615794 # value from Racket 2 is num-exact(2) 1 / 3 is num-exact(1 / 3) From 75da5d72c3cea8c7ae984fc2204da74f95b031f6 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 4 Sep 2025 01:37:25 -0400 Subject: [PATCH 14/24] js-numbers.js -> firstFewLen: set to maximum allowed value, 308 --- src/js/base/js-numbers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index ff801b7f9..b310325e2 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -837,7 +837,7 @@ define("pyret-base/js/js-numbers", function() { var nLen = nStr.length; // we furthermore need only the integer part's first few digits // although we must remember the number of digits ignored; - var firstFewLen = 20; // has to be < 308 + var firstFewLen = 308; // has to be <= 308 // say integer N = yyy...yyyxxx...xxx // where the number of x's is nx; // So N ~= yyy...yyy * 10^nx From 759c7a71564f42dde3deed9b1ff31b9d2b3e63da Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 4 Sep 2025 02:04:05 -0400 Subject: [PATCH 15/24] test-numbers.js: num-log, show comparison w/ Racket, Wolfram --- tests/pyret/tests/test-numbers.arr | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index 1e6c7029d..37fb3e246 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -167,7 +167,27 @@ check: num-log(0) raises "NumPositive" num-log(1) is 0 num-log(num-exp(1)) satisfies around(1, 0.0001) - num-log(num-expt(10, 36789)) is-roughly ~84709.80298615794 # value from Racket + + # in following logs, wolframalpha.com gives a lot more digits; rounding to our precision + + # Racket, Wolfram match Pyret + num-log(9e15) is-roughly ~36.736000972246906 + + # Wolfram gives ~36.841361487904731, Racket matches + num-log(1e16) is-roughly ~36.841361487904734 + + # Racket, Wolfram match + num-log(1e308) is-roughly ~709.1962086421661 + + # Racket gives ~711.49879373516, Wolfram matches + num-log(1e309) is-roughly ~711.4987937351601 + + # Racket gives ~84709.80298615794, Wolfram ~84709.80298615795 + num-log(1e36789) is-roughly ~84709.80298615796 + + # Racket, Wolfram match + # commenting because arg calculation takes much time + # num-log(num-expt(10, 1e5)) is-roughly ~230258.50929940457 2 is num-exact(2) 1 / 3 is num-exact(1 / 3) From 8f711378db0808d842079c809022cf5a40404aaf Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 4 Sep 2025 09:06:24 -0400 Subject: [PATCH 16/24] add tests/jsnums-test/jsnums-test.js to test js-numbers.js internals that can't be tested via Pyret --- tests/jsnums-test/jsnums-test.js | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/jsnums-test/jsnums-test.js diff --git a/tests/jsnums-test/jsnums-test.js b/tests/jsnums-test/jsnums-test.js new file mode 100644 index 000000000..444da477e --- /dev/null +++ b/tests/jsnums-test/jsnums-test.js @@ -0,0 +1,44 @@ +// To test: Build Pyret, then cd to this directory, and type +// node jsnums-test.js + +var Jasmine = require('jasmine'); +var jazz = new Jasmine(); +const R = require("requirejs"); +var build = process.env["PHASE"] || "build/phaseA"; +R.config({ + waitSeconds: 15000, + paths: { + "pyret-base": "../../" + build + } +}); +R(["pyret-base/js/js-numbers"], function(JN) { + var sampleErrorBacks = { + throwDomainError: function() { throw 'domainError'; }, + throwLogNonPositive: function() { throw 'logNonPositive'; }, + }; + describe("check exceptions in js-numbers methods that can't be tested in Pyret", function() { + it("bnpExp", function() { + // BigInteger.*.expt calls bnPow, which calls bnpExp + // shd raise exc for too-large + expect(function() { JN.makeBignum(2).expt(JN.makeBignum(0xffffffff + 1), sampleErrorBacks); }).toThrow('domainError'); + + // BigInteger.*.log + // should raise exception for arg <= 0 + expect(function() { JN.makeBignum(-1).log(sampleErrorBacks); }).toThrow('logNonPositive'); + + // BigInteger.*asin + // should raise exception for arg ∉ [-1, +1] + expect(function() { JN.makeBignum(-1.5).asin(sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(+1.5).asin(sampleErrorBacks); }).toThrow('domainError'); + + // BigInteger.*acos + // should raise exception for arg ∉ [-1, +1] + expect(function() { JN.makeBignum(-1.5).acos(sampleErrorBacks); }).toThrow('domainError'); + expect(function() { JN.makeBignum(+1.5).acos(sampleErrorBacks); }).toThrow('domainError'); + + }); + }); + + jazz.execute(); + +}); From 328277584116a0ec82b84a193e2fca0d3d5dfc91 Mon Sep 17 00:00:00 2001 From: Joe Politz Date: Fri, 22 Aug 2025 15:14:06 -0700 Subject: [PATCH 17/24] add starter2024.arr as a trove module in pyret-lang --- src/arr/trove/starter2024.arr | 510 ++++++++++++++++++++++++++++++++++ 1 file changed, 510 insertions(+) create mode 100644 src/arr/trove/starter2024.arr diff --git a/src/arr/trove/starter2024.arr b/src/arr/trove/starter2024.arr new file mode 100644 index 000000000..91d59d48d --- /dev/null +++ b/src/arr/trove/starter2024.arr @@ -0,0 +1,510 @@ +use context empty-context + +import lists as L +import image as I +import arrays as A +import option as O +import constants as C +import global as G + +provide: negate end + +provide from L: + all, + all2, + any, + append, + distinct, + drop, + each, + each2, + each3, + each3_n, + each4, + each4_n, + each_n, + empty, + filter, + filter-map, + filter-values, + find, + fold, + fold-while, + fold2, + fold3, + fold4, + fold_n, + foldl, + foldr, + get, + is-List, + is-empty, + is-link, + join-str, + join-str-last, + last, + length, + link, + list, + longer-than, + map, + map2, + map2_n, + map3, + map3_n, + map4, + map4_n, + map_n, + member, + member-always, + member-always3, + member-identical, + member-identical3, + member-now, + member-now3, + member-with, + member3, + partition, + push, + range, + range-by, + remove, + repeat, + reverse, + same-length, + set, + shorter-than, + shuffle, + sort, + sort-by, + split-at, + take, + take-while, + + type List, + data List +end + +provide from I: + above, + above-align, + above-align-list, + above-list, + add-line, + below, + below-align, + below-align-list, + below-list, + beside, + beside-align, + beside-align-list, + beside-list, + center-pinhole, + circle, + color-at-position, + color-list-to-bitmap, + color-list-to-image, + color-named, + crop, + draw-pinhole, + ellipse, + empty-color-scene, + empty-image, + empty-scene, + ff-decorative, + ff-default, + ff-modern, + ff-roman, + ff-script, + ff-swiss, + ff-symbol, + ff-system, + flip-horizontal, + flip-horizontal as reflect-y, + flip-vertical, + flip-vertical as reflect-x, + frame, + fs-italic, + fs-normal, + fs-slant, + fw-bold, + fw-light, + fw-normal, + image-baseline, + image-height, + image-pinhole-x, + image-pinhole-y, + image-to-color-list, + image-url, + image-width, + images-difference, + images-equal, + is-FillMode, + is-FontFamily, + is-FontStyle, + is-FontWeight, + is-Point, + is-XPlace, + is-YPlace, + is-angle, + is-ff-decorative, + is-ff-default, + is-ff-modern, + is-ff-roman, + is-ff-script, + is-ff-swiss, + is-ff-symbol, + is-ff-system, + is-fs-italic, + is-fs-normal, + is-fs-slant, + is-fw-bold, + is-fw-light, + is-image, + is-image-color, + is-mode, + is-mode-fade, + is-mode-outline, + is-mode-solid, + is-point-polar, + is-point-xy, + is-side-count, + is-step-count, + is-transparency, + is-x-left, + is-x-middle, + is-x-pinhole, + is-x-place, + is-x-right, + is-y-baseline, + is-y-bottom, + is-y-center, + is-y-pinhole, + is-y-place, + is-y-top, + isosceles-triangle, + line, + mode-fade, + mode-outline, + mode-solid, + move-pinhole, + name-to-color, + overlay, + overlay-align, + overlay-align-list, + overlay-list, + overlay-onto-offset, + overlay-xy, + place-image, + place-image-align, + place-pinhole, + point, + point-polar, + point-polygon, + point-xy, + put-image, + put-image as translate, + radial-star, + rectangle, + reflect-x, + reflect-y, + regular-polygon, + rhombus, + right-triangle, + rotate, + scale, + scale as dilate, + scale-xy, + scene-line, + square, + star, + star-polygon, + star-sized, + text, + text-font, + triangle, + triangle-aas, + triangle-asa, + triangle-ass, + triangle-saa, + triangle-sas, + triangle-ssa, + triangle-sss, + underlay, + underlay-align, + underlay-align-list, + underlay-list, + underlay-xy, + wedge, + x-center, + x-left, + x-middle, + x-pinhole, + x-right, + y-baseline, + y-bottom, + y-center, + y-middle, + y-pinhole, + y-top, + + type FillMode, + type FontFamily, + type FontStyle, + type FontWeight, + type Image, + type Point, + type XPlace, + type YPlace, + + data FillMode, + data FontFamily, + data FontStyle, + data FontWeight, + data Point, + data XPlace, + data YPlace, +end + +provide from A: + array, + array-from-list, + array-get-now, + array-length, + array-of, + array-set-now, + array-to-list-now, + build-array, + is-array, + + type Array +end + +provide from O: + is-Option, + is-none, + is-some, + none, + some, + + type Option, + data Option +end + +provide from C: + PI, + E +end + +provide from G: + _divide, + _greaterequal, + _greaterthan, + _lessequal, + _lessthan, + _minus, + _plus, + _times, + brander, + builtins, + display, + display-error, + equal-always, + equal-always3, + equal-now, + equal-now3, + gensym, + identical, + identical3, + is-boolean, + is-function, + is-nothing, + is-number, + is-object, + is-raw-array, + is-row, + is-string, + is-table, + is-tuple, + not, + nothing, + num-abs, + num-abs as abs, + num-acos, + num-asin, + num-atan, + num-atan2, + num-ceiling, + num-ceiling-digits, + num-ceiling-place, + num-cos, + num-cos as cos, + num-equal, + num-exact, + num-exp, + num-expt, + num-expt as expt, + num-floor, + num-floor-digits, + num-floor-place, + num-is-fixnum, + num-is-integer, + num-is-negative, + num-is-non-negative, + num-is-non-positive, + num-is-positive, + num-is-rational, + num-is-roughnum, + num-log, + num-max, + num-min, + num-modulo, + num-random, + num-random as random, + num-random-seed, + num-remainder, + num-round, + num-round-digits, + num-round-even, + num-round-even-digits, + num-round-even-place, + num-round-place, + num-sin, + num-sin as sin, + num-sqr, + num-sqr as sqr, + num-sqrt, + num-sqrt as sqrt, + num-tan, + num-tan as tan, + num-to-fixnum, + num-to-rational, + num-to-roughnum, + num-to-string, + num-to-string-digits, + num-tostring, + num-truncate, + num-truncate-digits, + num-truncate-place, + num-within, + num-within-abs, + num-within-rel, + print, + print-error, + raise, + random, + raw-array, + raw-array-and-mapi, + raw-array-build, + raw-array-build-opt, + raw-array-concat, + raw-array-filter, + raw-array-fold, + raw-array-from-list, + raw-array-get, + raw-array-length, + raw-array-map, + raw-array-map-1, + raw-array-of, + raw-array-or-mapi, + raw-array-set, + raw-array-sort-nums, + raw-array-sort-by, + raw-array-to-list, + ref-freeze, + ref-get, + ref-set, + roughly-equal, + roughly-equal-always, + roughly-equal-always3, + roughly-equal-now, + roughly-equal-now3, + run-task, + string-append, + string-char-at, + string-contains, + string-ends-with, + string-equal, + string-explode, + string-from-code-point, + string-from-code-points, + string-index-of, + string-find-index, + string-find, + string-find-opt, + string-get-index, + string-is-number, + string-isnumber, + string-length, + string-repeat, + string-replace, + string-split, + string-split-all, + string-starts-with, + string-substring, + string-to-code-point, + string-to-code-points, + string-to-lower, + string-to-number, + string-to-upper, + string-tolower, + string-tonumber, + string-toupper, + test-print, + time-now, + to-repr, + to-string, + torepr, + tostring, + within, + within-abs, + within-abs-now, + within-abs-now3, + within-abs3, + within-now, + within-now3, + within-rel, + within-rel-now, + within-rel-now3, + within-rel3, + within3, + + type Any, + type Method, + type Object, + type Function, + type NumNonNegative, + type NumNonPositive, + type NumNegative, + type NumPositive, + type NumRational, + type NumInteger, + type Roughnum, + type Exactnum, + type Boolean, + type Number, + type String, + type Nothing, + type RawArray, + type Row, + + data Boolean, + data Exactnum, + data Function, + data Method, + data Nothing, + data NumInteger, + data NumNegative, + data NumNonNegative, + data NumNonPositive, + data NumPositive, + data NumRational, + data Number, + data Object, + data Roughnum, + data Row, + data String, + data Table +end + +fun negate(n :: Number) -> Number: n * -1 end From 11cbc5c2122cfec694eb1296080803881278d18a Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Sun, 14 Sep 2025 00:38:21 -0400 Subject: [PATCH 18/24] - define fromFixnum() in terms of fromString() - improve fromString() to use itself recursively rather than makeBignum() --- src/js/base/js-numbers.js | 65 +++------------------------------------ 1 file changed, 5 insertions(+), 60 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 971c43eca..3112dfe7d 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -160,62 +160,7 @@ define("pyret-base/js/js-numbers", function() { // fromFixnum: fixnum -> pyretnum var fromFixnum = function(x, errbacks) { - if (!isFinite(x)) { - return Roughnum.makeInstance(x, errbacks); - } - var nf = Math.floor(x); - if (nf === x) { - if (isOverflow(nf)) { - return makeBignum(expandExponent(x+'')); - } else { - return nf; - } - } else { - // used to return float, now rational - var stringRep = x.toString(); - var exponentMatch = stringRep.match(genScientificPattern); - var exponentFactor = 1; - if (exponentMatch) { - // x is in scientific notation -- i.e. it has an E; - // find the exponent part, and change x to just the base part; - // calculate exponentFactor, which is the integer 10^exponent - var divideP = false; // divideP==true iff exponent is negative - stringRep = exponentMatch[1]; - x = Number(stringRep); - var exponentPart = exponentMatch[2]; - if (exponentPart.match('^-')) { - divideP = true; - exponentPart = exponentPart.substring(1); - } - exponentFactor = makeBignum("1" + zfill(Number(exponentPart))); - if (divideP) exponentFactor = divide(1, exponentFactor); - } - var decimalMatch = stringRep.match(/^.*\.(.*)$/); - var baseFactor; - if (decimalMatch) { - // we convert the after-decimal part to - // afterDecimalNumerator / afterDecimalDenominator - // where these are guaranteed integers - var afterDecimalNumerator = parseInt(decimalMatch[1]); - var afterDecimalDenominator = Math.pow(10, decimalMatch[1].length); - // x can now be the vulgar fraction - // (x * afterDecimalDenominator) / afterDecimalDenominator - // since x * afterDecimalDenominator is guaranteed to be an integer; - // however, we can simplify this multiplier; - // first find gcd(afterDecimalNumerator, afterDecimalDenominator) - var afterDecimalGCD = _integerGcd(afterDecimalNumerator, afterDecimalDenominator); - // the mulitplier is afterDecimalDenominator / afterDecimalGCD - var simplifiedMultipler = afterDecimalDenominator / afterDecimalGCD; - // multiply x by simplifiedMultipler to get an (integer) numerator; - // simplifiedMultipler itself is the denominator - baseFactor = Rational.makeInstance(Math.round(x * simplifiedMultipler), - Math.round(simplifiedMultipler), errbacks); - } else { - // x is already integer - baseFactor = Rational.makeInstance(x, 1, errbacks); - } - return multiply(exponentFactor, baseFactor); - } + return fromString(String(x), errbacks); }; var expandExponent = function(s) { @@ -2093,7 +2038,7 @@ define("pyret-base/js/js-numbers", function() { var beforeDecimalString = aMatch[2]; var beforeDecimal = 0; if (beforeDecimalString !== '') { - beforeDecimal = makeBignum(beforeDecimalString); + beforeDecimal = fromString(beforeDecimalString); } // var afterDecimalString = aMatch[3]; @@ -2101,9 +2046,9 @@ define("pyret-base/js/js-numbers", function() { var afterDecimal = 0; if (afterDecimalString !== '') { afterDecimalString = afterDecimalString.substring(1); - denominatorTen = makeBignum('1' + new Array(afterDecimalString.length + 1).join('0')); + denominatorTen = fromString('1' + new Array(afterDecimalString.length + 1).join('0')); if (afterDecimalString !== '') { - afterDecimal = makeBignum(afterDecimalString); + afterDecimal = fromString(afterDecimalString); } } // @@ -2117,7 +2062,7 @@ define("pyret-base/js/js-numbers", function() { if (exponentSign === '-' || exponentSign === '+') { exponentString = exponentString.substring(1); } - exponent = makeBignum('1' + new Array(Number(exponentString) + 1).join('0')); + exponent = fromString('1' + new Array(Number(exponentString) + 1).join('0')); } var finalDen = denominatorTen; From 6732459f0338f56eaccc20f447c2e609364d4141 Mon Sep 17 00:00:00 2001 From: Joe Politz Date: Thu, 18 Sep 2025 09:02:54 -0700 Subject: [PATCH 19/24] make sure starter2024 is included in the list of libraries to bundle with pyret-npm! --- src/arr/compiler/libs.arr | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/arr/compiler/libs.arr b/src/arr/compiler/libs.arr index 6d7b71678..3b13f33d0 100644 --- a/src/arr/compiler/libs.arr +++ b/src/arr/compiler/libs.arr @@ -1,5 +1,6 @@ import base as _ import essentials2020 as _ +import starter2024 as _ import arrays as _ import ast as _ @@ -53,3 +54,5 @@ import image-typed as _ import color as _ import csv as _ import charts as _ + + From e51a4118d6fcd0507f4d9068a85048f10a0e6ddd Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 18 Sep 2025 14:22:22 -0400 Subject: [PATCH 20/24] js-numbers.js: log() for nonintegral rational arguments = log(numr) - log(denr) --- src/js/base/js-numbers.js | 5 +++++ tests/pyret/tests/test-numbers.arr | 3 +++ 2 files changed, 8 insertions(+) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index b310325e2..1004c7c09 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -826,6 +826,11 @@ define("pyret-base/js/js-numbers", function() { if (typeof(n) === 'number') { return Roughnum.makeInstance(Math.log(n), errbacks); } + if (isRational(n) && !isInteger(n)) { + return subtract(log(numerator(n, errbacks)), + log(denominator(n, errbacks)), + errbacks); + } var nFix = n.toFixnum(); if (typeof(nFix) === 'number' && nFix !== Infinity) { return Roughnum.makeInstance(Math.log(nFix), errbacks); diff --git a/tests/pyret/tests/test-numbers.arr b/tests/pyret/tests/test-numbers.arr index 37fb3e246..c3438f719 100644 --- a/tests/pyret/tests/test-numbers.arr +++ b/tests/pyret/tests/test-numbers.arr @@ -185,6 +185,9 @@ check: # Racket gives ~84709.80298615794, Wolfram ~84709.80298615795 num-log(1e36789) is-roughly ~84709.80298615796 + # Racket gives ~-171658.17010439213, Wolfram 171658.170104392139 + num-log(1 / num-expt(9, num-expt(5, 7))) is-roughly ~-171658.17010439216 + # Racket, Wolfram match # commenting because arg calculation takes much time # num-log(num-expt(10, 1e5)) is-roughly ~230258.50929940457 From 31d92959a8e77a925307261890d65e5659f9da11 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Thu, 18 Sep 2025 15:06:54 -0400 Subject: [PATCH 21/24] js-numbers.js: log() -- add missing errbacks args --- src/js/base/js-numbers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 1004c7c09..e61f3081d 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -827,8 +827,8 @@ define("pyret-base/js/js-numbers", function() { return Roughnum.makeInstance(Math.log(n), errbacks); } if (isRational(n) && !isInteger(n)) { - return subtract(log(numerator(n, errbacks)), - log(denominator(n, errbacks)), + return subtract(log(numerator(n, errbacks), errbacks), + log(denominator(n, errbacks), errbacks), errbacks); } var nFix = n.toFixnum(); From 777d1184ded367370572736610ab6925f7f8315a Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 02:24:30 -0400 Subject: [PATCH 22/24] scrub regexp no longer used --- src/js/base/js-numbers.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index d7ef80bc7..69a9da9ae 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2039,11 +2039,8 @@ define("pyret-base/js/js-numbers", function() { var roughnumRatRegexp = new RegExp("^~([+-]?\\d+)/(\\d+)$"); - var scientificPattern = new RegExp("^([+-]?\\d*\\.?\\d*)[Ee]([+]?\\d+)$"); - var genScientificPattern = new RegExp("^([+-]?\\d*\\.?\\d*)[Ee]([+-]?\\d+)$"); - // fromString: string -> (pyretnum | false) var fromString = function(x, errbacks) { if (x.match(digitRegexp)) { From b3311dba189431a95937fe6f59051088cf189f88 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Fri, 19 Sep 2025 02:38:58 -0400 Subject: [PATCH 23/24] revert change to fromString() that replaces makeBignum() calls with recursive fromString() calls --- src/js/base/js-numbers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/base/js-numbers.js b/src/js/base/js-numbers.js index 69a9da9ae..b2857cf35 100644 --- a/src/js/base/js-numbers.js +++ b/src/js/base/js-numbers.js @@ -2065,7 +2065,7 @@ define("pyret-base/js/js-numbers", function() { var beforeDecimalString = aMatch[2]; var beforeDecimal = 0; if (beforeDecimalString !== '') { - beforeDecimal = fromString(beforeDecimalString); + beforeDecimal = makeBignum(beforeDecimalString); } // var afterDecimalString = aMatch[3]; @@ -2073,9 +2073,9 @@ define("pyret-base/js/js-numbers", function() { var afterDecimal = 0; if (afterDecimalString !== '') { afterDecimalString = afterDecimalString.substring(1); - denominatorTen = fromString('1' + new Array(afterDecimalString.length + 1).join('0')); + denominatorTen = makeBignum('1' + new Array(afterDecimalString.length + 1).join('0')); if (afterDecimalString !== '') { - afterDecimal = fromString(afterDecimalString); + afterDecimal = makeBignum(afterDecimalString); } } // @@ -2089,7 +2089,7 @@ define("pyret-base/js/js-numbers", function() { if (exponentSign === '-' || exponentSign === '+') { exponentString = exponentString.substring(1); } - exponent = fromString('1' + new Array(Number(exponentString) + 1).join('0')); + exponent = makeBignum('1' + new Array(Number(exponentString) + 1).join('0')); } var finalDen = denominatorTen; From 5e7fb048ae22a1c5eafc3d1d9e16c6fb6ea1aeb0 Mon Sep 17 00:00:00 2001 From: Dorai Sitaram Date: Wed, 8 Oct 2025 01:27:49 -0400 Subject: [PATCH 24/24] @ess476's PR #1217 (issue #1179) relativized to 2025 horizon --- src/arr/compiler/type-defaults.arr | 4 +- src/arr/trove/sets.arr | 26 +++++++ tests/pyret/tests/test-sets.arr | 114 ++++++++++++++++++++++++++++- 3 files changed, 142 insertions(+), 2 deletions(-) diff --git a/src/arr/compiler/type-defaults.arr b/src/arr/compiler/type-defaults.arr index 81f880635..c406107f9 100644 --- a/src/arr/compiler/type-defaults.arr +++ b/src/arr/compiler/type-defaults.arr @@ -304,7 +304,9 @@ module-const-sets = t-module("builtin://sets", "branch", t-arrow([list: t-top, t-number, t-avl, t-avl], t-avl), "fold", t-forall([list: tva, tvb], t-arrow([list: t-arrow([list: tvb, tva], tvb), tvb, t-set-app(tva)], tvb)), "all", t-forall([list: tva], t-arrow([list: t-arrow([list: tva], t-boolean), t-set-app(tva)], t-boolean)), - "any", t-forall([list: tva], t-arrow([list: t-arrow([list: tva], t-boolean), t-set-app(tva)], t-boolean)) + "any", t-forall([list: tva], t-arrow([list: t-arrow([list: tva], t-boolean), t-set-app(tva)], t-boolean)), + "map", t-forall([list: tva, tvb], t-arrow([list: t-set-app(tva), t-arrow([list: tva], tvb)], t-set-app(tvb))), + "filter", t-forall([list: tva], t-arrow([list: t-arrow([list: tva], t-boolean), t-set-app(tva)], t-set-app(tva))), ]), SD.make-string-dict() .set("AVLTree", t-data("AVLTree", [list:], [list:], [string-dict:])) diff --git a/src/arr/trove/sets.arr b/src/arr/trove/sets.arr index e2e28d80a..d23718bc7 100644 --- a/src/arr/trove/sets.arr +++ b/src/arr/trove/sets.arr @@ -11,6 +11,8 @@ provide: set-fold as fold, set-all as all, set-any as any, + set-map as map, + set-filter as filter, data Set, data AVLTree end @@ -375,6 +377,14 @@ data Set: method any(self, f) -> Boolean: self.elems.any(f) + end, + + method map(self, f) -> Set: + list-to-list-set(self.to-list().map(f)) + end, + + method filter(self, f) -> Set: + list-to-list-set(self.to-list().filter(f)) end | tree-set(elems :: AVLTree) with: @@ -444,6 +454,14 @@ data Set: method any(self, f) -> Boolean: self.elems.any(f) + end, + + method map(self, f) -> Set: + list-to-tree-set(self.to-list().map(f)) + end, + + method filter(self, f) -> Set: + list-to-tree-set(self.to-list().filter(f)) end sharing: @@ -597,6 +615,14 @@ fun list-to-tree(lst :: List): end end +fun set-map(s :: Set, f :: (T -> U)) -> Set: + s.map(f) +end + +fun set-filter(f :: (T -> Boolean), s :: Set) -> Set: + s.filter(f) +end + fun arr-to-list-set(arr :: RawArray) -> Set: for raw-array-fold(ls from list-set(empty), elt from arr, _ from 0): ls.add(elt) diff --git a/tests/pyret/tests/test-sets.arr b/tests/pyret/tests/test-sets.arr index dd6d4a370..4b4313a39 100644 --- a/tests/pyret/tests/test-sets.arr +++ b/tests/pyret/tests/test-sets.arr @@ -154,7 +154,7 @@ check "pick on list sets doesn't repeat order": found-diff is true end -check "sets pick visits all elemeents": +check "sets pick visits all elements": fun pick-sum(s): cases(P.Pick) s.pick(): @@ -169,3 +169,115 @@ check "sets pick visits all elemeents": pick-sum([tree-set:]) is 0 end + +check "Set map function": + + # Check empty sets: + sets.map(empty-list-set, lam(x): x + 1 end) is empty-list-set + + sets.map(empty-tree-set, lam(x): x + 1 end) is empty-tree-set + + # Other tests: + sets.map([list-set: 1, 2, 3, 4], lam(x): 1 end) + is [list-set: 1] + + sets.map([tree-set: 1, 2, 3, 4], lam(x): 1 end) + is [tree-set: 1] + + sets.map([list-set: 1, 2, 3, 4], lam(x): x + 1 end) + is [list-set: 5, 4, 3, 2] + + sets.map([tree-set: 1, 2, 3, 4], lam(x): x + 1 end) + is [tree-set: 5, 4, 3, 2] + + # Number -> String mapping test: + test-string = "abcd" + + sets.map([list-set: 0, 1, 2, 3], + lam(x): + string-substring(test-string, x, x + 1) + end).to-list().sort() is [list: "a", "b", "c", "d"] + + sets.map([tree-set: 0, 1, 2, 3], + lam(x): + string-substring(test-string, x, x + 1) + end).to-list().sort() is [list: "a", "b", "c", "d"] + + # String -> Number mapping test: + sets.map([list-set: "Arr", ",", "Hello", "Pyret", "mateys!"], string-length) + is [list-set: 1, 3, 7, 5] + + sets.map([tree-set: "Arr", ",", "Hello", "Pyret", "mateys!"],string-length) + is [tree-set: 1, 3, 7, 5] +end + +check "Set map method": + + # Check empty sets: + empty-list-set.map(lam(x): x + 1 end) is empty-list-set + + empty-tree-set.map(lam(x): x + 1 end) is empty-tree-set + + # Check map returns the same list type: + [list-set: 1, 2, 3, 4].map(lam(x): x end) + is [list-set: 1, 2, 3, 4] + + [tree-set: 1, 2, 3, 4].map(lam(x): x end) + is [tree-set: 1, 2, 3, 4] + + # Other tests: + [list-set: 1, 2, 3, 4].map(lam(x): 1 end) + is [list-set: 1] + + [list-set: 1, 2, 3, 4].map(lam(x): x + 1 end) + is [list-set: 5, 4, 3, 2] + + [tree-set: 1, 2, 3, 4].map(lam(x): x + 1 end) + is [tree-set: 5, 4, 3, 2] + + # Number -> String mapping test: + test-string = "abcd" + + [list-set: 0, 1, 2, 3].map(lam(x): + string-substring(test-string, x, x + 1) + end).to-list().sort() is [list: "a", "b", "c", "d"] + + [tree-set: 0, 1, 2, 3].map(lam(x): + string-substring(test-string, x, x + 1) + end).to-list().sort() is [list: "a", "b", "c", "d"] + + # String -> Number mapping test: + [list-set: "Arr", ",", "Hello", "Pyret", "mateys!"].map(string-length) + is [list-set: 1, 3, 7, 5] + + [tree-set: "Arr", ",", "Hello", "Pyret", "mateys!"].map(string-length) + is [tree-set: 1, 3, 7, 5] +end + +check "Set filter function": + + sets.filter(lam(e): e > 5 end, [list-set: -1, 1]) is [list-set: ] + sets.filter(lam(e): e > 5 end, [tree-set: -1, 1]) is [tree-set: ] + + sets.filter(lam(e): e > 0 end, [list-set: -1, 1]) is [list-set: 1] + sets.filter(lam(e): e > 0 end, [tree-set: -1, 1]) is [tree-set: 1] + + sets.filter(lam(e): num-modulo(e, 2) == 0 end, + [list-set: 1, 2, 3, 4]) is [list-set: 2, 4] + + sets.filter(lam(e): num-modulo(e, 2) == 0 end, + [tree-set: 1, 2, 3, 4]) is [tree-set: 2, 4] +end + +check "Set filter method": + + [list-set: -1, 1].filter(lam(e): e > 5 end) is [list-set: ] + [tree-set: -1, 1].filter(lam(e): e > 5 end) is [tree-set: ] + + [list-set: -1, 1].filter(lam(e): e > 0 end) is [list-set: 1] + [tree-set: -1, 1].filter(lam(e): e > 0 end) is [tree-set: 1] + + [list-set: 1, 2, 3, 4].filter(lam(e): num-modulo(e, 2) == 0 end) + is [list-set: 2, 4] + +end