Current section
26 Versions
Jump to
Current section
26 Versions
Compare versions
18
files changed
+403
additions
-107
deletions
| @@ -169,10 +169,6 @@ const Erlang_Binary = { | |
| 169 169 | const startValue = scopeData.data[0].value; |
| 170 170 | const lengthValue = scopeData.data[1].value; |
| 171 171 | |
| 172 | - if (startValue < 0n) { |
| 173 | - raiseInvalidOptions(); |
| 174 | - } |
| 175 | - |
| 176 172 | scopeStart = Number(startValue); |
| 177 173 | scopeLength = Number(lengthValue); |
| 178 174 | }); |
| @@ -470,17 +466,29 @@ const Erlang_Binary = { | |
| 470 466 | ](options, 3); |
| 471 467 | |
| 472 468 | // match/3 only supports :scope; reject :global, :trim, and :trim_all |
| 473 | - // Validate scope start is within subject bounds |
| 474 | - if (global || trim || trimAll || scopeStart > subject.bytes.length) { |
| 469 | + if (global || trim || trimAll) { |
| 475 470 | Interpreter.raiseArgumentError( |
| 476 471 | Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 477 472 | ); |
| 478 473 | } |
| 479 474 | |
| 475 | + // Validate scope start is within subject bounds |
| 476 | + if (scopeStart < 0 || scopeStart > subject.bytes.length) { |
| 477 | + Interpreter.raiseArgumentError( |
| 478 | + Interpreter.buildArgumentErrorMsg( |
| 479 | + 3, |
| 480 | + "specified part is not wholly inside binary", |
| 481 | + ), |
| 482 | + ); |
| 483 | + } |
| 484 | + |
| 480 485 | // Validate that if scopeLength is specified, scopeStart + scopeLength >= 0 |
| 481 486 | if (scopeLength !== null && scopeStart + scopeLength < 0) { |
| 482 487 | Interpreter.raiseArgumentError( |
| 483 | - Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 488 | + Interpreter.buildArgumentErrorMsg( |
| 489 | + 3, |
| 490 | + "specified part is not wholly inside binary", |
| 491 | + ), |
| 484 492 | ); |
| 485 493 | } |
| 486 494 | |
| @@ -490,7 +498,10 @@ const Erlang_Binary = { | |
| 490 498 | // Validate scope doesn't extend beyond subject |
| 491 499 | if (scopeStart + effectiveLength > subject.bytes.length) { |
| 492 500 | Interpreter.raiseArgumentError( |
| 493 | - Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 501 | + Interpreter.buildArgumentErrorMsg( |
| 502 | + 3, |
| 503 | + "specified part is not wholly inside binary", |
| 504 | + ), |
| 494 505 | ); |
| 495 506 | } |
| 496 507 | |
| @@ -599,15 +610,27 @@ const Erlang_Binary = { | |
| 599 610 | "_parse_search_opts/2" |
| 600 611 | ](options, 3); |
| 601 612 | |
| 602 | - if (global || trim || trimAll || scopeStart > subject.bytes.length) { |
| 613 | + if (global || trim || trimAll) { |
| 603 614 | Interpreter.raiseArgumentError( |
| 604 615 | Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 605 616 | ); |
| 606 617 | } |
| 607 618 | |
| 619 | + if (scopeStart < 0 || scopeStart > subject.bytes.length) { |
| 620 | + Interpreter.raiseArgumentError( |
| 621 | + Interpreter.buildArgumentErrorMsg( |
| 622 | + 3, |
| 623 | + "specified part is not wholly inside binary", |
| 624 | + ), |
| 625 | + ); |
| 626 | + } |
| 627 | + |
| 608 628 | if (scopeLength !== null && scopeStart + scopeLength < 0) { |
| 609 629 | Interpreter.raiseArgumentError( |
| 610 | - Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 630 | + Interpreter.buildArgumentErrorMsg( |
| 631 | + 3, |
| 632 | + "specified part is not wholly inside binary", |
| 633 | + ), |
| 611 634 | ); |
| 612 635 | } |
| 613 636 | |
| @@ -644,7 +667,10 @@ const Erlang_Binary = { | |
| 644 667 | |
| 645 668 | if (scopeStart + effectiveLength > subject.bytes.length) { |
| 646 669 | Interpreter.raiseArgumentError( |
| 647 | - Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 670 | + Interpreter.buildArgumentErrorMsg( |
| 671 | + 3, |
| 672 | + "specified part is not wholly inside binary", |
| 673 | + ), |
| 648 674 | ); |
| 649 675 | } |
| 650 676 | |
| @@ -1211,7 +1237,7 @@ const Erlang_Binary = { | |
| 1211 1237 | ](options, 3); |
| 1212 1238 | |
| 1213 1239 | // Validate scope start is within subject bounds |
| 1214 | - if (scopeStart > subject.bytes.length) { |
| 1240 | + if (scopeStart < 0 || scopeStart > subject.bytes.length) { |
| 1215 1241 | Interpreter.raiseArgumentError( |
| 1216 1242 | Interpreter.buildArgumentErrorMsg(3, "invalid options"), |
| 1217 1243 | ); |
| @@ -37,7 +37,12 @@ const Erlang_Elixir_Utils = { | |
| 37 37 | codePoints.push(Number(codepoint.value)); |
| 38 38 | |
| 39 39 | if (Type.isImproperList(result)) { |
| 40 | - current = result.data[1]; |
| 40 | + // cp/1 may return a multi-element improper tail, e.g. [codepoint | [rest | tail]]; |
| 41 | + // reconstruct it rather than dropping everything past the second element. |
| 42 | + current = |
| 43 | + result.data.length === 2 |
| 44 | + ? result.data[1] |
| 45 | + : Type.improperList(result.data.slice(1)); |
| 41 46 | |
| 42 47 | if (Type.isBitstring(current) && Bitstring.isEmpty(current)) { |
| 43 48 | break; |
| @@ -1645,16 +1645,11 @@ const Erlang = { | |
| 1645 1645 | |
| 1646 1646 | // Start div/2 |
| 1647 1647 | "div/2": (integer1, integer2) => { |
| 1648 | - if (!Type.isInteger(integer1) || !Type.isInteger(integer2)) { |
| 1649 | - const arg1 = Interpreter.inspect(integer1); |
| 1650 | - const arg2 = Interpreter.inspect(integer2); |
| 1651 | - |
| 1652 | - Interpreter.raiseArgumentError( |
| 1653 | - `bad argument in arithmetic expression: div(${arg1}, ${arg2})`, |
| 1654 | - ); |
| 1655 | - } |
| 1656 | - |
| 1657 | - if (integer2.value === 0n) { |
| 1648 | + if ( |
| 1649 | + !Type.isInteger(integer1) || |
| 1650 | + !Type.isInteger(integer2) || |
| 1651 | + integer2.value === 0n |
| 1652 | + ) { |
| 1658 1653 | const arg1 = Interpreter.inspect(integer1); |
| 1659 1654 | const arg2 = Interpreter.inspect(integer2); |
| @@ -1017,6 +1017,37 @@ const Erlang_Lists = { | |
| 1017 1017 | }, |
| 1018 1018 | // End sort/2 |
| 1019 1019 | // Deps: [] |
| 1020 | + |
| 1021 | + // Start suffix/2 |
| 1022 | + "suffix/2": (list1, list2) => { |
| 1023 | + // The Erlang implementation computes length/1 on both arguments, which |
| 1024 | + // rejects anything that is not a proper list with an "not a list" error. |
| 1025 | + if (!Type.isProperList(list1) || !Type.isProperList(list2)) { |
| 1026 | + Interpreter.raiseArgumentError( |
| 1027 | + Interpreter.buildArgumentErrorMsg(1, "not a list"), |
| 1028 | + ); |
| 1029 | + } |
| 1030 | + |
| 1031 | + const length1 = list1.data.length; |
| 1032 | + const delta = list2.data.length - length1; |
| 1033 | + |
| 1034 | + // The first list cannot be a suffix of a shorter second list. |
| 1035 | + if (delta < 0) { |
| 1036 | + return Type.boolean(false); |
| 1037 | + } |
| 1038 | + |
| 1039 | + // Compare the first list against the trailing part of the second list |
| 1040 | + // in place, avoiding any intermediate allocation. |
| 1041 | + for (let i = 0; i < length1; i++) { |
| 1042 | + if (!Interpreter.isStrictlyEqual(list1.data[i], list2.data[delta + i])) { |
| 1043 | + return Type.boolean(false); |
| 1044 | + } |
| 1045 | + } |
| 1046 | + |
| 1047 | + return Type.boolean(true); |
| 1048 | + }, |
| 1049 | + // End suffix/2 |
| 1050 | + // Deps: [] |
| 1020 1051 | }; |
| 1021 1052 | |
| 1022 1053 | export default Erlang_Lists; |
| @@ -19,7 +19,7 @@ const Erlang_Sets = { | |
| 19 19 | Interpreter.buildFunctionClauseErrorMsg(":proplists.get_value/3", [ |
| 20 20 | Type.atom("version"), |
| 21 21 | opts, |
| 22 | - Type.integer(1), |
| 22 | + Type.integer(2), |
| 23 23 | ]), |
| 24 24 | ); |
| 25 25 | } |
Loading more files…