Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introducing the bigint #6670

Merged
merged 45 commits into from
Mar 27, 2024
Merged

Introducing the bigint #6670

merged 45 commits into from
Mar 27, 2024

Conversation

mununki
Copy link
Member

@mununki mununki commented Mar 8, 2024

Discussions: #6525

This PR is to introduce the JS bigint as a primitive type. With the addition of the bigint type, this PR implements support for basic arithmetic operators and unary negation operators as a first step. The comparison operator and exponential operator is being added to the initial implementation of this PR for review.

I tried to make all arithmetic operations polymorphic, but the downside of stripping out the optimization logic for existing int and float was that we explicitly added operators.

The main changes are

  1. Changed the deprecated nativeint to bigint: use the *n literal of nativeint for bigint.
  2. Added arithmetic operators. (The comma can be changed to something better on the discussion. I initially considered +n, but saw the increased complexity in parsing and implemented it as a comma).
  3. Omitted optimizations at build time because there is no API in OCaml that supports bigint-sized numbers, unlike int and float
  4. Added the support untagged variant of VeryLargeNumber(bigint)
  5. Added Js.Bigint module to override the integer operator depending on user needs.
open! Js.Bigint

let a = 1n + 1n
let p = 2n ** 2n
  1. Added the support of bitwise operations
// you can use land, lor, lxor, ... with open! Js.Bigint
landn(1n, 1n)
lorn(1n, 1n)
lxorn(1n, 1n)
lnotn(1n)
lsln(1n, 1n)
asrn(1n, 1n)
// unsingned right shift (>>>) is not supported for BigInt in JS
  1. Support literal in the pattern matching
switch 1n {
| 1n => ...

@@ -30,6 +30,7 @@ type rec t =
| @as(null) Null
| String(string)
| Number(float)
| Bigint(bigint)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BigInt doesn't belong to JSON spec

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, correct. I'll remove it, thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed c8b78d2

@zth
Copy link
Collaborator

zth commented Mar 8, 2024

Can be done in a follow up PR of course, but adding support for it in untagged variants would be nice.

@mununki
Copy link
Member Author

mununki commented Mar 8, 2024

Can be done in a follow up PR of course, but adding support for it in untagged variants would be nice.

Oh, this PR already added support the untagged variant with Bigint.

@zth
Copy link
Collaborator

zth commented Mar 8, 2024

@mununki haha I'm sorry, you're right... Well done!

Copy link
Collaborator

@zth zth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! 🎉 This will be great to support. A few smaller questions from me. @cristianoc should have a look at this as well.

I'm not sold on the comma. Comma has such a specific meaning in the language that the code looks broken to me when there's a comma. I don't have a better suggestion yet though, but maybe we could figure one out together.

({pexp_desc = Pexp_constant (Pconst_integer (name, Some n)); _}, _);
_;
};
] when n = 'n' ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, but can't this when clause just be directly inside of the pattern?

@@ -375,7 +393,7 @@ module DynamicChecks = struct
in
let literals_overlaps_with_number () =
Ext_list.exists literal_cases (function
| Int _ | Float _ -> true
| Int _ | Float _ | Bigint _ -> true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bigint doesn't overlap with number, right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! a9e4c16

@@ -1,3 +1,11 @@
/*** JavaScript BigInt API */

type t
type t = bigint
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering whether we should just remove this one and refer to bigint directly instead. It's breaking so we could wait for a bit before doing that and just deprecate t meanwhile, but we should definitely refer to bigint instead of t in the code below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! cf62db8

Comment on lines 5 to 11
external \"~-": t => t = "%negbigint"
external \"~+": t => t = "%identity"
external \"+": (t, t) => t = "%addbigint"
external \"-": (t, t) => t = "%subbigint"
external \"*": (t, t) => t = "%mulbigint"
external \"/": (t, t) => t = "%divbigint"
external mod: (t, t) => t = "%modbigint"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As said above, I think t should be bigint instead here.

@cristianoc
Copy link
Collaborator

Having yet another syntax for one more numeric type seems to make an existing problem worse.

The existing problem is having to use + and +. for ints and floats.

I'd rather go the direction of making x+y resolve to int or float or bigint at type checking time.

@zth
Copy link
Collaborator

zth commented Mar 9, 2024

While we figure something like that out, can we get away with the following perhaps?

// Using opens, described in this PR
open! Js.Bigint

let a = 1n + 1n

// Or, functions on bigint...?
let a = Js.Bigint.add(1n, 1n)

@cknitt
Copy link
Member

cknitt commented Mar 9, 2024

I'd rather go the direction of making x+y resolve to int or float or bigint at type checking time.

This would be great, but how would it work with type inference? What type would the function

let f = (x, y) => x + y

have?

@cristianoc
Copy link
Collaborator

I'd rather go the direction of making x+y resolve to int or float or bigint at type checking time.

This would be great, but how would it work with type inference? What type would the function

let f = (x, y) => x + y

have?

Without type annotations, the default numeric type.
One could define a generic numeric type, though it's probably overkill as polymorphic numerical code is not very likely.

@cometkim
Copy link
Member

I'd rather go the direction of making x+y resolve to int or float or bigint at type checking time.

This would be great, but how would it work with type inference? What type would the function

suggested on #6477

actually we are doing same for comparison operators.

@mununki
Copy link
Member Author

mununki commented Mar 11, 2024

I agree adding another number type to make a problem worse. I didn't think about adding the generic number type yet. I think it is very good direction to solve the problem having two different operators for int and float. I grab a chance to look into how int and float type value are handled during compilation while working on this PR. Actually, I couldn't find many reasons to separate two different type(int, float) for emitting the JS output except optimization of int32 for int type value.

How about defining the number type and deprecating the int, float type in future?(maybe we can come back and decide the deprecation later) I can start working on defininig the number type and simplifying the operators with another PR. How about it?

@cristianoc
Copy link
Collaborator

I suspect a number of users rely on the semantic difference between int and float. Eg with float it's easy to get 0.999999 while 1 is expected so simple equality does not work.

That's why I was suggesting a simple change at the level of type checker, which does not break existing code.
Just, decides at type checking time what number type is intended.

@mununki
Copy link
Member Author

mununki commented Mar 11, 2024

I found I missed the coercion for bigint, then I've added the commits b13884d @zth

@mununki
Copy link
Member Author

mununki commented Mar 11, 2024

I suspect a number of users rely on the semantic difference between int and float. Eg with float it's easy to get 0.999999 while 1 is expected so simple equality does not work.

That's why I was suggesting a simple change at the level of type checker, which does not break existing code. Just, decides at type checking time what number type is intended.

If I understand correctly, Do you mean letting the type checker infer types for arithmetic operations like it does for comparisons?

// Int operations
external \"+": (int, int) => int = "%addint"
// Float operations
external \"+.": (float, float) => float = "%addfloat"

// to

// Number operations
external \"+": ('a, 'a) => 'a = "%addnumber"

@mununki mununki requested a review from cometkim March 11, 2024 13:09
@cristianoc
Copy link
Collaborator

cristianoc commented Mar 11, 2024

I suspect a number of users rely on the semantic difference between int and float. Eg with float it's easy to get 0.999999 while 1 is expected so simple equality does not work.

That's why I was suggesting a simple change at the level of type checker, which does not break existing code. Just, decides at type checking time what number type is intended.

If I understand correctly, Do you mean letting the type checker infer types for arithmetic operations like it does for comparisons?

// Int operations

external \"+": (int, int) => int = "%addint"

// Float operations

external \"+.": (float, float) => float = "%addfloat"



// to



// Number operations

external \"+": ('a, 'a) => 'a = "%addnumber"

Yes a bit like for comparison.
Except while fully polymorphic comparison exists, fully polymorphic addition does not.
One can't have a single function that works for both int and float, but needs to generate one or the other.
If the type is not constrained, one can default to int (backwards compatibility) or even give an error.

@mununki
Copy link
Member Author

mununki commented Mar 12, 2024

To summarize, if we'll discuss the issue of unifying the int and float operators separately, and if everyone agrees that open! Bigint and make the int operator available to bigint as well, I'm going to add commits for the exponential operator ** and additional optimizations to the comparison operator in this PR.

// exponentiation
let x = 1n ** 1n

// comparison optimization
let y = 1n == 1n
// compiled to var y = true;

lib/es6/caml.js Outdated
return 0;
} else if (x < y) {
return -1;
} else if (x > y || x === x) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird or clause

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good catch, I didn't notice because I was using the float_compare code as is. Why does float_compare have this branching as opposed to int_compare? Very weird to me too.

Copy link
Member Author

@mununki mununki Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those conditions are for below test cases;

// float_test.res
eq(__LOC__, float_compare(Js.Float._NaN, Js.Float._NaN), 0)
eq(__LOC__, generic_compare(Js.Float._NaN, Js.Float._NaN), 0)
eq(__LOC__, float_compare(Js.Float._NaN, neg_infinity), -1)
eq(__LOC__, generic_compare(Js.Float._NaN, neg_infinity), -1)
eq(__LOC__, float_compare(neg_infinity, Js.Float._NaN), 1)
eq(__LOC__, generic_compare(neg_infinity, Js.Float._NaN), 1)

Copy link
Member Author

@mununki mununki Mar 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why float_compare was implemented this way at the time, but my best guess is that it was to capitalize on the fact that comparing NaNs is false.

-Infinity === -Infinity // true
NaN === NaN // false

However, the bigint doesn't have a binding to Infinity or Nan for now. I'm going to fix bigint_compare as int_compare.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed my mind. I've added interop for NaN and methods to Js.Bigint, and also added the bigint_test.res test. Can you take a look? c972a13

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is the compare function. Those clauses are for the comparing with NaN.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got your point. NaN can not be produced with BigInt operation. NaN is produced only below 5 cases:

There are five different types of operations that return NaN:

  • Failed number conversion (e.g. explicit ones like parseInt("blabla"), Number(undefined), or implicit ones like Math.abs(undefined))
  • Math operation where the result is not a real number (e.g. Math.sqrt(-1))
  • Indeterminate form (e.g. 0 * Infinity, 1 ** Infinity, Infinity / Infinity, Infinity - Infinity)
  • A method or expression whose operand is or gets coerced to NaN (e.g. 7 ** NaN, 7 * "blabla") — this means NaN is contagious
  • Other cases where an invalid value is to be represented as a number (e.g. an invalid Date new Date("blabla").getTime(), "".charCodeAt(1))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So wouldn't it be better to get rid of NaNs in Js_bigint as well?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. NaN is obviously number type. Date, parseInt, Math are all about Number, not BigInt.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

6601152
ea49c05

@mununki
Copy link
Member Author

mununki commented Mar 13, 2024

While I'm working on the comparison lambda, lam, I think I found there are some codes better to be cleaned up. E.g. the physical comparison (%eq, %noteq) is converted by int_comp for int and float. in js_exp_make.ml. However, the case of %noteq is being omitted from the optimization, and there seems to be some ambiguity in the function name. I'll open an issue and work on it in a separate PR.

@mununki
Copy link
Member Author

mununki commented Mar 13, 2024

I've finished adding the comparison operation optimization and exponetiation operator **, for bigint. I have nothing left for this PR now.

therefore necessary to test for `_NaN`. Return `true` if the given value is
`_NaN`, `false` otherwise. See [`isNaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) on MDN.
*/
external isNaN: bigint => bool = "isNaN"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is necessary in bigint module. Unlike number, bigint operations (except comparison) cannot be mixed with other types and never produce NaN.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes sense to me.

Js.Bigint.isFinite(1234.)
```
*/
external isFinite: bigint => bool = "isFinite"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Js BigInt never produces Infinity

lib/es6/caml.js Outdated
return 0;
} else if (x < y) {
return -1;
} else if (x > y || x === x) {
Copy link
Member

@cometkim cometkim Mar 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NaN is a concept that belongs to Number (typeof NaN === number), not BigInt. BigInt operations never produce NaN AFAIK

@mununki mununki requested a review from cometkim March 17, 2024 08:46
Comment on lines 368 to 385
("%negbigint", Pnegbigint);
("%addbigint", Paddbigint);
("%subbigint", Psubbigint);
("%mulbigint", Pmulbigint);
("%divbigint", Pdivbigint Safe);
("%powbigint", Ppowbigint);
("%modbigint", Pmodbigint Safe);
("%eqbigint", Pbigintcomp Ceq);
("%noteqbigint", Pbigintcomp Cneq);
("%ltbigint", Pbigintcomp Clt);
("%lebigint", Pbigintcomp Cle);
("%gtbigint", Pbigintcomp Cgt);
("%gebigint", Pbigintcomp Cge);
Copy link
Member

@cometkim cometkim Mar 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems %lslbigint(<<), %asrbigint(>>), %andbigint(&), %orbigint(|), %xorbigint(^) are missing? (not sure bitwise-not(~) op) Logical operators are the main interface of BigInt.

Note that >>>, Unsigned right shift doesn't accept BigInt.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially, I wasn't going to include the bitwise operation implementation in this PR because I was concerned that it would make the PR too large, but now that I think about it, it doesn't make much difference, so I'll add it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added 9060aa0

@mununki mununki requested a review from cometkim March 18, 2024 02:19
@mununki
Copy link
Member Author

mununki commented Mar 25, 2024

Sorry for rebasing during review, there was a conflict in the change log file.

let bigint_comp (cmp : Lam_compat.comparison) ?comment (e0: t) (e1: t) =
bin ?comment (Lam_compile_util.jsop_of_comp cmp) e0 e1

let bigint_div ~checked ?comment (e0: t) (e1: t) =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the option to disable checking divide by zero is really only hidden under some ancient internal-only configuration

at some point, it can be removed, perhaps in a later PR

@@ -27,6 +27,7 @@ let not_zero_constant (x : Lam_constant.t) =
match x with
| Const_int { i } -> i <> 0l
| Const_int64 i -> i <> 0L
| Const_bigint i -> i <> "0"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this normalised, or are -0 and 00 also possible?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

00 is normalized, but -0. I'll add another clause i <> "-0"

Copy link
Collaborator

@cristianoc cristianoc Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about instead using a representation for Conts_bigint with a boolean (sign), and normalised string. So there's no confusion and it's less likely to introduce bugs in future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, very good. I'll refactor accordingly.

Js.Bigint.fromString("")

/* returns 17n */
Js.Bigint.fromString("0x11")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is not supported as literal, but can be constructed in this way via parsing right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, users can use this way for unsupported literal such as 0x11n

@val
/**
Parses the given `string` into a `bigint` using JavaScript semantics. Return the
number as a `bigint` if successfully parsed, `null`, `undefined`, `_NaN` otherwise.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what null, undefined, _NaN refers to.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is invalid comment, I'll remove this part.


```rescript
/* returns 123n */
Js.Bigint.fromString("123")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this called fromStringExn?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, you're correct.

Js.Bigint.fromString("0o11")
```
*/
external fromStringExn: string => bigint = "BigInt"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the exception? It would be good to have an example to see how to catch it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JS BigInt constructor throws error such as Uncaught SyntaxError in case of passing a non-numeric value. I'll add an example .

| _ ->
(* If both numbers are either negative or positive, compare their lengths. *)
let len0, len1 = (String.length s0, String.length s1) in
if len0 = len1 then String.compare s0 s1 (* If lengths are equal, compare the strings directly. *)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems wrong for negative numbers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems fine to me. Can you elaborate the case wrong?
https://ocaml.org/play#code=bGV0IGNvbXBhcmUgczAgczEgPQogICgqIGNoZWNrIGlmIG5lZ2F0aXZlICopCiAgbGV0IGlzX25lZyBzID0gU3RyaW5nLmxlbmd0aCBzID4gMCAmJiBzLlswXSA9ICctJyBpbgogIG1hdGNoIChpc19uZWcgczAsIGlzX25lZyBzMSkgd2l0aAogIHwgKHRydWUsIGZhbHNlKSAtPiAtMSAgKCogSWYgb25seSBzMCBpcyBuZWdhdGl2ZSwgaXQncyBzbWFsbGVyLiAqKQogIHwgKGZhbHNlLCB0cnVlKSAtPiAxICAgKCogSWYgb25seSBzMSBpcyBuZWdhdGl2ZSwgczAgaXMgbGFyZ2VyLiAqKQogIHwgXyAtPgogICAgKCogSWYgYm90aCBudW1iZXJzIGFyZSBlaXRoZXIgbmVnYXRpdmUgb3IgcG9zaXRpdmUsIGNvbXBhcmUgdGhlaXIgbGVuZ3Rocy4gKikKICAgIGxldCBsZW4wLCBsZW4xID0gKFN0cmluZy5sZW5ndGggczAsIFN0cmluZy5sZW5ndGggczEpIGluCiAgICBpZiBsZW4wID0gbGVuMSB0aGVuIFN0cmluZy5jb21wYXJlIHMwIHMxICAoKiBJZiBsZW5ndGhzIGFyZSBlcXVhbCwgY29tcGFyZSB0aGUgc3RyaW5ncyBkaXJlY3RseS4gKikKICAgIGVsc2UgaWYgbGVuMCA%2BIGxlbjEgdGhlbiAKICAgICAgaWYgaXNfbmVnIHMwIHRoZW4gLTEgZWxzZSAxICAoKiBBIGxvbmdlciBzMCBtZWFucyBpdCdzIGxhcmdlciB1bmxlc3MgaXQncyBuZWdhdGl2ZS4gKikKICAgIGVsc2UgICgqIGxlbjAgPCBsZW4xICopCiAgICAgIGlmIGlzX25lZyBzMSB0aGVuIDEgZWxzZSAtMSAgKCogQSBsb25nZXIgczEgbWVhbnMgczAgaXMgc21hbGxlciB1bmxlc3MgczEgaXMgbmVnYXRpdmUuICopCgpsZXQgKCkgPQooKiBsZW4wIDwgbGVuMSAgKikKICBhc3NlcnQoKGNvbXBhcmUgIi0xIiAiLTEwIiA9IDEpKTsKICBhc3NlcnQoKGNvbXBhcmUgIjEiICItMTAiID0gMSkpOwogIGFzc2VydCgoY29tcGFyZSAiLTEiICIxMCIgPSAtMSkpOwooKiBsZW4wID4gbGVuMSAgKikKICBhc3NlcnQoKGNvbXBhcmUgIi0xMDAiICItMTAiID0gLTEpKTsKICBhc3NlcnQoKGNvbXBhcmUgIjEwMCIgIi0xMCIgPSAxKSk7CiAgYXNzZXJ0KChjb21wYXJlICItMTAwIiAiMTAiID0gLTEpKTsKKCogbGVuMCA9IGxlbjEgICopCiAgYXNzZXJ0KChjb21wYXJlICItMTAiICItMTAiID0gMCkpOwogIGFzc2VydCgoY29tcGFyZSAiMTAiICItMTAiID0gMSkpOwogIGFzc2VydCgoY29tcGFyZSAiLTEwIiAiMTAiID0gLTEpKTsKICBwcmludF9lbmRsaW5lICJ0ZXN0IHBhc3NpbmchIg%3D%3D

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I figure it out this case is wrong assert((compare "-10" "-12" = 1)) I'll fix it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

000n -> 0n
001n -> 1n
01_000_000n -> 1000000n
0x1 -> 0x1n
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is now a syntax error too right?

001n -> 1n
01_000_000n -> 1000000n
0x1 -> 0x1n
0x001n -> 0x001n
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and this

@@ -300,6 +300,9 @@ let inline_int_primitive (i : int32) : string list =
let inline_int64_primitive (i : int64) : string list =
[""; to_string (Ffi_inline_const (Const_int64 i))]

let inline_bigint_primitive (i : string) : string list =
[""; to_string (Ffi_inline_const (Const_bigint i))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not check that the bigint is valid, or remove leading zero's.
Don't know if this is a problem, just observing the fact.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One possibility is to do the check is_numeric at parsing time, instead of type checking time. And do the normalisation (remove zeros) whenever a Const_bigint is constructed.

Copy link
Member Author

@mununki mununki Mar 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good. I'm going to try to refactor Const_bigint bool * string in AST type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed the model of bigint in asttypes.ml, lam_constant.ml and js_op.ml

9652115

What do you think?

type number =
| Float of float_lit
| Int of { i : int32; c : int option }
| Uint of int32
| Bigint of bigint_lit
| Bigint of bool * string
Copy link
Collaborator

@cristianoc cristianoc Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small style suggestion keep bigint_lit as { positive: bool; value: string }

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, it seems not going to be unboxed, still okay?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The difference is going to be tiny in practice.

| Bigint i -> bigint i
| Bigint i ->
let open Bigint_utils in
let sign, i = i |> remove_leading_sign in
Copy link
Collaborator

@cristianoc cristianoc Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style/naming: perhaps:
let {positive, value} = parse_bigint i
where parse_bigint also removes the leading zeros, so there's no way one can forget to do that

@@ -506,7 +506,7 @@ let prim ~primitive:(prim : Lam_primitive.t) ~args loc : t =
(* FIXME: could raise? *)
Lift.bool
(Lam_compat.cmp_float cmp (float_of_string a) (float_of_string b))
| Pbigintcomp cmp, Const_bigint a, Const_bigint b -> default ()
| Pbigintcomp cmp, Const_bigint (_, a), Const_bigint (_, b) -> default ()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: here a and b are never used, I thought the compiler would give a warning

if len = 0 then (false, str)
else
if is_neg str || is_pos str then (not (is_neg str), String.sub str 1 (len -1))
else (not (is_neg str), str)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in this branch is_neg is impossible, so just (true, str)

(function Tpat_constant(Const_bigint i) -> String.length i | _ -> assert false)
(function i -> Tpat_constant(Const_bigint (String.make i '*')))
(function Tpat_constant(Const_bigint (sign, i)) -> String.length (Bigint_utils.to_string sign i) | _ -> assert false)
(function i -> Tpat_constant(Const_bigint (true, (String.make i '*'))))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure what (String.make i '*') is doing here?
I would have guessed string_of_int i instead.
Not sure how to trigger this code path, perhaps with some example?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure. I guess this function is used in pattern matching for gadt partially. But logically I think we can change (String.make i '*') to string_of_int i here for less space complexity.

@@ -812,6 +812,12 @@ let parseConstant p =
let constant =
match p.Parser.token with
| Int {i; suffix} ->
(* Only decimal literal is allowed for bigint *)
if suffix = Some 'n' && not (Bigint_utils.is_numeric i) then
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perhaps is_numeric could be called is_valid?

if suffix = Some 'n' && not (Bigint_utils.is_numeric i) then
Parser.err p
(Diagnostics.message
"Invalid decimal literal. Only decimal literal is allowed for \
Copy link
Collaborator

@cristianoc cristianoc Mar 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps "Invalid bigint literal. Only decimal...".

@mununki
Copy link
Member Author

mununki commented Mar 27, 2024

Fixed as reviewed. d82b4bc

Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to go!
Anything missing?

@mununki
Copy link
Member Author

mununki commented Mar 27, 2024

Nothing left.
Thank you for your good reviews!

@zth
Copy link
Collaborator

zth commented Mar 27, 2024

Fantastic @mununki , well done! 👏

@zth zth merged commit f0dda99 into 11.0_release Mar 27, 2024
14 checks passed
@zth zth deleted the bigint branch March 27, 2024 10:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants