Current section
26 Versions
Jump to
Current section
26 Versions
Compare versions
6
files changed
+125
additions
-20
deletions
| @@ -23,7 +23,10 @@ export default class Bitstring { | |
| 23 23 | |
| 24 24 | static calculateSegmentBitCount(segment) { |
| 25 25 | const size = $.resolveSegmentSize(segment); |
| 26 | + if (size === null) return null; |
| 27 | + |
| 26 28 | const unit = $.resolveSegmentUnit(segment); |
| 29 | + if (unit === null) return null; |
| 27 30 | |
| 28 31 | return size * unit; |
| 29 32 | } |
| @@ -619,21 +622,11 @@ export default class Bitstring { | |
| 619 622 | } |
| 620 623 | |
| 621 624 | static resolveSegmentUnit(segment) { |
| 622 | - if (segment.unit !== null && segment.size !== null) { |
| 625 | + if (segment.unit !== null) { |
| 623 626 | return Number(segment.unit); |
| 624 627 | } |
| 625 628 | |
| 626 | - switch (segment.type) { |
| 627 | - case "binary": |
| 628 | - return 8; |
| 629 | - |
| 630 | - case "float": |
| 631 | - case "integer": |
| 632 | - return 1; |
| 633 | - |
| 634 | - default: |
| 635 | - return null; |
| 636 | - } |
| 629 | + return segment.type === "binary" ? 8 : 1; |
| 637 630 | } |
| 638 631 | |
| 639 632 | static serialize(bitstring) { |
| @@ -361,15 +361,43 @@ const Erlang = { | |
| 361 361 | |
| 362 362 | // Start binary_to_integer/1 |
| 363 363 | "binary_to_integer/1": (binary) => { |
| 364 | + return Erlang["binary_to_integer/2"](binary, Type.integer(10)); |
| 365 | + }, |
| 366 | + // End binary_to_integer/1 |
| 367 | + // Deps: [:erlang.binary_to_integer/2] |
| 368 | + |
| 369 | + // Start binary_to_integer/2 |
| 370 | + "binary_to_integer/2": (binary, base) => { |
| 364 371 | if (!Type.isBinary(binary)) { |
| 365 372 | Interpreter.raiseArgumentError( |
| 366 373 | Interpreter.buildArgumentErrorMsg(1, "not a binary"), |
| 367 374 | ); |
| 368 375 | } |
| 369 376 | |
| 370 | - const text = Bitstring.toText(binary); |
| 377 | + if (!Type.isInteger(base) || base.value < 2n || base.value > 36n) { |
| 378 | + Interpreter.raiseArgumentError( |
| 379 | + Interpreter.buildArgumentErrorMsg( |
| 380 | + 2, |
| 381 | + "not an integer in the range 2 through 36", |
| 382 | + ), |
| 383 | + ); |
| 384 | + } |
| 371 385 | |
| 372 | - if (!/^[+-]?\d+$/.test(text)) { |
| 386 | + const text = Bitstring.toText(binary); |
| 387 | + const baseNum = Number(base.value); |
| 388 | + |
| 389 | + let validPattern; |
| 390 | + |
| 391 | + // Validate the text representation based on the base |
| 392 | + if (baseNum <= 10) { |
| 393 | + const maxDigit = String(baseNum - 1); |
| 394 | + validPattern = new RegExp(`^[+-]?[0-${maxDigit}]+$`); |
| 395 | + } else { |
| 396 | + const maxLetter = String.fromCharCode(65 + baseNum - 11); // A=10, B=11, etc. |
| 397 | + validPattern = new RegExp(`^[+-]?[0-9A-${maxLetter}]+$`, "i"); |
| 398 | + } |
| 399 | + |
| 400 | + if (!validPattern.test(text)) { |
| 373 401 | Interpreter.raiseArgumentError( |
| 374 402 | Interpreter.buildArgumentErrorMsg( |
| 375 403 | 1, |
| @@ -378,9 +406,14 @@ const Erlang = { | |
| 378 406 | ); |
| 379 407 | } |
| 380 408 | |
| 381 | - return Type.integer(BigInt(text)); |
| 409 | + // For base 10, use BigInt directly to avoid precision loss |
| 410 | + // For other bases, use parseInt which handles the base conversion |
| 411 | + const result = |
| 412 | + baseNum === 10 ? BigInt(text) : BigInt(parseInt(text, baseNum)); |
| 413 | + |
| 414 | + return Type.integer(result); |
| 382 415 | }, |
| 383 | - // End binary_to_integer/1 |
| 416 | + // End binary_to_integer/2 |
| 384 417 | // Deps: [] |
| 385 418 | |
| 386 419 | // Start bit_size/1 |
| @@ -828,6 +861,61 @@ const Erlang = { | |
| 828 861 | // End orelse/2 |
| 829 862 | // Deps: [] |
| 830 863 | |
| 864 | + // Start split_binary/2 |
| 865 | + "split_binary/2": (binary, position) => { |
| 866 | + if (!Type.isBinary(binary)) { |
| 867 | + Interpreter.raiseArgumentError( |
| 868 | + Interpreter.buildArgumentErrorMsg(1, "not a binary"), |
| 869 | + ); |
| 870 | + } |
| 871 | + |
| 872 | + if (!Type.isInteger(position)) { |
| 873 | + Interpreter.raiseArgumentError( |
| 874 | + Interpreter.buildArgumentErrorMsg(2, "not an integer"), |
| 875 | + ); |
| 876 | + } |
| 877 | + |
| 878 | + if (position.value < 0n) { |
| 879 | + Interpreter.raiseArgumentError( |
| 880 | + Interpreter.buildArgumentErrorMsg(2, "out of range"), |
| 881 | + ); |
| 882 | + } |
| 883 | + |
| 884 | + const pos = Number(position.value); |
| 885 | + const totalBytes = Number(Erlang["byte_size/1"](binary).value); |
| 886 | + |
| 887 | + if (pos > totalBytes) { |
| 888 | + Interpreter.raiseArgumentError( |
| 889 | + Interpreter.buildArgumentErrorMsg(2, "out of range"), |
| 890 | + ); |
| 891 | + } |
| 892 | + |
| 893 | + // If position is 0, first part is empty binary |
| 894 | + if (pos === 0) { |
| 895 | + return Type.tuple([Type.bitstring(""), binary]); |
| 896 | + } |
| 897 | + |
| 898 | + // If position equals total size, second part is empty binary |
| 899 | + if (pos === totalBytes) { |
| 900 | + return Type.tuple([binary, Type.bitstring("")]); |
| 901 | + } |
| 902 | + |
| 903 | + // Split the binary using takeChunk |
| 904 | + // First part: from start to position (pos bytes) |
| 905 | + const firstPart = Bitstring.takeChunk(binary, 0, pos * 8); |
| 906 | + |
| 907 | + // Second part: from position to end |
| 908 | + const secondPart = Bitstring.takeChunk( |
| 909 | + binary, |
| 910 | + pos * 8, |
| 911 | + (totalBytes - pos) * 8, |
| 912 | + ); |
| 913 | + |
| 914 | + return Type.tuple([firstPart, secondPart]); |
| 915 | + }, |
| 916 | + // End split_binary/2 |
| 917 | + // Deps: [:erlang.byte_size/1] |
| 918 | + |
| 831 919 | // Start tl/1 |
| 832 920 | "tl/1": (list) => { |
| 833 921 | if (!Type.isList(list) || list.data.length === 0) { |
| @@ -1313,8 +1313,10 @@ export default class Interpreter { | |
| 1313 1313 | let chunkOffset = 0; |
| 1314 1314 | const rightBitCount = Bitstring.calculateBitCount(right); |
| 1315 1315 | |
| 1316 | - for (const segment of left.segments) { |
| 1316 | + for (let i = 0; i < left.segments.length; i++) { |
| 1317 | + const segment = left.segments[i]; |
| 1317 1318 | const segmentType = segment.type; |
| 1319 | + const isLastSegment = i === left.segments.length - 1; |
| 1318 1320 | |
| 1319 1321 | if ( |
| 1320 1322 | segmentType === "utf8" || |
| @@ -1327,7 +1329,23 @@ export default class Interpreter { | |
| 1327 1329 | throw new HologramInterpreterError(message); |
| 1328 1330 | } |
| 1329 1331 | |
| 1330 | - const chunkBitCount = Bitstring.calculateSegmentBitCount(segment); |
| 1332 | + let chunkBitCount; |
| 1333 | + |
| 1334 | + // Special case: last segment with binary or bitstring type and no explicit size |
| 1335 | + // should consume all remaining bits |
| 1336 | + if ( |
| 1337 | + isLastSegment && |
| 1338 | + (segmentType === "binary" || segmentType === "bitstring") && |
| 1339 | + segment.size === null |
| 1340 | + ) { |
| 1341 | + chunkBitCount = rightBitCount - chunkOffset; |
| 1342 | + } else { |
| 1343 | + chunkBitCount = Bitstring.calculateSegmentBitCount(segment); |
| 1344 | + |
| 1345 | + if (chunkBitCount === null) { |
| 1346 | + return $.#handleMatchFail(right, raiseMatchError); |
| 1347 | + } |
| 1348 | + } |
| 1331 1349 | |
| 1332 1350 | if ( |
| 1333 1351 | segment.type === "float" && |
| @@ -1352,6 +1370,9 @@ export default class Interpreter { | |
| 1352 1370 | context, |
| 1353 1371 | raiseMatchError, |
| 1354 1372 | ); |
| 1373 | + } else if (segment.value.type === "match_placeholder") { |
| 1374 | + // Match placeholder in bitstring patterns just consumes the chunk without binding |
| 1375 | + // This is equivalent to _ in Elixir bitstring patterns |
| 1355 1376 | } else { |
| 1356 1377 | const segmentBitstring = Bitstring.fromSegments([segment]); |
| @@ -7,7 +7,7 @@ | |
| 7 7 | {<<"Sponsor">>,<<"https://github.com/sponsors/bartblast">>}, |
| 8 8 | {<<"Website">>,<<"https://hologram.page">>}]}. |
| 9 9 | {<<"name">>,<<"hologram">>}. |
| 10 | - {<<"version">>,<<"0.6.2">>}. |
| 10 | + {<<"version">>,<<"0.6.3">>}. |
| 11 11 | {<<"description">>, |
| 12 12 | <<"Full stack isomorphic Elixir web framework that can be used on top of Phoenix.">>}. |
| 13 13 | {<<"elixir">>,<<"~> 1.0">>}. |
| @@ -16,6 +16,7 @@ defmodule Hologram.Compiler.CallGraph do | |
| 16 16 | @type edge :: {vertex, vertex} |
| 17 17 | @type vertex :: module | mfa |
| 18 18 | |
| 19 | + # TODO: Determine automatically based on deps annotations next to function implementations |
| 19 20 | @erlang_mfa_edges [ |
| 20 21 | {{:erlang, :"=<", 2}, {:erlang, :<, 2}}, |
| 21 22 | {{:erlang, :"=<", 2}, {:erlang, :==, 2}}, |
| @@ -25,9 +26,11 @@ defmodule Hologram.Compiler.CallGraph do | |
| 25 26 | {{:erlang, :binary_to_atom, 1}, {:erlang, :binary_to_atom, 2}}, |
| 26 27 | {{:erlang, :binary_to_existing_atom, 1}, {:erlang, :binary_to_atom, 1}}, |
| 27 28 | {{:erlang, :binary_to_existing_atom, 2}, {:erlang, :binary_to_atom, 2}}, |
| 29 | + {{:erlang, :binary_to_integer, 1}, {:erlang, :binary_to_integer, 2}}, |
| 28 30 | {{:erlang, :error, 1}, {:erlang, :error, 2}}, |
| 29 31 | {{:erlang, :integer_to_binary, 1}, {:erlang, :integer_to_binary, 2}}, |
| 30 32 | {{:erlang, :iolist_to_binary, 1}, {:lists, :flatten, 1}}, |
| 33 | + {{:erlang, :split_binary, 2}, {:erlang, :byte_size, 1}}, |
| 31 34 | {{:lists, :keymember, 3}, {:lists, :keyfind, 3}}, |
| 32 35 | {{:maps, :get, 2}, {:maps, :get, 3}}, |
| 33 36 | {{:maps, :update, 3}, {:maps, :is_key, 2}}, |
Loading more files…