diff --git a/backend/src/Builtins/Builtins.Pure/Builtin.fs b/backend/src/Builtins/Builtins.Pure/Builtin.fs index b763637dac..1996b8b287 100644 --- a/backend/src/Builtins/Builtins.Pure/Builtin.fs +++ b/backend/src/Builtins/Builtins.Pure/Builtin.fs @@ -22,6 +22,7 @@ let builtins () : Builtins = Libs.UInt64.builtins () Libs.Int128.builtins () Libs.UInt128.builtins () + Libs.Int.builtins () Libs.Float.builtins () diff --git a/backend/src/Builtins/Builtins.Pure/Builtins.Pure.fsproj b/backend/src/Builtins/Builtins.Pure/Builtins.Pure.fsproj index 91cef60cc1..67e5b2c30a 100644 --- a/backend/src/Builtins/Builtins.Pure/Builtins.Pure.fsproj +++ b/backend/src/Builtins/Builtins.Pure/Builtins.Pure.fsproj @@ -22,6 +22,7 @@ + diff --git a/backend/src/Builtins/Builtins.Pure/Libs/Int.fs b/backend/src/Builtins/Builtins.Pure/Libs/Int.fs new file mode 100644 index 0000000000..1ff8cf71ec --- /dev/null +++ b/backend/src/Builtins/Builtins.Pure/Libs/Int.fs @@ -0,0 +1,725 @@ +/// Builtins for the arbitrary-precision integer type `Int` (the default, +/// suffix-less integer). The polymorphic operators in NoModule.fs back the +/// infix forms (`+`, `<`, ...); these typed builtins give the +/// `Darklang.Stdlib.Int` package its named functions, mirroring `Int64`. +module Builtins.Pure.Libs.Int + +open Prelude +open LibExecution.RuntimeTypes +open LibExecution.Builtin.Shortcuts + +module VT = LibExecution.ValueType +module Dval = LibExecution.Dval +module RTE = RuntimeError + +let private bigZero = System.Numerics.BigInteger.Zero +let private bigOne = System.Numerics.BigInteger.One +let private bigMinusOne = System.Numerics.BigInteger.MinusOne + +let private divideByZero (vm : VMState) : 'a = + RTE.Ints.DivideByZeroError |> RTE.Int |> raiseRTE vm.threadID + +let private zeroModulus (vm : VMState) : 'a = + RTE.Ints.ZeroModulus |> RTE.Int |> raiseRTE vm.threadID + +let private negativeModulus (vm : VMState) : 'a = + RTE.Ints.NegativeModulus |> RTE.Int |> raiseRTE vm.threadID + +let private negativeExponent (vm : VMState) : 'a = + RTE.Ints.NegativeExponent |> RTE.Int |> raiseRTE vm.threadID + +let private outOfRange (vm : VMState) : 'a = + RTE.Ints.OutOfRange |> RTE.Int |> raiseRTE vm.threadID + +// BigInteger <-> 128-bit conversions (no `bigint`/cast operator covers these) +let private i128ToBig (a : System.Int128) : bigint = + System.Numerics.BigInteger.op_Implicit a +let private u128ToBig (a : System.UInt128) : bigint = + System.Numerics.BigInteger.op_Implicit a +let private bigToI128 (b : bigint) : System.Int128 = + System.Numerics.BigInteger.op_Explicit b +let private bigToU128 (b : bigint) : System.UInt128 = + System.Numerics.BigInteger.op_Explicit b + +/// `Int` -> a fixed-width type: `Some` if the value fits `[lo, hi]`, else `None`. +let private toFixed + (resultType : KnownType) + (lo : bigint) + (hi : bigint) + (make : bigint -> Dval) + (v : Dval) + : Ply = + let b = Dval.asBigInt v + if b >= lo && b <= hi then + Dval.optionSome resultType (make b) |> Ply + else + Dval.optionNone resultType |> Ply + + +let fns () : List = + [ { name = fn "intMod" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TInt + description = + "Returns the result of wrapping around so that {{0 <= res < b}}. + The modulus must be greater than 0. + Use if you want the remainder after division, which + has a different behavior for negative numbers." + fn = + (function + | _, vm, _, [ DInt a; DInt b ] -> + let m = DarkInt.toBigInt b + if m = bigZero then + zeroModulus vm + elif m < bigZero then + negativeModulus vm + else + let r = DarkInt.toBigInt a % m + Ply(Dval.int (if r < bigZero then m + r else r)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intRemainder" 0 + typeParams = [] + parameters = [ Param.make "value" TInt ""; Param.make "divisor" TInt "" ] + returnType = TypeReference.result TInt TString + description = + "Returns the integer remainder left over after dividing by + , as a . The remainder will be negative only + if {{ < 0}}. The sign of doesn't influence the + outcome. Returns an {{Error}} if is {{0}}." + fn = + let resultOk = Dval.resultOk KTInt KTString + let resultError = Dval.resultError KTInt KTString + (function + | _, _, _, [ DInt value; DInt divisor ] -> + let d = DarkInt.toBigInt divisor + if d = bigZero then + DString "`divisor` must be non-zero" |> resultError |> Ply + else + DarkInt.toBigInt value % d |> Dval.int |> resultOk |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intAdd" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TInt + description = "Adds two arbitrary-precision integers" + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DInt(DarkInt.add a b)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intSubtract" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TInt + description = "Subtracts two arbitrary-precision integers" + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DInt(DarkInt.subtract a b)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intMultiply" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TInt + description = "Multiplies two arbitrary-precision integers" + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DInt(DarkInt.multiply a b)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intPower" 0 + typeParams = [] + parameters = [ Param.make "base" TInt ""; Param.make "exponent" TInt "" ] + returnType = TInt + description = + "Raise to the power of . + must be non-negative. The arbitrary-precision result grows as needed." + fn = + (function + | _, vm, _, [ DInt b; DInt exp ] -> + let number = DarkInt.toBigInt b + let exp = DarkInt.toBigInt exp + if exp < bigZero then + negativeExponent vm + elif exp > bigint System.Int32.MaxValue then + // `**` needs an Int32 exponent; only trivial bases are representable. + if number = bigZero then + Ply(Dval.int bigZero) + elif number = bigOne then + Ply(Dval.int bigOne) + elif number = bigMinusOne then + Ply( + Dval.int (if exp % (bigint 2) = bigZero then bigOne else bigMinusOne) + ) + else + outOfRange vm + else + Ply(Dval.int (number ** (int exp))) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intDivide" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TInt + description = "Divides two arbitrary-precision integers, rounding towards zero" + fn = + (function + | _, vm, _, [ DInt a; DInt b ] -> + if DarkInt.isZero b then divideByZero vm else Ply(DInt(DarkInt.divide a b)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intNegate" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TInt + description = "Returns the negation of , {{-a}}" + fn = + (function + | _, _, _, [ DInt a ] -> Ply(DInt(DarkInt.negate a)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intGreaterThan" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TBool + description = "Returns {{true}} if is greater than " + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b > 0)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intGreaterThanOrEqualTo" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TBool + description = + "Returns {{true}} if is greater than or equal to " + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b >= 0)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intLessThan" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TBool + description = "Returns {{true}} if is less than " + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b < 0)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intLessThanOrEqualTo" 0 + typeParams = [] + parameters = [ Param.make "a" TInt ""; Param.make "b" TInt "" ] + returnType = TBool + description = + "Returns {{true}} if is less than or equal to " + fn = + (function + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b <= 0)) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intSqrt" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TFloat + description = + "Get the square root of an as a . Note that very + large values lose precision when converted to a float." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> Ply(DFloat(sqrt (float (Dval.asBigInt v)))) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToFloat" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TFloat + description = + "Converts an to a . Very large values lose + precision." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> Ply(DFloat(float (Dval.asBigInt v))) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intParse" 0 + typeParams = [] + parameters = [ Param.make "s" TString "" ] + returnType = TypeReference.result TInt TString + description = + "Returns the value of a . Arbitrary precision, so + the only failure is a badly-formatted string." + fn = + let resultOk = Dval.resultOk KTInt KTString + let resultError = Dval.resultError KTInt KTString + (function + | _, _, _, [ DString s ] -> + match System.Numerics.BigInteger.TryParse(s) with + | true, i -> i |> Dval.int |> resultOk |> Ply + | false, _ -> DString "Invalid Int" |> resultError |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToString" 0 + typeParams = [] + parameters = [ Param.make "int" TInt "" ] + returnType = TString + description = "Stringify " + fn = + (function + | _, _, _, [ (DInt _) as v ] -> Ply(DString(string (Dval.asBigInt v))) + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + // Conversions from the fixed-width integer types into `Int`. Every + // fixed-width value fits, so these are total. + + { name = fn "intFromInt8" 0 + typeParams = [] + parameters = [ Param.make "a" TInt8 "" ] + returnType = TInt + description = "Converts an Int8 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DInt8 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromUInt8" 0 + typeParams = [] + parameters = [ Param.make "a" TUInt8 "" ] + returnType = TInt + description = "Converts a UInt8 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DUInt8 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromInt16" 0 + typeParams = [] + parameters = [ Param.make "a" TInt16 "" ] + returnType = TInt + description = "Converts an Int16 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DInt16 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromUInt16" 0 + typeParams = [] + parameters = [ Param.make "a" TUInt16 "" ] + returnType = TInt + description = "Converts a UInt16 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DUInt16 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromInt32" 0 + typeParams = [] + parameters = [ Param.make "a" TInt32 "" ] + returnType = TInt + description = "Converts an Int32 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DInt32 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromUInt32" 0 + typeParams = [] + parameters = [ Param.make "a" TUInt32 "" ] + returnType = TInt + description = "Converts a UInt32 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DUInt32 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromInt64" 0 + typeParams = [] + parameters = [ Param.make "a" TInt64 "" ] + returnType = TInt + description = "Converts an Int64 to an arbitrary-precision Int." + fn = + (function + // An Int64 is always in `DarkInt.Finite` range. + | _, _, _, [ DInt64 a ] -> DInt(DarkInt.Finite a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromUInt64" 0 + typeParams = [] + parameters = [ Param.make "a" TUInt64 "" ] + returnType = TInt + description = "Converts a UInt64 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DUInt64 a ] -> Dval.int (bigint a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromInt128" 0 + typeParams = [] + parameters = [ Param.make "a" TInt128 "" ] + returnType = TInt + description = "Converts an Int128 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DInt128 a ] -> Dval.int (i128ToBig a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intFromUInt128" 0 + typeParams = [] + parameters = [ Param.make "a" TUInt128 "" ] + returnType = TInt + description = "Converts a UInt128 to an arbitrary-precision Int." + fn = + (function + | _, _, _, [ DUInt128 a ] -> Dval.int (u128ToBig a) |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + // Conversions from `Int` into the fixed-width integer types. The value may + // not fit the target range, so each returns an `Option`. + + { name = fn "intToInt8" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TInt8 + description = "Converts an Int to an Int8, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTInt8 + (bigint System.SByte.MinValue) + (bigint System.SByte.MaxValue) + (fun b -> DInt8(sbyte b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToUInt8" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TUInt8 + description = "Converts an Int to a UInt8, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTUInt8 + (bigint System.Byte.MinValue) + (bigint System.Byte.MaxValue) + (fun b -> DUInt8(byte b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToInt16" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TInt16 + description = "Converts an Int to an Int16, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTInt16 + (bigint System.Int16.MinValue) + (bigint System.Int16.MaxValue) + (fun b -> DInt16(int16 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToUInt16" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TUInt16 + description = "Converts an Int to a UInt16, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTUInt16 + (bigint System.UInt16.MinValue) + (bigint System.UInt16.MaxValue) + (fun b -> DUInt16(uint16 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToInt32" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TInt32 + description = "Converts an Int to an Int32, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTInt32 + (bigint System.Int32.MinValue) + (bigint System.Int32.MaxValue) + (fun b -> DInt32(int32 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToUInt32" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TUInt32 + description = "Converts an Int to a UInt32, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTUInt32 + (bigint System.UInt32.MinValue) + (bigint System.UInt32.MaxValue) + (fun b -> DUInt32(uint32 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToInt64" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TInt64 + description = + "Converts an Int to an Int64, returning None if it doesn't fit the + 64-bit range." + fn = + let someType = KTInt64 + (function + // By the invariant, a `Finite` always fits Int64 and an `Infinite` never + // does. + | _, _, _, [ DInt(DarkInt.Finite a) ] -> + Dval.optionSome someType (DInt64 a) |> Ply + | _, _, _, [ DInt(DarkInt.Infinite _) ] -> Dval.optionNone someType |> Ply + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToUInt64" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TUInt64 + description = "Converts an Int to a UInt64, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTUInt64 + (bigint System.UInt64.MinValue) + (bigint System.UInt64.MaxValue) + (fun b -> DUInt64(uint64 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToInt128" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TInt128 + description = "Converts an Int to an Int128, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTInt128 + (i128ToBig System.Int128.MinValue) + (i128ToBig System.Int128.MaxValue) + (fun b -> DInt128(bigToI128 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } + + + { name = fn "intToUInt128" 0 + typeParams = [] + parameters = [ Param.make "a" TInt "" ] + returnType = TypeReference.option TUInt128 + description = "Converts an Int to a UInt128, returning None if it doesn't fit." + fn = + (function + | _, _, _, [ (DInt _) as v ] -> + toFixed + KTUInt128 + (u128ToBig System.UInt128.MinValue) + (u128ToBig System.UInt128.MaxValue) + (fun b -> DUInt128(bigToU128 b)) + v + | _ -> incorrectArgs ()) + sqlSpec = NotQueryable + previewable = Pure + capabilities = LibExecution.Capabilities.noCaps + deprecated = NotDeprecated } ] + + +let builtins () = LibExecution.Builtin.make [] (fns ()) diff --git a/backend/src/Builtins/Builtins.Pure/Libs/Json.fs b/backend/src/Builtins/Builtins.Pure/Libs/Json.fs index c84c48877e..8cf3bb6b3d 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/Json.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/Json.fs @@ -103,6 +103,7 @@ let rec serialize (threadID : ThreadID) (w : Utf8JsonWriter) (dv : Dval) : unit | DUInt64 i -> w.WriteNumberValue i | DInt128 i -> w.WriteRawValue(i.ToString()) | DUInt128 i -> w.WriteRawValue(i.ToString()) + | DInt i -> w.WriteRawValue(string (DarkInt.toBigInt i)) | DFloat f -> if System.Double.IsNaN f then @@ -229,6 +230,55 @@ let raiseCantMatchWithType ParseError.CantMatchWithType(typ, j.GetRawText(), path) |> raiseError +let private maxJsonIntDigits = 1_000_000L + + +/// Parses a JSON number literal to an exact `bigint`, without going through a +/// double (which would round values past 2^53). Accepts integer-valued decimal +/// and exponent forms — `1.0`, `1.2E2`, `1200E-1`, `9007199254740993.0` — the +/// same forms the fixed-width int parsers accept, but exactly. Returns None for +/// genuinely fractional values (`1.5`) or malformed input. +/// +/// `BigInteger.TryParse` with `NumberStyles.Float` does the exact format parsing +/// (no double, integral-via-exponent accepted, true fractions rejected); we only +/// add a size bound up front, since it would otherwise allocate without limit for +/// a literal like `1e2000000000`. +let private parseExactJsonInteger (raw : string) : bigint option = + let unsigned = raw.TrimStart('-', '+') + + // split the significand from the exponent + let mantissa, exp = + match unsigned.IndexOfAny [| 'e'; 'E' |] with + | -1 -> unsigned, 0L + | i -> + match System.Int32.TryParse(unsigned.Substring(i + 1)) with + | true, e -> unsigned.Substring(0, i), int64 e + | false, _ -> unsigned, System.Int64.MaxValue // unparseable/huge exp → reject + + let dotIndex = mantissa.IndexOf '.' + // integer-part length, and total significand digit count (excluding the point) + let intLen = int64 (if dotIndex = -1 then mantissa.Length else dotIndex) + let significandDigits = int64 mantissa.Length - (if dotIndex = -1 then 0L else 1L) + + // Two bounds, each catching a different blow-up, before BigInteger allocates: + // - significandDigits: the input itself (e.g. a 2M-digit fraction that + // TryParse would parse into a transient bigint before rejecting) + // - intLen + max exp 0: the expanded result (e.g. `1e2000000000`) + if + significandDigits > maxJsonIntDigits || intLen + max exp 0L > maxJsonIntDigits + then + None + else + match + System.Numerics.BigInteger.TryParse( + raw, + System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture + ) + with + | true, v -> Some v + | false, _ -> None + let parse (threadID : ThreadID) @@ -447,6 +497,14 @@ let parse else raiseCantMatchWithType TUInt128 j pathSoFar |> Ply + | TInt, JsonValueKind.Number -> + // Accept integer-valued decimal/exponent forms (like the fixed-width + // types do), but parse them EXACTLY — never through a double, which would + // round values past 2^53 (e.g. `9007199254740993.0`). + match parseExactJsonInteger (j.GetRawText()) with + | Some i -> Dval.int i |> Ply + | None -> raiseCantMatchWithType TInt j pathSoFar |> Ply + | TFloat, JsonValueKind.Number -> j.GetDouble() |> DFloat |> Ply | TFloat, JsonValueKind.String -> match j.GetString() with @@ -687,10 +745,11 @@ let parse | TUInt16, _ | TInt32, _ | TUInt32, _ - | TInt128, _ - | TUInt128, _ | TInt64, _ | TUInt64, _ + | TInt128, _ + | TUInt128, _ + | TInt, _ | TFloat, _ | TChar, _ | TString, _ diff --git a/backend/src/Builtins/Builtins.Pure/Libs/List.fs b/backend/src/Builtins/Builtins.Pure/Libs/List.fs index 3cf287c460..3aeccd0bb4 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/List.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/List.fs @@ -39,6 +39,8 @@ module DvalComparator = | DUInt64 i1, DUInt64 i2 -> order i1 i2 | DInt128 i1, DInt128 i2 -> order i1 i2 | DUInt128 i1, DUInt128 i2 -> order i1 i2 + // to bigint so `order` compares numerically, not by struct case tag + | DInt i1, DInt i2 -> order (DarkInt.toBigInt i1) (DarkInt.toBigInt i2) | DFloat f1, DFloat f2 -> order f1 f2 @@ -112,6 +114,7 @@ module DvalComparator = | DUInt64 _, _ | DInt128 _, _ | DUInt128 _, _ + | DInt _, _ | DFloat _, _ | DChar _, _ | DString _, _ diff --git a/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs b/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs index 3edddcc99b..436e6f6f71 100644 --- a/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs +++ b/backend/src/Builtins/Builtins.Pure/Libs/NoModule.fs @@ -48,6 +48,7 @@ let rec equals (a : Dval) (b : Dval) : bool = | DUInt64 a, DUInt64 b -> a = b | DInt128 a, DInt128 b -> a = b | DUInt128 a, DUInt128 b -> a = b + | DInt a, DInt b -> a = b | DFloat a, DFloat b -> a = b @@ -144,6 +145,7 @@ let rec equals (a : Dval) (b : Dval) : bool = | DUInt64 _, _ | DInt128 _, _ | DUInt128 _, _ + | DInt _, _ | DFloat _, _ | DChar _, _ | DString _, _ @@ -245,8 +247,9 @@ let fns () : List = parameters = [ Param.make "a" varA ""; Param.make "b" varB "" ] returnType = varA description = - "Adds two numbers of the same numeric type. Integer overflow raises a - runtime error; float arithmetic follows IEEE (overflow to infinity)." + "Adds two numbers of the same numeric type. Fixed-width integer overflow + raises a runtime error; the arbitrary-precision Int grows instead of + overflowing; float arithmetic follows IEEE (overflow to infinity)." fn = (function | _, vm, _, [ DInt8 a; DInt8 b ] -> @@ -271,6 +274,7 @@ let fns () : List = | _, vm, _, [ DUInt128 a; DUInt128 b ] -> overflowChecked vm (fun () -> DUInt128(System.UInt128.op_CheckedAddition (a, b))) + | _, _, _, [ DInt a; DInt b ] -> Ply(DInt(DarkInt.add a b)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DFloat(a + b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -285,8 +289,10 @@ let fns () : List = parameters = [ Param.make "a" varA ""; Param.make "b" varB "" ] returnType = varA description = - "Subtracts two numbers of the same numeric type. Integer overflow raises a - runtime error; float arithmetic follows IEEE (overflow to infinity)." + "Subtracts two numbers of the same numeric type. Fixed-width integer + overflow raises a runtime error; the arbitrary-precision Int grows + instead of overflowing; float arithmetic follows IEEE (overflow to + infinity)." fn = (function | _, vm, _, [ DInt8 a; DInt8 b ] -> @@ -311,6 +317,7 @@ let fns () : List = | _, vm, _, [ DUInt128 a; DUInt128 b ] -> overflowChecked vm (fun () -> DUInt128(System.UInt128.op_CheckedSubtraction (a, b))) + | _, _, _, [ DInt a; DInt b ] -> Ply(DInt(DarkInt.subtract a b)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DFloat(a - b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -325,8 +332,10 @@ let fns () : List = parameters = [ Param.make "a" varA ""; Param.make "b" varB "" ] returnType = varA description = - "Multiplies two numbers of the same numeric type. Integer overflow raises a - runtime error; float arithmetic follows IEEE (overflow to infinity)." + "Multiplies two numbers of the same numeric type. Fixed-width integer + overflow raises a runtime error; the arbitrary-precision Int grows + instead of overflowing; float arithmetic follows IEEE (overflow to + infinity)." fn = (function | _, vm, _, [ DInt8 a; DInt8 b ] -> @@ -351,6 +360,7 @@ let fns () : List = | _, vm, _, [ DUInt128 a; DUInt128 b ] -> overflowChecked vm (fun () -> DUInt128(System.UInt128.op_CheckedMultiply (a, b))) + | _, _, _, [ DInt a; DInt b ] -> Ply(DInt(DarkInt.multiply a b)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DFloat(a * b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -408,6 +418,8 @@ let fns () : List = overflowChecked vm (fun () -> DInt128(a / b)) | _, vm, _, [ DUInt128 a; DUInt128 b ] -> if b = System.UInt128.Zero then divideByZero vm else Ply(DUInt128(a / b)) + | _, vm, _, [ DInt a; DInt b ] -> + if DarkInt.isZero b then divideByZero vm else Ply(DInt(DarkInt.divide a b)) // Float division by zero follows IEEE semantics (Infinity/NaN), as before | _, _, _, [ DFloat a; DFloat b ] -> Ply(DFloat(a / b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b @@ -477,6 +489,15 @@ let fns () : List = Ply(DInt128(if r < System.Int128.Zero then m + r else r)) | _, vm, _, [ DUInt128 v; DUInt128 m ] -> if m = System.UInt128.Zero then zeroModulus vm else Ply(DUInt128(v % m)) + | _, vm, _, [ DInt v; DInt m ] -> + let m = DarkInt.toBigInt m + if m = System.Numerics.BigInteger.Zero then + zeroModulus vm + elif m < System.Numerics.BigInteger.Zero then + negativeModulus vm + else + let r = DarkInt.toBigInt v % m + Ply(Dval.int (if r < System.Numerics.BigInteger.Zero then m + r else r)) | _, vm, _, [ DFloat v; DFloat m ] -> if m = 0.0 then zeroModulus vm @@ -500,7 +521,8 @@ let fns () : List = description = "Raises a number to the power of another number of the same type. Supported for every integer type and Float (not Int128/UInt128). - Integer exponents must be non-negative; integer overflow raises." + Integer exponents must be non-negative. Fixed-width integer overflow + raises a runtime error; the arbitrary-precision Int grows instead." fn = (function | _, vm, _, [ DInt8 number; DInt8 exp ] -> @@ -560,6 +582,30 @@ let fns () : List = // exceeds Int64 range. overflowChecked vm (fun () -> (bigint number) ** (int exp) |> int64 |> DInt64) + | _, vm, _, [ DInt number; DInt exp ] -> + let number = DarkInt.toBigInt number + let exp = DarkInt.toBigInt exp + if exp < System.Numerics.BigInteger.Zero then + negativeExponent vm + elif exp > bigint System.Int32.MaxValue then + // `**` needs an Int32 exponent; only trivial bases are representable. + if number = System.Numerics.BigInteger.Zero then + Ply(Dval.int System.Numerics.BigInteger.Zero) + elif number = System.Numerics.BigInteger.One then + Ply(Dval.int System.Numerics.BigInteger.One) + elif number = System.Numerics.BigInteger.MinusOne then + Ply( + Dval.int ( + if exp % (bigint 2) = System.Numerics.BigInteger.Zero then + System.Numerics.BigInteger.One + else + System.Numerics.BigInteger.MinusOne + ) + ) + else + outOfRange vm + else + Ply(Dval.int (number ** (int exp))) | _, _, _, [ DFloat number; DFloat exp ] -> Ply(DFloat(number ** exp)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -593,6 +639,7 @@ let fns () : List = | _, vm, _, [ DInt128 a ] -> overflowChecked vm (fun () -> DInt128(System.Int128.op_CheckedUnaryNegation a)) + | _, _, _, [ DInt a ] -> Ply(DInt(DarkInt.negate a)) | _, _, _, [ DFloat a ] -> Ply(DFloat(-a)) | _, vm, _, [ a ] -> numericTypeError vm a a | _ -> incorrectArgs ()) @@ -619,6 +666,7 @@ let fns () : List = | _, _, _, [ DUInt64 a; DUInt64 b ] -> Ply(DBool(a > b)) | _, _, _, [ DInt128 a; DInt128 b ] -> Ply(DBool(a > b)) | _, _, _, [ DUInt128 a; DUInt128 b ] -> Ply(DBool(a > b)) + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b > 0)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DBool(a > b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -646,6 +694,7 @@ let fns () : List = | _, _, _, [ DUInt64 a; DUInt64 b ] -> Ply(DBool(a >= b)) | _, _, _, [ DInt128 a; DInt128 b ] -> Ply(DBool(a >= b)) | _, _, _, [ DUInt128 a; DUInt128 b ] -> Ply(DBool(a >= b)) + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b >= 0)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DBool(a >= b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -672,6 +721,7 @@ let fns () : List = | _, _, _, [ DUInt64 a; DUInt64 b ] -> Ply(DBool(a < b)) | _, _, _, [ DInt128 a; DInt128 b ] -> Ply(DBool(a < b)) | _, _, _, [ DUInt128 a; DUInt128 b ] -> Ply(DBool(a < b)) + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b < 0)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DBool(a < b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) @@ -699,6 +749,7 @@ let fns () : List = | _, _, _, [ DUInt64 a; DUInt64 b ] -> Ply(DBool(a <= b)) | _, _, _, [ DInt128 a; DInt128 b ] -> Ply(DBool(a <= b)) | _, _, _, [ DUInt128 a; DUInt128 b ] -> Ply(DBool(a <= b)) + | _, _, _, [ DInt a; DInt b ] -> Ply(DBool(DarkInt.compare a b <= 0)) | _, _, _, [ DFloat a; DFloat b ] -> Ply(DBool(a <= b)) | _, vm, _, [ a; b ] -> numericTypeError vm a b | _ -> incorrectArgs ()) diff --git a/backend/src/LibDB/AstTransformer.fs b/backend/src/LibDB/AstTransformer.fs index e1b5db0194..0abfddbf2a 100644 --- a/backend/src/LibDB/AstTransformer.fs +++ b/backend/src/LibDB/AstTransformer.fs @@ -82,6 +82,7 @@ let rec private transformTypeRef | PT.TUInt64 | PT.TInt128 | PT.TUInt128 + | PT.TInt | PT.TFloat | PT.TChar | PT.TString @@ -187,6 +188,7 @@ and private transformExpr (mapping : HashMapping) (expr : PT.Expr) : PT.Expr = | PT.EUInt64 _ | PT.EInt128 _ | PT.EUInt128 _ + | PT.EInt _ | PT.EFloat _ | PT.EChar _ | PT.EVariable _ diff --git a/backend/src/LibDB/DeferredResolver.fs b/backend/src/LibDB/DeferredResolver.fs index a1edf9d61b..2aa89a4bb6 100644 --- a/backend/src/LibDB/DeferredResolver.fs +++ b/backend/src/LibDB/DeferredResolver.fs @@ -200,6 +200,7 @@ let rec private reResolveTypeRef | PT.TUInt64 | PT.TInt128 | PT.TUInt128 + | PT.TInt | PT.TFloat | PT.TChar | PT.TString @@ -350,6 +351,7 @@ and private reResolveExpr | PT.EUInt64 _ | PT.EInt128 _ | PT.EUInt128 _ + | PT.EInt _ | PT.EFloat _ | PT.EChar _ | PT.EVariable _ diff --git a/backend/src/LibDB/DependencyExtractor.fs b/backend/src/LibDB/DependencyExtractor.fs index de3c7c44e3..6bbbca770e 100644 --- a/backend/src/LibDB/DependencyExtractor.fs +++ b/backend/src/LibDB/DependencyExtractor.fs @@ -48,6 +48,7 @@ let rec private extractFromTypeRef (typeRef : PT.TypeReference) : List = | PT.MPUInt64 _ | PT.MPInt128 _ | PT.MPUInt128 _ + | PT.MPInt _ | PT.MPFloat _ | PT.MPChar _ | PT.MPString _ @@ -169,6 +171,7 @@ and extractFromExpr (expr : PT.Expr) : List = | PT.EUInt64 _ | PT.EInt128 _ | PT.EUInt128 _ + | PT.EInt _ | PT.EFloat _ | PT.EChar _ | PT.EVariable _ diff --git a/backend/src/LibDB/RuntimeTypes.fs b/backend/src/LibDB/RuntimeTypes.fs index efe4adddcc..34581bb5c1 100644 --- a/backend/src/LibDB/RuntimeTypes.fs +++ b/backend/src/LibDB/RuntimeTypes.fs @@ -134,6 +134,7 @@ module Blob = | RT.DUInt64 _ | RT.DInt128 _ | RT.DUInt128 _ + | RT.DInt _ | RT.DFloat _ | RT.DChar _ | RT.DString _ diff --git a/backend/src/LibExecution/CapabilityAnalysis.fs b/backend/src/LibExecution/CapabilityAnalysis.fs index c8b5d4013a..96b472dcc2 100644 --- a/backend/src/LibExecution/CapabilityAnalysis.fs +++ b/backend/src/LibExecution/CapabilityAnalysis.fs @@ -35,6 +35,7 @@ let rec fnRefs (expr : PT.Expr) : List = | PT.EUInt64 _ | PT.EInt128 _ | PT.EUInt128 _ + | PT.EInt _ | PT.EFloat _ | PT.EChar _ | PT.EVariable _ diff --git a/backend/src/LibExecution/Dval.fs b/backend/src/LibExecution/Dval.fs index 20db42c938..57dd4ef5f3 100644 --- a/backend/src/LibExecution/Dval.fs +++ b/backend/src/LibExecution/Dval.fs @@ -18,6 +18,13 @@ let uint64 (i : uint64) = DUInt64 i let int128 (i : System.Int128) = DInt128 i let uint128 (i : System.UInt128) = DUInt128 i +/// The default `Int`. Normalizes the bigint into `DarkInt` (small values use the +/// Finite/int64 representation; only overflow uses Infinite/bigint). +let int (b : bigint) : Dval = RuntimeTypes.Dval.int b + +/// The numeric value of an `Int` Dval as a bigint. +let asBigInt (dv : Dval) : bigint = RuntimeTypes.Dval.asBigInt dv + let string (s : string) = DString s let uuid (s : System.Guid) = DUuid s @@ -121,6 +128,7 @@ let rec isPersistable (dv : Dval) : bool = | DUInt64 _ | DInt128 _ | DUInt128 _ + | DInt _ | DFloat _ | DChar _ | DString _ diff --git a/backend/src/LibExecution/Interpreter.fs b/backend/src/LibExecution/Interpreter.fs index e6eb30a2b1..8ef35c23b0 100644 --- a/backend/src/LibExecution/Interpreter.fs +++ b/backend/src/LibExecution/Interpreter.fs @@ -56,6 +56,7 @@ let rec private isFullyKnown (vt : ValueType) : bool = | KTUInt64 | KTInt128 | KTUInt128 + | KTInt | KTFloat | KTChar | KTString @@ -198,6 +199,7 @@ let rec checkAndExtractMatchPattern | MPUInt64 l, DUInt64 r -> l = r, [] | MPInt128 l, DInt128 r -> l = r, [] | MPUInt128 l, DUInt128 r -> l = r, [] + | MPInt l, DInt r -> l = DarkInt.toBigInt r, [] | MPFloat l, DFloat r -> l = r, [] | MPChar l, DChar r -> l = r, [] | MPString l, DString r -> l = r, [] @@ -247,6 +249,7 @@ let rec checkAndExtractMatchPattern | MPUInt64 _, _ | MPInt128 _, _ | MPUInt128 _, _ + | MPInt _, _ | MPFloat _, _ | MPChar _, _ | MPString _, _ diff --git a/backend/src/LibExecution/ProgramTypes.fs b/backend/src/LibExecution/ProgramTypes.fs index f815f5e438..380c8c26e5 100644 --- a/backend/src/LibExecution/ProgramTypes.fs +++ b/backend/src/LibExecution/ProgramTypes.fs @@ -283,6 +283,7 @@ type MatchPattern = | MPUInt64 of id * uint64 | MPInt128 of id * System.Int128 | MPUInt128 of id * System.UInt128 + | MPInt of id * bigint | MPFloat of id * Sign * string * string @@ -342,6 +343,7 @@ type TypeReference = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat @@ -395,6 +397,7 @@ type Expr = | EUInt64 of id * uint64 | EInt128 of id * System.Int128 | EUInt128 of id * System.UInt128 + | EInt of id * bigint // Allow the user to have arbitrarily big numbers, even if they don't make sense as // floats. The float is split as we want to preserve what the user entered. @@ -561,6 +564,7 @@ module Expr = | EUInt64(id, _) | EInt128(id, _) | EUInt128(id, _) + | EInt(id, _) | EChar(id, _) | EString(id, _) | EFloat(id, _, _, _) diff --git a/backend/src/LibExecution/ProgramTypesAst.fs b/backend/src/LibExecution/ProgramTypesAst.fs index 1757a65764..af32f6d4be 100644 --- a/backend/src/LibExecution/ProgramTypesAst.fs +++ b/backend/src/LibExecution/ProgramTypesAst.fs @@ -22,6 +22,7 @@ let rec symbolsUsedInExpr (expr : Expr) : Set = | EUInt64 _ | EInt128 _ | EUInt128 _ + | EInt _ | EFloat _ | EChar _ -> Set.empty @@ -129,6 +130,7 @@ let rec unqualifiedResolvedNamesInExpr (expr : Expr) : Set = | EUInt64 _ | EInt128 _ | EUInt128 _ + | EInt _ | EFloat _ | EChar _ -> Set.empty diff --git a/backend/src/LibExecution/ProgramTypesToDarkTypes.fs b/backend/src/LibExecution/ProgramTypesToDarkTypes.fs index 5684fcd0cd..62bc64094f 100644 --- a/backend/src/LibExecution/ProgramTypesToDarkTypes.fs +++ b/backend/src/LibExecution/ProgramTypesToDarkTypes.fs @@ -303,6 +303,7 @@ module TypeReference = | PT.TUInt64 -> "TUInt64", [] | PT.TInt128 -> "TInt128", [] | PT.TUInt128 -> "TUInt128", [] + | PT.TInt -> "TInt", [] | PT.TFloat -> "TFloat", [] | PT.TChar -> "TChar", [] | PT.TString -> "TString", [] @@ -351,6 +352,7 @@ module TypeReference = | DEnum(_, _, [], "TUInt32", []) -> PT.TUInt32 | DEnum(_, _, [], "TInt128", []) -> PT.TInt128 | DEnum(_, _, [], "TUInt128", []) -> PT.TUInt128 + | DEnum(_, _, [], "TInt", []) -> PT.TInt | DEnum(_, _, [], "TFloat", []) -> PT.TFloat | DEnum(_, _, [], "TChar", []) -> PT.TChar | DEnum(_, _, [], "TString", []) -> PT.TString @@ -438,6 +440,7 @@ module MatchPattern = | PT.MPUInt32(id, i) -> "MPUInt32", [ DInt64(int64 id); DUInt32 i ] | PT.MPInt128(id, i) -> "MPInt128", [ DInt64(int64 id); DInt128 i ] | PT.MPUInt128(id, i) -> "MPUInt128", [ DInt64(int64 id); DUInt128 i ] + | PT.MPInt(id, i) -> "MPInt", [ DInt64(int64 id); Dval.int i ] | PT.MPFloat(id, sign, whole, remainder) -> "MPFloat", @@ -490,6 +493,8 @@ module MatchPattern = PT.MPInt128(uint64 id, i) | DEnum(_, _, [], "MPUInt128", [ DInt64 id; DUInt128 i ]) -> PT.MPUInt128(uint64 id, i) + | DEnum(_, _, [], "MPInt", [ DInt64 id; (DInt _) as intVal ]) -> + PT.MPInt(uint64 id, Dval.asBigInt intVal) | DEnum(_, _, [], @@ -756,6 +761,7 @@ module Expr = | PT.EUInt32(id, i) -> "EUInt32", [ DInt64(int64 id); DUInt32 i ] | PT.EInt128(id, i) -> "EInt128", [ DInt64(int64 id); DInt128 i ] | PT.EUInt128(id, i) -> "EUInt128", [ DInt64(int64 id); DUInt128 i ] + | PT.EInt(id, i) -> "EInt", [ DInt64(int64 id); Dval.int i ] | PT.EFloat(id, sign, whole, remainder) -> "EFloat", [ DInt64(int64 id); Sign.toDT sign; DString whole; DString remainder ] @@ -945,6 +951,8 @@ module Expr = PT.EInt128(uint64 id, i) | DEnum(_, _, [], "EUInt128", [ DInt64 id; DUInt128 i ]) -> PT.EUInt128(uint64 id, i) + | DEnum(_, _, [], "EInt", [ DInt64 id; (DInt _) as intVal ]) -> + PT.EInt(uint64 id, Dval.asBigInt intVal) | DEnum(_, _, [], "EFloat", [ DInt64 id; sign; DString whole; DString remainder ]) -> PT.EFloat(uint64 id, Sign.fromDT sign, whole, remainder) | DEnum(_, _, [], "EChar", [ DInt64 id; DString c ]) -> PT.EChar(uint64 id, c) diff --git a/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs b/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs index e04ef8b868..c362fb082c 100644 --- a/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs +++ b/backend/src/LibExecution/ProgramTypesToRuntimeTypes.fs @@ -85,6 +85,7 @@ module TypeReference = | PT.TUInt64 -> RT.TUInt64 | PT.TInt128 -> RT.TInt128 | PT.TUInt128 -> RT.TUInt128 + | PT.TInt -> RT.TInt | PT.TFloat -> RT.TFloat @@ -130,6 +131,7 @@ module TypeReference = | PT.TUInt64 -> RT.ValueType.Known RT.KTUInt64 | PT.TInt128 -> RT.ValueType.Known RT.KTInt128 | PT.TUInt128 -> RT.ValueType.Known RT.KTUInt128 + | PT.TInt -> RT.ValueType.Known RT.KTInt | PT.TFloat -> RT.ValueType.Known RT.KTFloat | PT.TChar -> RT.ValueType.Known RT.KTChar | PT.TString -> RT.ValueType.Known RT.KTString @@ -247,6 +249,7 @@ module MatchPattern = | PT.MPUInt64(_, i) -> RT.MPUInt64 i, symbols, rc | PT.MPInt128(_, i) -> RT.MPInt128 i, symbols, rc | PT.MPUInt128(_, i) -> RT.MPUInt128 i, symbols, rc + | PT.MPInt(_, i) -> RT.MPInt i, symbols, rc | PT.MPFloat(_, sign, whole, frac) -> RT.MPFloat(makeFloat sign whole frac), symbols, rc @@ -358,6 +361,7 @@ module MatchPattern = | RT.MPUInt64 i -> RT.MPUInt64 i | RT.MPInt128 i -> RT.MPInt128 i | RT.MPUInt128 i -> RT.MPUInt128 i + | RT.MPInt i -> RT.MPInt i | RT.MPFloat f -> RT.MPFloat f | RT.MPChar c -> RT.MPChar c | RT.MPString s -> RT.MPString s @@ -592,6 +596,7 @@ module Expr = | PT.EInt32(_id, num) -> justLoadDval (RT.DInt32 num) | PT.EInt64(_id, num) -> justLoadDval (RT.DInt64 num) | PT.EInt128(_id, num) -> justLoadDval (RT.DInt128 num) + | PT.EInt(_id, num) -> justLoadDval (RT.Dval.int num) | PT.EUInt8(_id, num) -> justLoadDval (RT.DUInt8 num) | PT.EUInt16(_id, num) -> justLoadDval (RT.DUInt16 num) | PT.EUInt32(_id, num) -> justLoadDval (RT.DUInt32 num) @@ -1238,6 +1243,7 @@ module PackageValue = | PT.EUInt64(_, i) -> RT.DUInt64 i | PT.EInt128(_, i) -> RT.DInt128 i | PT.EUInt128(_, i) -> RT.DUInt128 i + | PT.EInt(_, i) -> RT.Dval.int i | PT.EFloat(_, sign, whole, part) -> let str = (if sign = Positive then "" else "-") + whole + "." + part match System.Double.TryParse(str) with diff --git a/backend/src/LibExecution/RTQueryCompiler.fs b/backend/src/LibExecution/RTQueryCompiler.fs index 4efe50dee7..ae8d789005 100644 --- a/backend/src/LibExecution/RTQueryCompiler.fs +++ b/backend/src/LibExecution/RTQueryCompiler.fs @@ -213,6 +213,14 @@ let rec symbolicToSql | RT.DBool true -> Ok("1", state) | RT.DBool false -> Ok("0", state) | RT.DInt64 n -> Ok(string n, state) + // The arbitrary-precision `Int` is deliberately NOT queryable yet: SQLite has + // no native bignum, so emitting it as a number would let values past 2^63 be + // compared as lossy floats (silently wrong). Reject loudly instead — use a + // sized type like `Int64` for indexed numeric fields. (A future order- + // preserving encoded projection will make `Int` queryable.) + | RT.DInt _ -> + Error + "The arbitrary-precision Int type is not yet queryable in DB.query. Use a sized integer type (e.g. Int64) for fields you filter or sort on." | RT.DFloat f -> Ok(string f, state) | RT.DString _ -> // Use a parameter to prevent SQL injection @@ -620,6 +628,7 @@ let compileLambda | RT.DUInt64 _ -> "UInt64" | RT.DInt128 _ -> "Int128" | RT.DUInt128 _ -> "UInt128" + | RT.DInt _ -> "Int" | RT.DFloat _ -> "Float" | RT.DChar _ -> "Char" | RT.DString _ -> "String" diff --git a/backend/src/LibExecution/RuntimeTypes.fs b/backend/src/LibExecution/RuntimeTypes.fs index 8e6b5ff2a5..a538dbfb3a 100644 --- a/backend/src/LibExecution/RuntimeTypes.fs +++ b/backend/src/LibExecution/RuntimeTypes.fs @@ -143,6 +143,8 @@ type KnownType = | KTUInt64 | KTInt128 | KTUInt128 + // arbitrary-precision integer; the default `Int` + | KTInt | KTFloat | KTChar | KTString @@ -156,7 +158,7 @@ type KnownType = | KTStream of ValueType /// `let empty = []` // KTList Unknown - /// `let intList = [1]` // KTList (ValueType.Known KTInt64) + /// `let intList = [1]` // KTList (ValueType.Known KTInt) | KTList of ValueType /// Intuitively, since `Dval`s generate `KnownType`s, you would think that we can @@ -169,7 +171,7 @@ type KnownType = | KTTuple of ValueType * ValueType * List /// let f = (fun x -> x) // KTFn([Unknown], Unknown) - /// let intF = (fun (x: Int) -> x) // KTFn([Known KTInt64], Unknown) + /// let intF = (fun (x: Int) -> x) // KTFn([Known KTInt], Unknown) /// /// Note that we could theoretically know some return types by analyzing the /// code or type signatures of functions. We don't do this yet as it's @@ -209,6 +211,104 @@ and [] ValueType = +/// The payload of the default `Int` (`DInt`). +/// Int64-range values are `Finite`; larger values are `Infinite`. +/// Use `DarkInt.ofBigInt` to preserve that invariant. +/// +/// CLEANUP: because this is a DU, small Ints still carry space for the bigint +/// case plus a tag, not just the Int64 value. If profiling shows this matters, +/// revisit the representation or consider caching common small values. +[] +type DarkInt = + | Finite of finite : int64 + | Infinite of infinite : bigint + +module DarkInt = + let private int64Min = bigint System.Int64.MinValue + let private int64Max = bigint System.Int64.MaxValue + + /// Smart constructor enforcing the invariant: a value fitting Int64 becomes + /// `Finite`, only overflow becomes `Infinite`. + let ofBigInt (b : bigint) : DarkInt = + if b >= int64Min && b <= int64Max then + DarkInt.Finite(int64 b) + else + DarkInt.Infinite b + + let toBigInt (di : DarkInt) : bigint = + match di with + | DarkInt.Finite i -> bigint i + | DarkInt.Infinite b -> b + + let isZero (di : DarkInt) : bool = + match di with + | DarkInt.Finite i -> i = 0L + | DarkInt.Infinite _ -> false // 0 is always Finite, so an Infinite is never zero + + /// Compare by numeric value (NOT by case tag — an Infinite is always outside + /// Int64 range, so tag order would be wrong). + let compare (a : DarkInt) (b : DarkInt) : int = + match a, b with + | DarkInt.Finite x, DarkInt.Finite y -> + if x < y then -1 + elif x > y then 1 + else 0 + | _ -> + let bx, by = toBigInt a, toBigInt b + if bx < by then -1 + elif bx > by then 1 + else 0 + + // Arithmetic: int64 fast path on Finite/Finite, promoting to bigint only on + // overflow; bigint otherwise. Results normalize through `ofBigInt`. + let add (a : DarkInt) (b : DarkInt) : DarkInt = + match a, b with + | DarkInt.Finite x, DarkInt.Finite y -> + try + DarkInt.Finite(Checked.(+) x y) + with :? System.OverflowException -> + ofBigInt (bigint x + bigint y) + | _ -> ofBigInt (toBigInt a + toBigInt b) + + let subtract (a : DarkInt) (b : DarkInt) : DarkInt = + match a, b with + | DarkInt.Finite x, DarkInt.Finite y -> + try + DarkInt.Finite(Checked.(-) x y) + with :? System.OverflowException -> + ofBigInt (bigint x - bigint y) + | _ -> ofBigInt (toBigInt a - toBigInt b) + + let multiply (a : DarkInt) (b : DarkInt) : DarkInt = + match a, b with + | DarkInt.Finite x, DarkInt.Finite y -> + try + DarkInt.Finite(Checked.(*) x y) + with :? System.OverflowException -> + ofBigInt (bigint x * bigint y) + | _ -> ofBigInt (toBigInt a * toBigInt b) + + /// Integer division; caller must ensure the divisor is non-zero. + let divide (a : DarkInt) (b : DarkInt) : DarkInt = + match a, b with + // Int64 division overflows only on MinValue / -1; promote that case. + | DarkInt.Finite x, DarkInt.Finite y -> + try + DarkInt.Finite(x / y) + with :? System.OverflowException -> + ofBigInt (bigint x / bigint y) + | _ -> ofBigInt (toBigInt a / toBigInt b) + + let negate (a : DarkInt) : DarkInt = + match a with + | DarkInt.Finite x -> + if x = System.Int64.MinValue then + ofBigInt (-(bigint x)) + else + DarkInt.Finite(-x) + | DarkInt.Infinite b -> ofBigInt (-b) + + type TypeReference = | TUnit | TBool @@ -222,6 +322,8 @@ type TypeReference = | TUInt64 | TInt128 | TUInt128 + // arbitrary-precision integer; the default `Int` + | TInt | TFloat | TChar | TString @@ -260,6 +362,7 @@ type TypeReference = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString @@ -335,6 +438,7 @@ type MatchPattern = | MPUInt64 of uint64 | MPInt128 of System.Int128 | MPUInt128 of System.UInt128 + | MPInt of bigint | MPFloat of float | MPChar of string | MPString of string @@ -640,6 +744,11 @@ and [] Dval = | DUInt64 of uint64 | DInt128 of System.Int128 | DUInt128 of System.UInt128 + // The default `Int`: arbitrary precision, with the Finite/Infinite split + // contained in `DarkInt`. + // CLEANUP: small Ints carry the memory overhead of the bigint case plus a tag. + // Revisit the representation if profiling shows Int memory or GC pressure. + | DInt of DarkInt | DFloat of double @@ -1200,6 +1309,15 @@ module TypeDeclaration = // Functions for working with Dark runtime values module Dval = + /// Constructs an `Int` Dval from a bigint, normalizing through `DarkInt`. + let int (b : bigint) : Dval = DInt(DarkInt.ofBigInt b) + + /// The numeric value of an `Int` Dval as a bigint. + let asBigInt (dv : Dval) : bigint = + match dv with + | DInt i -> DarkInt.toBigInt i + | _ -> Exception.raiseInternal "asBigInt called on a non-Int Dval" [] + let rec toValueType (dv : Dval) : ValueType = match dv with | DUnit -> ValueType.Known KTUnit @@ -1216,6 +1334,7 @@ module Dval = | DUInt64 _ -> ValueType.Known KTUInt64 | DInt128 _ -> ValueType.Known KTInt128 | DUInt128 _ -> ValueType.Known KTUInt128 + | DInt _ -> ValueType.Known KTInt | DFloat _ -> ValueType.Known KTFloat | DChar _ -> ValueType.Known KTChar | DString _ -> ValueType.Known KTString @@ -1299,6 +1418,7 @@ module Dval = | DUInt64 _ | DInt128 _ | DUInt128 _ + | DInt _ | DFloat _ | DChar _ | DString _ @@ -1980,6 +2100,7 @@ module Types = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString @@ -2060,6 +2181,7 @@ module TypeReference = | TUInt64 -> return ValueType.Known KTUInt64 | TInt128 -> return ValueType.Known KTInt128 | TUInt128 -> return ValueType.Known KTUInt128 + | TInt -> return ValueType.Known KTInt | TFloat -> return ValueType.Known KTFloat | TChar -> return ValueType.Known KTChar | TString -> return ValueType.Known KTString @@ -2124,6 +2246,7 @@ module TypeReference = | KTUInt64 -> TUInt64 | KTInt128 -> TInt128 | KTUInt128 -> TUInt128 + | KTInt -> TInt | KTFloat -> TFloat | KTChar -> TChar | KTString -> TString @@ -2161,6 +2284,7 @@ module TypeReference = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString diff --git a/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs b/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs index ec1e844dac..124072a10a 100644 --- a/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs +++ b/backend/src/LibExecution/RuntimeTypesToDarkTypes.fs @@ -236,6 +236,7 @@ module TypeReference = | TUInt64 -> "TUInt64", [] | TInt128 -> "TInt128", [] | TUInt128 -> "TUInt128", [] + | TInt -> "TInt", [] | TFloat -> "TFloat", [] | TChar -> "TChar", [] | TString -> "TString", [] @@ -284,6 +285,7 @@ module TypeReference = | DEnum(_, _, [], "TUInt64", []) -> TUInt64 | DEnum(_, _, [], "TInt128", []) -> TInt128 | DEnum(_, _, [], "TUInt128", []) -> TUInt128 + | DEnum(_, _, [], "TInt", []) -> TInt | DEnum(_, _, [], "TFloat", []) -> TFloat | DEnum(_, _, [], "TChar", []) -> TChar | DEnum(_, _, [], "TString", []) -> TString @@ -367,6 +369,7 @@ module MatchPattern = | MPUInt64 i -> "MPUInt64", [ DUInt64 i ] | MPInt128 i -> "MPInt128", [ DInt128 i ] | MPUInt128 i -> "MPUInt128", [ DUInt128 i ] + | MPInt i -> "MPInt", [ Dval.int i ] | MPFloat f -> "MPFloat", [ DFloat f ] | MPChar c -> "MPChar", [ DString c ] | MPString s -> "MPString", [ DString s ] @@ -407,6 +410,7 @@ module MatchPattern = | DEnum(_, _, [], "MPUInt64", [ DUInt64 i ]) -> MPUInt64 i | DEnum(_, _, [], "MPInt128", [ DInt128 i ]) -> MPInt128 i | DEnum(_, _, [], "MPUInt128", [ DUInt128 i ]) -> MPUInt128(i) + | DEnum(_, _, [], "MPInt", [ (DInt _) as v ]) -> MPInt(Dval.asBigInt v) | DEnum(_, _, [], "MPFloat", [ DFloat f ]) -> MPFloat f | DEnum(_, _, [], "MPChar", [ DString c ]) -> MPChar c | DEnum(_, _, [], "MPString", [ DString s ]) -> MPString s @@ -448,6 +452,7 @@ module KnownType = | KTUInt64 -> "KTUInt64", [] | KTInt128 -> "KTInt128", [] | KTUInt128 -> "KTUInt128", [] + | KTInt -> "KTInt", [] | KTFloat -> "KTFloat", [] | KTChar -> "KTChar", [] | KTString -> "KTString", [] @@ -496,6 +501,7 @@ module KnownType = | DEnum(_, _, [], "KTUInt64", []) -> KTUInt64 | DEnum(_, _, [], "KTInt128", []) -> KTInt128 | DEnum(_, _, [], "KTUInt128", []) -> KTUInt128 + | DEnum(_, _, [], "KTInt", []) -> KTInt | DEnum(_, _, [], "KTFloat", []) -> KTFloat | DEnum(_, _, [], "KTChar", []) -> KTChar | DEnum(_, _, [], "KTString", []) -> KTString @@ -674,6 +680,7 @@ module Dval = | DUInt64 i -> mk "DUInt64" [ DUInt64 i ] | DInt128 i -> mk "DInt128" [ DInt128 i ] | DUInt128 i -> mk "DUInt128" [ DUInt128 i ] + | DInt i -> mk "DInt" [ DInt i ] | DFloat f -> mk "DFloat" [ DFloat f ] | DChar c -> mk "DChar" [ DChar c ] | DString s -> mk "DString" [ DString s ] @@ -750,6 +757,10 @@ module Dval = | DEnum(_, _, [], "DUInt64", [ DUInt64 i ]) -> DUInt64 i | DEnum(_, _, [], "DInt128", [ DInt128 i ]) -> DInt128 i | DEnum(_, _, [], "DUInt128", [ DUInt128 i ]) -> DUInt128 i + // Both forms of Int (small and big) are stored under the one "DInt" name. + // Rebuild it with Dval.int so the value always comes back in the right form. + | DEnum(_, _, [], "DInt", [ (DInt _) as v ]) -> + RuntimeTypes.Dval.int (RuntimeTypes.Dval.asBigInt v) | DEnum(_, _, [], "DFloat", [ DFloat f ]) -> DFloat f | DEnum(_, _, [], "DChar", [ DChar c ]) -> DChar c | DEnum(_, _, [], "DString", [ DString s ]) -> DString s diff --git a/backend/src/LibExecution/TypeChecker.fs b/backend/src/LibExecution/TypeChecker.fs index 69d7652364..432b3f1887 100644 --- a/backend/src/LibExecution/TypeChecker.fs +++ b/backend/src/LibExecution/TypeChecker.fs @@ -52,6 +52,7 @@ let rec unifyValueType | TUInt64, ValueType.Known KTUInt64 -> return Ok tst | TInt128, ValueType.Known KTInt128 -> return Ok tst | TUInt128, ValueType.Known KTUInt128 -> return Ok tst + | TInt, ValueType.Known KTInt -> return Ok tst | TFloat, ValueType.Known KTFloat -> return Ok tst diff --git a/backend/src/LibExecution/ValueType.fs b/backend/src/LibExecution/ValueType.fs index 1c1599e264..15c843995a 100644 --- a/backend/src/LibExecution/ValueType.fs +++ b/backend/src/LibExecution/ValueType.fs @@ -67,6 +67,7 @@ let rec private mergeKnownTypes | KTUInt64, KTUInt64 -> KTUInt64 |> Ok | KTInt128, KTInt128 -> KTInt128 |> Ok | KTUInt128, KTUInt128 -> KTUInt128 |> Ok + | KTInt, KTInt -> KTInt |> Ok | KTFloat, KTFloat -> KTFloat |> Ok | KTChar, KTChar -> KTChar |> Ok | KTString, KTString -> KTString |> Ok diff --git a/backend/src/LibParser/FSharpToWrittenTypes.fs b/backend/src/LibParser/FSharpToWrittenTypes.fs index 85631675b2..59235389d8 100644 --- a/backend/src/LibParser/FSharpToWrittenTypes.fs +++ b/backend/src/LibParser/FSharpToWrittenTypes.fs @@ -76,6 +76,7 @@ module TypeReference = | [], "UInt32", [] -> WT.TUInt32 | [], "Int128", [] -> WT.TInt128 | [], "UInt128", [] -> WT.TUInt128 + | [], "Int", [] -> WT.TInt | [], "String", [] -> WT.TString | [], "Char", [] -> WT.TChar | [], "Float", [] -> WT.TFloat @@ -248,6 +249,12 @@ module MatchPattern = | false, _ -> raiseParserError "Failed to parse UInt128" [ "pat", pat ] (Some pat.Range) + | SynPat.Const(SynConst.UserNum(s, "I"), _) -> + match System.Numerics.BigInteger.TryParse(s) with + | true, i -> WT.MPInt(id, i) + | false, _ -> + raiseParserError "Failed to parse Int" [ "pat", pat ] (Some pat.Range) + | SynPat.Const(SynConst.Double d, _) -> let sign, whole, fraction = readFloat d WT.MPFloat(id, sign, whole, fraction) @@ -401,6 +408,15 @@ module Expr = | false, _ -> raiseParserError "Failed to parse UInt128" [ "ast", ast ] (Some ast.Range) + // `1I` is the arbitrary-precision `Int`. F#'s bigint suffix; the suffix-less + // default lives in the Dark (tree-sitter) parser, which can distinguish a + // bare literal from `1l` (Int32) — FCS cannot. + | SynExpr.Const(SynConst.UserNum(s, "I"), _) -> + match System.Numerics.BigInteger.TryParse(s) with + | true, i -> WT.EInt(id, i) + | false, _ -> + raiseParserError "Failed to parse Int" [ "ast", ast ] (Some ast.Range) + | SynExpr.Const(SynConst.Char c, _) -> WT.EChar(id, string c) | SynExpr.Const(SynConst.Bool b, _) -> WT.EBool(id, b) | SynExpr.Const(SynConst.Double d, _) -> diff --git a/backend/src/LibParser/WrittenTypes.fs b/backend/src/LibParser/WrittenTypes.fs index 05273e71df..f3dd4e19a4 100644 --- a/backend/src/LibParser/WrittenTypes.fs +++ b/backend/src/LibParser/WrittenTypes.fs @@ -73,6 +73,7 @@ type MatchPattern = | MPUInt32 of id * uint32 | MPInt128 of id * System.Int128 | MPUInt128 of id * System.UInt128 + | MPInt of id * bigint | MPFloat of id * Sign * string * string | MPChar of id * string | MPString of id * string @@ -126,6 +127,7 @@ type TypeReference = | TUInt32 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString @@ -159,6 +161,7 @@ type Expr = | EUInt32 of id * uint32 | EInt128 of id * System.Int128 | EUInt128 of id * System.UInt128 + | EInt of id * bigint | EFloat of id * Sign * string * string | EChar of id * string | EString of id * List diff --git a/backend/src/LibParser/WrittenTypesToProgramTypes.fs b/backend/src/LibParser/WrittenTypesToProgramTypes.fs index 31a275f79f..90d29bcbb1 100644 --- a/backend/src/LibParser/WrittenTypesToProgramTypes.fs +++ b/backend/src/LibParser/WrittenTypesToProgramTypes.fs @@ -65,6 +65,7 @@ module TypeReference = | WT.TUInt64 -> return PT.TUInt64 | WT.TInt128 -> return PT.TInt128 | WT.TUInt128 -> return PT.TUInt128 + | WT.TInt -> return PT.TInt | WT.TFloat -> return PT.TFloat | WT.TChar -> return PT.TChar | WT.TString -> return PT.TString @@ -189,6 +190,7 @@ module MatchPattern = | WT.MPUInt32(id, i) -> (context, PT.MPUInt32(id, i)) | WT.MPInt128(id, i) -> (context, PT.MPInt128(id, i)) | WT.MPUInt128(id, i) -> (context, PT.MPUInt128(id, i)) + | WT.MPInt(id, i) -> (context, PT.MPInt(id, i)) | WT.MPBool(id, b) -> (context, PT.MPBool(id, b)) | WT.MPChar(id, c) -> (context, PT.MPChar(id, c)) | WT.MPString(id, s) -> (context, PT.MPString(id, s)) @@ -270,6 +272,7 @@ module Expr = | WT.EUInt32(id, num) -> return PT.EUInt32(id, num) | WT.EInt128(id, num) -> return PT.EInt128(id, num) | WT.EUInt128(id, num) -> return PT.EUInt128(id, num) + | WT.EInt(id, num) -> return PT.EInt(id, num) | WT.EString(id, segments) -> let! segments = Ply.List.mapSequentially diff --git a/backend/src/LibSerialization/Binary/Serializers/PT/Expr.fs b/backend/src/LibSerialization/Binary/Serializers/PT/Expr.fs index 3252198094..97b21295ad 100644 --- a/backend/src/LibSerialization/Binary/Serializers/PT/Expr.fs +++ b/backend/src/LibSerialization/Binary/Serializers/PT/Expr.fs @@ -208,6 +208,10 @@ module MatchPattern = w.Write 20uy w.Write id NEList.write write w patterns + | MPInt(id, value) -> + w.Write 21uy + w.Write id + String.write w (string value) let rec read (r : BinaryReader) : MatchPattern = match r.ReadByte() with @@ -300,6 +304,10 @@ module MatchPattern = let id = r.ReadUInt64() let patterns = NEList.read read r MPOr(id, patterns) + | 21uy -> + let id = r.ReadUInt64() + let value = String.read r |> System.Numerics.BigInteger.Parse + MPInt(id, value) | b -> raiseFormatError $"Invalid MatchPattern tag: {b}" @@ -439,6 +447,10 @@ module Expr = w.Write 9uy w.Write id String.write w (string value) + | EInt(id, value) -> + w.Write 35uy + w.Write id + String.write w (string value) | EBool(id, value) -> w.Write 10uy w.Write id @@ -753,4 +765,8 @@ module Expr = let id = r.ReadUInt64() let index = r.ReadInt32() EArg(id, index) + | 35uy -> + let id = r.ReadUInt64() + let value = String.read r |> System.Numerics.BigInteger.Parse + EInt(id, value) | b -> raiseFormatError $"Invalid Expr tag: {b}" diff --git a/backend/src/LibSerialization/Binary/Serializers/PT/TypeReference.fs b/backend/src/LibSerialization/Binary/Serializers/PT/TypeReference.fs index 8deb3d3b69..b7a38d6a0a 100644 --- a/backend/src/LibSerialization/Binary/Serializers/PT/TypeReference.fs +++ b/backend/src/LibSerialization/Binary/Serializers/PT/TypeReference.fs @@ -58,6 +58,7 @@ let rec write (w : BinaryWriter) (t : TypeReference) : unit = | TStream inner -> w.Write 25uy write w inner + | TInt -> w.Write 26uy let rec read (r : BinaryReader) : TypeReference = match r.ReadByte() with @@ -99,4 +100,5 @@ let rec read (r : BinaryReader) : TypeReference = TTuple(first, second, rest) | 24uy -> TBlob | 25uy -> TStream(read r) + | 26uy -> TInt | b -> raiseFormatError $"Invalid TypeReference tag: {b}" diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs b/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs index f51744bd77..c815b0c5c1 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/Dval.fs @@ -72,6 +72,7 @@ and writeKnownType (w : BinaryWriter) (kt : KnownType) = | KTStream vt -> w.Write 24uy writeValueType w vt + | KTInt -> w.Write 25uy and writeApplicableImpl (w : BinaryWriter) (app : Applicable) = match app with @@ -199,6 +200,12 @@ and writeDvalImpl (w : BinaryWriter) (dval : Dval) = // Streams are not persistable by design — lifetime is bounded by // the VM that produced them. Caller should drain to Blob first. raiseFormatError "Cannot serialize DStream — drain to a Blob first" + // Serialized as the decimal value (via DarkInt), so the wire format doesn't + // depend on the Finite/Infinite representation. + | DInt value -> + w.Write 25uy + String.write w (string (DarkInt.toBigInt value)) + let rec readDval : BinaryReader -> Dval = fun r -> readDvalImpl r and readValueType : BinaryReader -> ValueType = fun r -> readValueTypeImpl r @@ -248,6 +255,7 @@ and readKnownType (r : BinaryReader) : KnownType = | 22uy -> KTDict(readValueType r) | 23uy -> KTBlob | 24uy -> KTStream(readValueType r) + | 25uy -> KTInt | b -> raiseFormatError $"Invalid KnownType tag: {b}" and readApplicableImpl (r : BinaryReader) : Applicable = @@ -334,6 +342,7 @@ and readDvalImpl (r : BinaryReader) : Dval = let hash = String.read r let length = r.ReadInt64() DBlob(Persistent(hash, length)) + | 25uy -> Dval.int (System.Numerics.BigInteger.Parse(String.read r)) | b -> raiseFormatError $"Invalid Dval tag: {b}" diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs b/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs index 5e5f0dfc69..5d8c815060 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/Instructions.fs @@ -106,6 +106,9 @@ module MatchPattern = | MPOr patterns -> w.Write 20uy NEList.write write w patterns + | MPInt value -> + w.Write 21uy + String.write w (value.ToString()) let rec read (r : BinaryReader) : MatchPattern = match r.ReadByte() with @@ -146,6 +149,7 @@ module MatchPattern = | 20uy -> let patterns = NEList.read read r MPOr(patterns) + | 21uy -> MPInt(System.Numerics.BigInteger.Parse(String.read r)) | b -> raiseFormatError $"Invalid MatchPattern tag: {b}" diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/TypeReference.fs b/backend/src/LibSerialization/Binary/Serializers/RT/TypeReference.fs index 087e811ae9..3328fa51c9 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/TypeReference.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/TypeReference.fs @@ -58,6 +58,7 @@ let rec write (w : BinaryWriter) (t : TypeReference) : unit = | TStream inner -> w.Write 25uy write w inner + | TInt -> w.Write 26uy let rec read (r : BinaryReader) : TypeReference = match r.ReadByte() with @@ -97,4 +98,5 @@ let rec read (r : BinaryReader) : TypeReference = | 23uy -> TDB(read r) | 24uy -> TBlob | 25uy -> TStream(read r) + | 26uy -> TInt | b -> raiseFormatError $"Invalid TypeReference tag: {b}" diff --git a/backend/src/LibSerialization/Binary/Serializers/RT/ValueType.fs b/backend/src/LibSerialization/Binary/Serializers/RT/ValueType.fs index cfa2bf5ae9..280acdeff0 100644 --- a/backend/src/LibSerialization/Binary/Serializers/RT/ValueType.fs +++ b/backend/src/LibSerialization/Binary/Serializers/RT/ValueType.fs @@ -62,6 +62,7 @@ and writeKnownType (w : BinaryWriter) (kt : KnownType) : unit = | KTStream vt -> w.Write 24uy write w vt + | KTInt -> w.Write 25uy let rec read (r : BinaryReader) : ValueType = match r.ReadByte() with @@ -106,4 +107,5 @@ and readKnownType (r : BinaryReader) : KnownType = | 22uy -> KTDict(read r) | 23uy -> KTBlob | 24uy -> KTStream(read r) + | 25uy -> KTInt | b -> raiseFormatError $"Invalid KnownType tag: {b}" diff --git a/backend/src/LibSerialization/DvalReprInternalQueryable.fs b/backend/src/LibSerialization/DvalReprInternalQueryable.fs index e4b8b8f43f..4f18190c08 100644 --- a/backend/src/LibSerialization/DvalReprInternalQueryable.fs +++ b/backend/src/LibSerialization/DvalReprInternalQueryable.fs @@ -107,6 +107,7 @@ let rec private toJsonV0 | DUInt64 i -> w.WriteNumberValue i | DInt128 i -> w.WriteRawValue(i.ToString()) | DUInt128 i -> w.WriteRawValue(i.ToString()) + | DInt i -> w.WriteRawValue(string (DarkInt.toBigInt i)) | DFloat f -> if System.Double.IsNaN f then @@ -237,6 +238,8 @@ let parseJsonV0 j.GetRawText() |> System.Int128.Parse |> DInt128 |> Ply | TUInt128, JsonValueKind.Number -> j.GetRawText() |> System.UInt128.Parse |> DUInt128 |> Ply + | TInt, JsonValueKind.Number -> + j.GetRawText() |> System.Numerics.BigInteger.Parse |> Dval.int |> Ply | TFloat, JsonValueKind.Number -> j.GetDouble() |> DFloat |> Ply | TFloat, JsonValueKind.String -> @@ -406,6 +409,7 @@ let parseJsonV0 | TUInt64, _ | TInt128, _ | TUInt128, _ + | TInt, _ | TFloat, _ | TChar, _ | TString, _ @@ -438,6 +442,7 @@ module Test = | DUInt64 _ | DInt128 _ | DUInt128 _ + | DInt _ | DFloat _ | DChar _ | DString _ diff --git a/backend/src/LibSerialization/Hashing/Canonical.fs b/backend/src/LibSerialization/Hashing/Canonical.fs index 6ac99ce170..50c3f662c6 100644 --- a/backend/src/LibSerialization/Hashing/Canonical.fs +++ b/backend/src/LibSerialization/Hashing/Canonical.fs @@ -209,6 +209,7 @@ let writeTypeReference | PT.TStream inner -> w.Write 25uy writeTypeReference mode w inner + | PT.TInt -> w.Write 26uy // ===================== @@ -298,6 +299,9 @@ let writeMatchPattern (w : BinaryWriter) (pattern : PT.MatchPattern) = | PT.MPOr(_id, patterns) -> w.Write 20uy Common.NEList.write writeMatchPattern w patterns + | PT.MPInt(_id, value) -> + w.Write 21uy + Common.String.write w (string value) // ===================== @@ -491,6 +495,9 @@ let writeExpr (mode : HashRefMode) (w : BinaryWriter) (expr : PT.Expr) = | PT.EArg(_id, index) -> w.Write 34uy w.Write index + | PT.EInt(_id, value) -> + w.Write 35uy + Common.String.write w (string value) // ===================== diff --git a/backend/testfiles/execution/cloud/db.dark b/backend/testfiles/execution/cloud/db.dark index 73d4e04e74..945349ac20 100644 --- a/backend/testfiles/execution/cloud/db.dark +++ b/backend/testfiles/execution/cloud/db.dark @@ -22,6 +22,13 @@ type SortedX = { x: String; sortBy: Int64 } [] type SortedXDB = SortedX +// has both an arbitrary-precision Int field (not queryable) and an Int64 field +// (queryable), to check the v1 query-compiler policy +type IntFields = { big: Int; sized: Int64 } + +[] +type IntFieldsDB = IntFields + // simple data stores of Enums // , and Records containing Enums @@ -754,6 +761,16 @@ module FindAll = // bad variable name friendsError (fun p -> let x = 32L in true && p.height > y) = sqlerror="This variable is not defined: y" + // arbitrary-precision Int is not queryable yet (v1): comparing an Int field + // rejects loudly, while the sized Int64 field compiles fine. + (Stdlib.DB.query IntFieldsDB (fun p -> p.big > 5I)) = sqlerror="The arbitrary-precision Int type is not yet queryable in DB.query. Use a sized integer type (e.g. Int64) for fields you filter or sort on." + (Stdlib.DB.query IntFieldsDB (fun p -> p.sized > 5L)) = [] + + // storage is untouched: a >Int64 value round-trips through set/get fine + // (it's only numeric *querying* that's restricted) + (let _ = Stdlib.DB.set (IntFields { big = 9223372036854775808I; sized = 5L }) "k" IntFieldsDB + Stdlib.DB.get "k" IntFieldsDB) = Stdlib.Option.Option.Some(IntFields { big = 9223372036854775808I; sized = 5L }) + // sql injection (friendsError (fun p -> "; select * from users;" == p.name)) = [] diff --git a/backend/testfiles/execution/stdlib/ints/int.dark b/backend/testfiles/execution/stdlib/ints/int.dark new file mode 100644 index 0000000000..4566568bb1 --- /dev/null +++ b/backend/testfiles/execution/stdlib/ints/int.dark @@ -0,0 +1,66 @@ +// Arbitrary-precision `Int` — the small/big representation split. +// +// `Int` is stored as a small (Int64-backed) value when it fits, and only +// promotes to a big representation on overflow. Values are NORMALIZED: any +// result that fits Int64 always uses the small representation. These tests +// exercise the boundary, promotion, demotion, normalization, and finite/infinite +// ordering — the properties that break if normalization is wrong. Literals use +// the F# `I` suffix because these testfiles are parsed by the F# parser. + +// --- boundary: largest small value vs first big value --- +9223372036854775807I + 0I = 9223372036854775807I // Int64.MAX stays small +9223372036854775807I + 1I = 9223372036854775808I // promotes past Int64.MAX +-9223372036854775808I - 1I = -9223372036854775809I // promotes past Int64.MIN + +// --- demotion: a big-arithmetic result that fits Int64 collapses to small --- +9223372036854775808I - 1I = 9223372036854775807I +(2I ^ 100I) - (2I ^ 100I) = 0I +(2I ^ 100I) / (2I ^ 100I) = 1I + +// --- normalization: a promoted-then-demoted result equals the small literal --- +// (this is the property that fails if a small value is left as the big rep) +(9223372036854775808I - 9223372036854775808I) == 0I = true +9223372036854775807I == (9223372036854775806I + 1I) = true + +// --- equality and ordering across representations --- +(2I ^ 64I) == (2I ^ 64I) = true +9223372036854775808I > 5I = true +5I < 9223372036854775808I = true +9223372036854775808I == 5I = false + +// --- arithmetic correctness at scale --- +2I ^ 64I = 18446744073709551616I +2I ^ 200I + = 1606938044258990275541962092341162602522202993782792835301376I + +// --- round-trips (stringify / parse / Int64 conversions) --- +Stdlib.Int.toString 5I = "5" +Stdlib.Int.toString 9223372036854775808I = "9223372036854775808" +Stdlib.Int.parse "9223372036854775808" = Stdlib.Result.Result.Ok 9223372036854775808I +Stdlib.Int.parse "5" = Stdlib.Result.Result.Ok 5I +Stdlib.Int.fromInt64 5L = 5I + +// toInt64 relies on the invariant: a small Int always fits, a big Int never does +Stdlib.Int.toInt64 5I = Stdlib.Option.Option.Some 5L +Stdlib.Int.toInt64 9223372036854775808I = Stdlib.Option.Option.None + + +// --- conversions from the fixed-width types (always succeed) --- +Stdlib.Int.fromInt8 127y = 127I +Stdlib.Int.fromUInt8 255uy = 255I +Stdlib.Int.fromInt16 32767s = 32767I +Stdlib.Int.fromUInt32 4294967295ul = 4294967295I +// values past Int64 still convert exactly +Stdlib.Int.fromUInt64 18446744073709551615UL = 18446744073709551615I +Stdlib.Int.fromUInt128 340282366920938463463374607431768211455Z = + 340282366920938463463374607431768211455I + +// --- conversions into the fixed-width types (Some if it fits, else None) --- +Stdlib.Int.toInt8 100I = Stdlib.Option.Option.Some 100y +Stdlib.Int.toInt8 200I = Stdlib.Option.Option.None +Stdlib.Int.toUInt8 (Stdlib.Int.negate 1I) = Stdlib.Option.Option.None +Stdlib.Int.toInt32 (2I ^ 40I) = Stdlib.Option.Option.None +Stdlib.Int.toInt128 (2I ^ 100I) = + Stdlib.Option.Option.Some 1267650600228229401496703205376Q +Stdlib.Int.toInt128 (2I ^ 200I) = Stdlib.Option.Option.None +Stdlib.Int.toUInt128 (2I ^ 200I) = Stdlib.Option.Option.None diff --git a/backend/testfiles/execution/stdlib/json.dark b/backend/testfiles/execution/stdlib/json.dark index ae31700887..130cbf43b4 100644 --- a/backend/testfiles/execution/stdlib/json.dark +++ b/backend/testfiles/execution/stdlib/json.dark @@ -1447,4 +1447,45 @@ module Package = // ## Nested types (lists, tuples, records, etc.) // Stdlib.Json.serialize> * Dict>>>>> = Ok "test" -// Stdlib.Json.serialize> * Dict>>>>> = Ok "test" \ No newline at end of file +// Stdlib.Json.serialize> * Dict>>>>> = Ok "test" + +module Int = + module Basic = + Stdlib.Json.serialize 0I = "0" + Stdlib.Json.serialize 12345I = "12345" + Stdlib.Json.serialize -12345I = "-12345" + Stdlib.Json.parse "0" = Result.Ok 0I + Stdlib.Json.parse "12345" = Result.Ok 12345I + Stdlib.Json.parse "-12345" = Result.Ok(-12345I) + + module Huge = + // arbitrary precision: well past Int64, exact round-trip + Stdlib.Json.serialize 123456789012345678901234567890I = "123456789012345678901234567890" + Stdlib.Json.parse "123456789012345678901234567890" = Result.Ok 123456789012345678901234567890I + Stdlib.Json.parse "-123456789012345678901234567890" = Result.Ok(-123456789012345678901234567890I) + (Stdlib.Json.parse (Stdlib.Json.serialize 99999999999999999999999999999999I)) = Result.Ok 99999999999999999999999999999999I + + module IntegerValuedFloatForms = + // integer-valued decimal/exponent forms are accepted, parsed EXACTLY + // (the same forms the fixed-width int parsers accept) + Stdlib.Json.parse "0.0" = Result.Ok 0I + Stdlib.Json.parse "-1.0" = Result.Ok(-1I) + Stdlib.Json.parse "1.2E2" = Result.Ok 120I + Stdlib.Json.parse "1.2E+2" = Result.Ok 120I + Stdlib.Json.parse "1200E-1" = Result.Ok 120I + // exact even past 2^53 — guards the precision-loss regression + Stdlib.Json.parse "9007199254740993.0" = Result.Ok 9007199254740993I + + module Errors = + // genuinely fractional values are rejected, never rounded + (Stdlib.Json.parse "1.5") |> err = Result.Error "Can't parse JSON `1.5` as type `Int` at path: `root`" + (Stdlib.Json.parse "9007199254740993.5") |> err = Result.Error "Can't parse JSON `9007199254740993.5` as type `Int` at path: `root`" + (Stdlib.Json.parse "\"42\"") |> err = Result.Error "Can't parse JSON `\"42\"` as type `Int` at path: `root`" + (Stdlib.Json.parse "null") |> err = Result.Error "Can't parse JSON `null` as type `Int` at path: `root`" + + // extreme exponents are rejected cleanly, never crash or hang the parser + (Stdlib.Json.parse "1e2147483647") |> err = Result.Error "Can't parse JSON `1e2147483647` as type `Int` at path: `root`" + (Stdlib.Json.parse "1e-2147483647") |> err = Result.Error "Can't parse JSON `1e-2147483647` as type `Int` at path: `root`" + (Stdlib.Json.parse "1e-2147483648") |> err = Result.Error "Can't parse JSON `1e-2147483648` as type `Int` at path: `root`" + // ...but zero with an extreme negative exponent is still exactly zero + Stdlib.Json.parse "0e-2147483648" = Result.Ok 0I diff --git a/backend/testfiles/execution/stdlib/language-tools/semanticTokenization.dark b/backend/testfiles/execution/stdlib/language-tools/semanticTokenization.dark index d88dd541f7..4b2c7dd6b6 100644 --- a/backend/testfiles/execution/stdlib/language-tools/semanticTokenization.dark +++ b/backend/testfiles/execution/stdlib/language-tools/semanticTokenization.dark @@ -453,6 +453,12 @@ module TokenizeExpression = ("1Z" |> tokenize) = [ (TokenType.Number, (0L, 0L), (0L, 1L)) (TokenType.Symbol, (0L, 1L), (0L, 2L)) ] + // suffixless Int literal: just a Number token, no suffix Symbol + ("1" |> tokenize) = [ (TokenType.Number, (0L, 0L), (0L, 1L)) ] + + ("12345678901234567890" |> tokenize) = + [ (TokenType.Number, (0L, 0L), (0L, 20L)) ] + ("1.0" |> tokenize) = [ (TokenType.Number, (0L, 0L), (0L, 3L)) ] ("\"hello\"" |> tokenize) = @@ -703,6 +709,16 @@ module TokenizeMatchExpression = (TokenType.Number, (1L, 7L), (1L, 8L)) (TokenType.Symbol, (1L, 8L), (1L, 9L)) ] + // suffixless Int in both pattern and rhs: Number tokens with no suffix Symbol + ("match x with\n| 1 -> 2" |> tokenize) = + [ (TokenType.Keyword, (0L, 0L), (0L, 5L)) + (TokenType.VariableName, (0L, 6L), (0L, 7L)) + (TokenType.Keyword, (0L, 8L), (0L, 12L)) + (TokenType.Symbol, (1L, 0L), (1L, 1L)) + (TokenType.Number, (1L, 2L), (1L, 3L)) + (TokenType.Symbol, (1L, 4L), (1L, 6L)) + (TokenType.Number, (1L, 7L), (1L, 8L)) ] + ("match 1L with\n| 1L -> 1L" |> tokenize) = [ (TokenType.Keyword, (0L, 0L), (0L, 5L)) (TokenType.Number, (0L, 6L), (0L, 7L)) diff --git a/backend/testfiles/serialization-artifacts/toplevels-binary-latest.bin b/backend/testfiles/serialization-artifacts/toplevels-binary-latest.bin index 8f343856d7..3ffeb449f6 100644 Binary files a/backend/testfiles/serialization-artifacts/toplevels-binary-latest.bin and b/backend/testfiles/serialization-artifacts/toplevels-binary-latest.bin differ diff --git a/backend/tests/TestUtils/TestUtils.fs b/backend/tests/TestUtils/TestUtils.fs index 0e4759a967..b7d0aeee69 100644 --- a/backend/tests/TestUtils/TestUtils.fs +++ b/backend/tests/TestUtils/TestUtils.fs @@ -328,6 +328,7 @@ module Expect = | DUInt64 _ | DInt128 _ | DUInt128 _ + | DInt _ | DFloat _ @@ -612,6 +613,7 @@ module Expect = | DUInt64 _, _ | DInt128 _, _ | DUInt128 _, _ + | DInt _, _ | DFloat _, _ | DChar _, _ | DString _, _ @@ -811,6 +813,7 @@ module Expect = | EUInt64(_, v), EUInt64(_, v') -> check path v v' | EInt128(_, v), EInt128(_, v') -> check path v v' | EUInt128(_, v), EUInt128(_, v') -> check path v v' + | EInt(_, v), EInt(_, v') -> check path v v' | EFloat(_, sign, whole, part), EFloat(_, sign', whole', part') -> check path sign sign' @@ -966,6 +969,7 @@ module Expect = | EUInt64 _, _ | EInt128 _, _ | EUInt128 _, _ + | EInt _, _ | EString _, _ | EChar _, _ | EVariable _, _ @@ -1055,6 +1059,7 @@ let visitDval (f : Dval -> 'a) (dv : Dval) : List<'a> = | DUInt64 _ | DInt128 _ | DUInt128 _ + | DInt _ | DFloat _ | DChar _ | DString _ // TODO: should actually traverse in interpolations @@ -1381,6 +1386,16 @@ let interestingDvals () : List = let sampleDvals () : List = List.concat [ List.map (fun (k, v) -> k, DInt64 v, TInt64) interestingInts + // the default `Int`, exercising both Finite and Infinite representations + [ "int small", Dval.int (bigint 5), TInt + "int int64 max", Dval.int (bigint System.Int64.MaxValue), TInt + "int int64 min", Dval.int (bigint System.Int64.MinValue), TInt + "int big", + Dval.int (System.Numerics.BigInteger.Parse "123456789012345678901234567890"), + TInt + "int big negative", + Dval.int (System.Numerics.BigInteger.Parse "-123456789012345678901234567890"), + TInt ] List.map (fun (k, v) -> k, DFloat v, TFloat) interestingFloats List.map (fun (k, v) -> k, DString v, TString) interestingStrings List.map (fun (k, v) -> k, DString v, TString) naughtyStrings diff --git a/backend/tests/Tests/Serialization.TestValues.fs b/backend/tests/Tests/Serialization.TestValues.fs index 711b9a55bc..ff311d36e5 100644 --- a/backend/tests/Tests/Serialization.TestValues.fs +++ b/backend/tests/Tests/Serialization.TestValues.fs @@ -53,6 +53,7 @@ module RuntimeTypes = RT.TUInt64 RT.TInt128 RT.TUInt128 + RT.TInt RT.TFloat @@ -93,6 +94,7 @@ module RuntimeTypes = known RT.KnownType.KTUInt64 known RT.KnownType.KTInt128 known RT.KnownType.KTUInt128 + known RT.KnownType.KTInt known RT.KnownType.KTFloat known RT.KnownType.KTChar known RT.KnownType.KTString @@ -191,6 +193,9 @@ module RuntimeTypes = RT.DUInt64 18446744073709551615UL RT.DInt128 170141183460469231731687303715884105727Q RT.DUInt128 340282366920938463463374607431768211455Z + // the default Int, both Finite (Int64 range) and Infinite (past Int64) + RT.Dval.int (bigint System.Int64.MaxValue) + RT.Dval.int (System.Numerics.BigInteger.Parse "123456789012345678901234567890") RT.DFloat(3.14159) RT.DChar "A" RT.DString "Hello, World!" @@ -232,6 +237,7 @@ module ProgramTypes = MPUInt32(id, 4294967295ul) MPInt128(id, 170141183460469231731687303715884105727Q) MPUInt128(id, 340282366920938463463374607431768211455Z) + MPInt(id, System.Numerics.BigInteger.Parse "123456789012345678901234567890") MPBool(id, false) MPChar(id, "w") MPString(id, "testing testing 123") @@ -263,6 +269,7 @@ module ProgramTypes = TUInt32 TInt128 TUInt128 + TInt TString TList TInt64 TTuple(TBool, TBool, [ TBool ]) @@ -671,7 +678,12 @@ module ProgramTypes = PT.EInt32(id, 4l) PT.EUInt32(id, 3ul) PT.EInt128(id, -1Q) - PT.EUInt128(id, 1Z) ] + PT.EUInt128(id, 1Z) + PT.EInt(id, 5I) + PT.EInt( + id, + System.Numerics.BigInteger.Parse "123456789012345678901234567890" + ) ] ) diff --git a/packages/darklang/cli/packages/propagate.dark b/packages/darklang/cli/packages/propagate.dark index b75b5890ef..7e43cee301 100644 --- a/packages/darklang/cli/packages/propagate.dark +++ b/packages/darklang/cli/packages/propagate.dark @@ -72,6 +72,7 @@ let typeReferenceEqual | (TUInt64, TUInt64) -> true | (TInt128, TInt128) -> true | (TUInt128, TUInt128) -> true + | (TInt, TInt) -> true | (TFloat, TFloat) -> true | (TChar, TChar) -> true | (TString, TString) -> true diff --git a/packages/darklang/languageTools/lsp-server/hoverInformation.dark b/packages/darklang/languageTools/lsp-server/hoverInformation.dark index c96365bab0..fd3d654cd2 100644 --- a/packages/darklang/languageTools/lsp-server/hoverInformation.dark +++ b/packages/darklang/languageTools/lsp-server/hoverInformation.dark @@ -322,6 +322,7 @@ let collectBuiltinTypeHoverInfoAtPos | TUInt64 r -> createBuiltinTypeInfoIfInRange ctx r "UInt64" position | TInt128 r -> createBuiltinTypeInfoIfInRange ctx r "Int128" position | TUInt128 r -> createBuiltinTypeInfoIfInRange ctx r "UInt128" position + | TInt r -> createBuiltinTypeInfoIfInRange ctx r "Int" position | TFloat r -> createBuiltinTypeInfoIfInRange ctx r "Float" position | TChar r -> createBuiltinTypeInfoIfInRange ctx r "Char" position | TString r -> createBuiltinTypeInfoIfInRange ctx r "String" position @@ -932,6 +933,7 @@ let collectMatchPatternHoverInfoAtPos | MPUInt64(r, _, _) | MPInt128(r, _, _) | MPUInt128(r, _, _) + | MPInt(r, _) | MPFloat(r, _, _, _) | MPChar(r, _, _, _) | MPString(r, _, _, _) -> Stdlib.Option.Option.None diff --git a/packages/darklang/languageTools/parser/expr.dark b/packages/darklang/languageTools/parser/expr.dark index 837e0af067..09a6ad0806 100644 --- a/packages/darklang/languageTools/parser/expr.dark +++ b/packages/darklang/languageTools/parser/expr.dark @@ -70,6 +70,16 @@ let parseIntLiteral | Error _ -> createUnparseableError node | _ -> createUnparseableError node + // The default `Int` - a suffix-less arbitrary-precision integer literal. + | Ok intPart, Error _ -> + match Stdlib.Int.parse (getText intPart) with + | Ok parsedValue -> + (WrittenTypes.Expr.EInt(node.range, (intPart.range, parsedValue))) + |> Stdlib.Result.Result.Ok + | Error _ -> createUnparseableError node + + | _ -> createUnparseableError node + let parseFloatLiteral (node: ParsedNode) @@ -754,6 +764,9 @@ let parseMatchCase rhs = rhs }) |> Stdlib.Result.Result.Ok + // Propagate a specific pattern error (e.g. the qualified-enum-case note) + // rather than masking it with a generic failure. + | _, Error e, _, _ -> Stdlib.Result.Result.Error e | _ -> createUnparseableError node @@ -911,7 +924,8 @@ let parseCase | "int64_literal" | "uint64_literal" | "int128_literal" - | "uint128_literal" -> parseIntLiteral node + | "uint128_literal" + | "bigint_literal" -> parseIntLiteral node | "float_literal" -> parseFloatLiteral node | "string_segment" -> parseStringLiteral node | "char_literal" -> parseCharLiteral node diff --git a/packages/darklang/languageTools/parser/matchPattern.dark b/packages/darklang/languageTools/parser/matchPattern.dark index 47a9011414..78cbd28ee9 100644 --- a/packages/darklang/languageTools/parser/matchPattern.dark +++ b/packages/darklang/languageTools/parser/matchPattern.dark @@ -71,6 +71,16 @@ let parseMPInt | Error _ -> createUnparseableError node | _ -> createUnparseableError node + // The default `Int` — a suffix-less arbitrary-precision integer pattern. + | Ok intPart, Error _ -> + match Stdlib.Int.parse (getText intPart) with + | Ok parsedValue -> + (WrittenTypes.MatchPattern.MPInt(node.range, (intPart.range, parsedValue))) + |> Stdlib.Result.Result.Ok + | Error _ -> createUnparseableError node + + | _ -> createUnparseableError node + let parseMPFloat @@ -228,31 +238,46 @@ let parseMPEnum : Stdlib.Result.Result = let caseNameNode = findField node "case_name" - let enumFieldsNode = - (findNodeByFieldName node "enum_fields") - |> Stdlib.Option.map (fun enumFieldsNode -> - match enumFieldsNode.children |> Stdlib.List.chunkBySize 2L with - | Ok pairs -> - pairs - |> Stdlib.List.map (fun contentSymbolPair -> - match contentSymbolPair with - | [ contentNode; symbol ] | [ contentNode ] -> parseMatchPattern contentNode - | _ -> createUnparseableError enumFieldsNode) - |> Stdlib.Result.collect - | Error _ -> createUnparseableError enumFieldsNode) - - |> Stdlib.Option.withDefault ([] |> Stdlib.Result.Result.Ok) - - match (caseNameNode, enumFieldsNode) with - | Ok caseNameNode, Ok enumFieldsNode -> - (WrittenTypes.MatchPattern.MPEnum( - node.range, - (caseNameNode.range, caseNameNode.text), - enumFieldsNode - )) - |> Stdlib.Result.Result.Ok + // A qualified enum-case pattern like `| Stdlib.Result.Result.Ok n ->` is + // unsupported (use the unqualified `| Ok n`). tree-sitter recovers by keeping + // the first path segment as the case name and dropping the remaining dotted + // path into an ERROR child of this enum node. Reject it rather than silently + // building a case from the surviving fragment. + let errorChild = + node.children |> Stdlib.List.findFirst (fun c -> c.typ == "ERROR") - | _ -> createUnparseableError node + match errorChild with + | Some err when Stdlib.String.contains err.text "." -> + createUnparseableErrorMsg + node + "Invalid match pattern. Enum patterns use the unqualified case name (e.g. `| Ok n`), not a qualified path like `| Stdlib.Result.Result.Ok n`." + | Some _ -> createUnparseableErrorMsg node "Invalid match pattern." + | None -> + let enumFieldsNode = + (findNodeByFieldName node "enum_fields") + |> Stdlib.Option.map (fun enumFieldsNode -> + match enumFieldsNode.children |> Stdlib.List.chunkBySize 2L with + | Ok pairs -> + pairs + |> Stdlib.List.map (fun contentSymbolPair -> + match contentSymbolPair with + | [ contentNode; symbol ] | [ contentNode ] -> parseMatchPattern contentNode + | _ -> createUnparseableError enumFieldsNode) + |> Stdlib.Result.collect + | Error _ -> createUnparseableError enumFieldsNode) + + |> Stdlib.Option.withDefault ([] |> Stdlib.Result.Result.Ok) + + match (caseNameNode, enumFieldsNode) with + | Ok caseNameNode, Ok enumFieldsNode -> + (WrittenTypes.MatchPattern.MPEnum( + node.range, + (caseNameNode.range, caseNameNode.text), + enumFieldsNode + )) + |> Stdlib.Result.Result.Ok + + | _ -> createUnparseableError node let parseMPOr @@ -307,7 +332,8 @@ let parseMatchPattern | "int64" | "uint64" | "int128" - | "uint128" -> parseMPInt child + | "uint128" + | "int" -> parseMPInt child | "float" -> parseMPFloat child diff --git a/packages/darklang/languageTools/parser/typeReference.dark b/packages/darklang/languageTools/parser/typeReference.dark index a2df05af53..12d45f32a1 100644 --- a/packages/darklang/languageTools/parser/typeReference.dark +++ b/packages/darklang/languageTools/parser/typeReference.dark @@ -59,6 +59,7 @@ let parseBuiltIn | "UInt64" -> (TBuiltin.TUInt64 node.range) |> builtin | "Int128" -> (TBuiltin.TInt128 node.range) |> builtin | "UInt128" -> (TBuiltin.TUInt128 node.range) |> builtin + | "Int" -> (TBuiltin.TInt node.range) |> builtin | "Float" -> (TBuiltin.TFloat node.range) |> builtin | "Char" -> (TBuiltin.TChar node.range) |> builtin | "String" -> (TBuiltin.TString node.range) |> builtin diff --git a/packages/darklang/languageTools/programTypes.dark b/packages/darklang/languageTools/programTypes.dark index 3c278e58a5..38f6983956 100644 --- a/packages/darklang/languageTools/programTypes.dark +++ b/packages/darklang/languageTools/programTypes.dark @@ -72,6 +72,7 @@ type TypeReference = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString @@ -122,6 +123,7 @@ type MatchPattern = | MPUInt64 of ID * UInt64 | MPInt128 of ID * Int128 | MPUInt128 of ID * UInt128 + | MPInt of ID * Int | MPFloat of ID * Sign * String * String | MPChar of ID * String | MPString of ID * String @@ -210,6 +212,7 @@ type Expr = | EUInt64 of ID * UInt64 | EInt128 of ID * Int128 | EUInt128 of ID * UInt128 + | EInt of ID * Int // Allow the user to have arbitrarily big numbers, even if they don't make sense as // floats. The float is split as we want to preserve what the user entered. @@ -294,6 +297,7 @@ module Expr = | EUInt64(id, _) | EInt128(id, _) | EUInt128(id, _) + | EInt(id, _) | EFloat(id, _, _, _) | EChar(id, _) | EString(id, _) diff --git a/packages/darklang/languageTools/runtimeTypes.dark b/packages/darklang/languageTools/runtimeTypes.dark index ac61c97c16..887f32d1ca 100644 --- a/packages/darklang/languageTools/runtimeTypes.dark +++ b/packages/darklang/languageTools/runtimeTypes.dark @@ -53,6 +53,7 @@ type TypeReference = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString @@ -86,6 +87,7 @@ type KnownType = | KTUInt64 | KTInt128 | KTUInt128 + | KTInt | KTFloat | KTChar | KTString @@ -130,6 +132,7 @@ type MatchPattern = | MPUInt64 of UInt64 | MPInt128 of Int128 | MPUInt128 of UInt128 + | MPInt of Int | MPFloat of Float | MPChar of String | MPString of String @@ -177,6 +180,7 @@ type Dval = | DUInt64 of UInt64 | DInt128 of Int128 | DUInt128 of UInt128 + | DInt of Int | DFloat of Float | DChar of String | DString of String diff --git a/packages/darklang/languageTools/semanticTokens.dark b/packages/darklang/languageTools/semanticTokens.dark index 38ca8f493f..7d4895947c 100644 --- a/packages/darklang/languageTools/semanticTokens.dark +++ b/packages/darklang/languageTools/semanticTokens.dark @@ -144,6 +144,7 @@ module TypeReference = | TUInt64 r | TInt128 r | TUInt128 r + | TInt r | TFloat r | TChar r | TString r @@ -414,6 +415,8 @@ module MatchPattern = | MPUInt128(_, (intPartRange, _), suffixPartRange) -> [ makeToken intPartRange TokenType.Number makeToken suffixPartRange TokenType.Symbol ] + // suffix-less Int + | MPInt(_, (intPartRange, _)) -> [ makeToken intPartRange TokenType.Number ] | MPFloat(range, _sign, _whole, _fraction) -> [ makeToken range TokenType.Number ] | MPBool(range, _b) -> [ makeToken range TokenType.Keyword ] @@ -647,6 +650,9 @@ module Expr = [ makeToken intPartRange TokenType.Number makeToken suffixPartRange TokenType.Symbol ] + // suffix-less Int + | EInt(_, (intPartRange, _)) -> [ makeToken intPartRange TokenType.Number ] + // 12.0 | EFloat(range, _sign, _whole, _fraction) -> [ makeToken range TokenType.Number ] diff --git a/packages/darklang/languageTools/writtenTypes.dark b/packages/darklang/languageTools/writtenTypes.dark index 2948ad25cb..c5628b79f3 100644 --- a/packages/darklang/languageTools/writtenTypes.dark +++ b/packages/darklang/languageTools/writtenTypes.dark @@ -65,6 +65,7 @@ module TypeReference = | TUInt64 of Range | TInt128 of Range | TUInt128 of Range + | TInt of Range | TFloat of Range | TChar of Range | TString of Range @@ -156,6 +157,7 @@ type MatchPattern = | MPUInt64 of Range * intPart: (Range * UInt64) * suffixPart: Range | MPInt128 of Range * intPart: (Range * Int128) * suffixPart: Range | MPUInt128 of Range * intPart: (Range * UInt128) * suffixPart: Range + | MPInt of Range * intPart: (Range * Int) | MPFloat of Range * Sign * String * String | MPChar of Range * @@ -256,6 +258,7 @@ type Expr = | EUInt64 of Range * intPart: (Range * UInt64) * suffixPart: Range | EInt128 of Range * intPart: (Range * Int128) * suffixPart: Range | EUInt128 of Range * intPart: (Range * UInt128) * suffixPart: Range + | EInt of Range * intPart: (Range * Int) | EFloat of Range * Sign * String * String | EString of Range * diff --git a/packages/darklang/languageTools/writtenTypesToProgramTypes.dark b/packages/darklang/languageTools/writtenTypesToProgramTypes.dark index efc02b90c2..06ed11aabf 100644 --- a/packages/darklang/languageTools/writtenTypesToProgramTypes.dark +++ b/packages/darklang/languageTools/writtenTypesToProgramTypes.dark @@ -175,6 +175,7 @@ module TypeReference = | TUInt64 _range -> (ProgramTypes.TypeReference.TUInt64, []) | TInt128 _range -> (ProgramTypes.TypeReference.TInt128, []) | TUInt128 _range -> (ProgramTypes.TypeReference.TUInt128, []) + | TInt _range -> (ProgramTypes.TypeReference.TInt, []) | TFloat _range -> (ProgramTypes.TypeReference.TFloat, []) | TChar _range -> (ProgramTypes.TypeReference.TChar, []) | TString _range -> (ProgramTypes.TypeReference.TString, []) @@ -386,6 +387,7 @@ module Expr = | MPInt128(_, (_, i), _) -> ProgramTypes.MatchPattern.MPInt128(gid (), i) | MPUInt128(_, (_, i), _) -> ProgramTypes.MatchPattern.MPUInt128(gid (), i) + | MPInt(_, (_, i)) -> ProgramTypes.MatchPattern.MPInt(gid (), i) | MPFloat(_, s, w, f) -> ProgramTypes.MatchPattern.MPFloat(gid (), s, w, f) | MPString(_, contents, _, _) -> @@ -576,6 +578,7 @@ module Expr = | EUInt64(_, (_, i), _) -> (ProgramTypes.Expr.EUInt64(gid (), i), []) | EInt128(_, (_, i), _) -> (ProgramTypes.Expr.EInt128(gid (), i), []) | EUInt128(_, (_, i), _) -> (ProgramTypes.Expr.EUInt128(gid (), i), []) + | EInt(_, (_, i)) -> (ProgramTypes.Expr.EInt(gid (), i), []) | EFloat(_, s, w, f) -> (ProgramTypes.Expr.EFloat(gid (), s, w, f), []) | EString(_, _, contents, _, _) -> let (segment, unresolvedNames) = @@ -1408,6 +1411,7 @@ module FunctionDeclaration = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString diff --git a/packages/darklang/prettyPrinter/programTypes.dark b/packages/darklang/prettyPrinter/programTypes.dark index 1106c84688..45acf58a81 100644 --- a/packages/darklang/prettyPrinter/programTypes.dark +++ b/packages/darklang/prettyPrinter/programTypes.dark @@ -327,6 +327,7 @@ let typeReference | TUInt64 -> "UInt64" | TInt128 -> "Int128" | TUInt128 -> "UInt128" + | TInt -> "Int" | TFloat -> "Float" | TChar -> "Char" | TString -> "String" @@ -406,6 +407,7 @@ let matchPattern (mp: LanguageTools.ProgramTypes.MatchPattern) : String = | MPUInt64(_id, i) -> (Stdlib.UInt64.toString i) ++ "UL" | MPInt128(_id, i) -> (Stdlib.Int128.toString i) ++ "Q" | MPUInt128(_id, i) -> (Stdlib.UInt128.toString i) ++ "Z" + | MPInt(_id, i) -> Stdlib.Int.toString i | MPFloat(_id, sign, whole, remainder) -> let remainderPart = PrettyPrinter.processRemainder remainder @@ -560,6 +562,7 @@ let expr (ctx: Context) (e: LanguageTools.ProgramTypes.Expr) : String = | EUInt64(_id, i) -> (Stdlib.UInt64.toString i) ++ "UL" | EInt128(_id, i) -> Stdlib.Int128.toString i ++ "Q" | EUInt128(_id, i) -> Stdlib.UInt128.toString i ++ "Z" + | EInt(_id, i) -> Stdlib.Int.toString i | EFloat(_id, sign, whole, remainder) -> let signPart = PrettyPrinter.sign sign diff --git a/packages/darklang/prettyPrinter/runtimeError.dark b/packages/darklang/prettyPrinter/runtimeError.dark index a749411732..ed9928064b 100644 --- a/packages/darklang/prettyPrinter/runtimeError.dark +++ b/packages/darklang/prettyPrinter/runtimeError.dark @@ -138,6 +138,7 @@ module RuntimeError = | Known KTUInt64 -> ". Try wrapping it with `Stdlib.UInt64.toString`." | Known KTInt128 -> ". Try wrapping it with `Stdlib.Int128.toString`." | Known KTUInt128 -> ". Try wrapping it with `Stdlib.UInt128.toString`." + | Known KTInt -> ". Try wrapping it with `Stdlib.Int.toString`." | Known KTFloat -> ". Try wrapping it with `Stdlib.Float.toString`." | Known KTBool -> ". Try wrapping it with `Stdlib.Bool.toString`." | Known KTChar -> ". Try wrapping it with `Stdlib.Char.toString`." diff --git a/packages/darklang/prettyPrinter/runtimeTypes.dark b/packages/darklang/prettyPrinter/runtimeTypes.dark index d196ba8958..db34e550eb 100644 --- a/packages/darklang/prettyPrinter/runtimeTypes.dark +++ b/packages/darklang/prettyPrinter/runtimeTypes.dark @@ -150,6 +150,7 @@ let typeReference | TUInt64 -> "UInt64" | TInt128 -> "Int128" | TUInt128 -> "UInt128" + | TInt -> "Int" | TFloat -> "Float" @@ -229,6 +230,7 @@ let knownType | KTUInt64 -> "UInt64" | KTInt128 -> "Int128" | KTUInt128 -> "UInt128" + | KTInt -> "Int" | KTFloat -> "Float" @@ -314,6 +316,7 @@ module Dval = | DUInt64 _ -> "UInt64" | DInt128 _ -> "Int128" | DUInt128 _ -> "UInt128" + | DInt _ -> "Int" | DFloat _ -> "Float" | DChar _ -> "Char" | DString _ -> "String" @@ -391,6 +394,7 @@ module Dval = | DUInt64 i -> Stdlib.UInt64.toString i | DInt128 i -> Stdlib.Int128.toString i | DUInt128 i -> Stdlib.UInt128.toString i + | DInt i -> Stdlib.Int.toString i | DFloat f -> Stdlib.Float.toString f // CLEANUP: deal with Infinity, NegativeInfinity, and NaN diff --git a/packages/darklang/scm/unresolvedCheck.dark b/packages/darklang/scm/unresolvedCheck.dark index 573cca5743..72eb01335b 100644 --- a/packages/darklang/scm/unresolvedCheck.dark +++ b/packages/darklang/scm/unresolvedCheck.dark @@ -47,6 +47,7 @@ let checkTypeRef (tr: LanguageTools.ProgramTypes.TypeReference) : List = | TUInt64 | TInt128 | TUInt128 + | TInt | TFloat | TChar | TString @@ -165,6 +166,7 @@ let checkExpr (expr: LanguageTools.ProgramTypes.Expr) : List = | EUInt64 (_id, _v) | EInt128 (_id, _v) | EUInt128 (_id, _v) + | EInt (_id, _v) | EChar (_id, _v) | EVariable (_id, _v) | EArg (_id, _idx) -> [] diff --git a/packages/darklang/stdlib/int.dark b/packages/darklang/stdlib/int.dark new file mode 100644 index 0000000000..34b39d90e5 --- /dev/null +++ b/packages/darklang/stdlib/int.dark @@ -0,0 +1,185 @@ +module Darklang.Stdlib.Int + +// `Int` is the default, arbitrary-precision integer (a bare literal like `1`). +// The polymorphic operators (`+`, `-`, `<`, ...) work on it directly; this +// module provides the same operations as named functions, plus parse/stringify +// and conversions to/from the fixed-width integer types. +// +// Note: because this file is parsed by the F# host parser, every `Int` literal +// below needs the `I` suffix (a bare `1` would be an `Int32` here). + + +/// Returns the result of wrapping around so that {{0 <= res < b}}. +/// The modulus must be greater than 0. +/// Use if you want the remainder after division, which has +/// a different behavior for negative numbers. +let ``mod`` (a: Int) (b: Int) : Int = Builtin.intMod a b + + +/// Returns the integer remainder left over after dividing by +/// , as a . +/// For example, {{Int.remainder 15 6 == Ok 3}}. The remainder will be +/// negative only if {{ < 0}}. +/// The sign of doesn't influence the outcome. +/// Returns an {{Error}} if is {{0}}. +let remainder (value: Int) (divisor: Int) : Stdlib.Result.Result = + Builtin.intRemainder value divisor + + +/// Adds two integers together +let add (a: Int) (b: Int) : Int = Builtin.intAdd a b + + +/// Subtracts two integers +let subtract (a: Int) (b: Int) : Int = Builtin.intSubtract a b + + +/// Multiplies two integers +let multiply (a: Int) (b: Int) : Int = Builtin.intMultiply a b + + +/// Divides two integers, rounding towards zero +let divide (a: Int) (b: Int) : Int = Builtin.intDivide a b + + +/// Raise to the power of . +/// must be non-negative. +let power (``base``: Int) (exponent: Int) : Int = + Builtin.intPower ``base`` exponent + + +/// Returns the absolute value of (turning negative inputs into positive outputs) +let absoluteValue (a: Int) : Int = if lessThan a 0I then negate a else a + + +/// Returns the negation of , {{-a}} +let negate (a: Int) : Int = Builtin.intNegate a + + +/// Returns {{true}} if is greater than +let greaterThan (a: Int) (b: Int) : Bool = Builtin.intGreaterThan a b + + +/// Returns {{true}} if is greater than or equal to +let greaterThanOrEqualTo (a: Int) (b: Int) : Bool = + Builtin.intGreaterThanOrEqualTo a b + + +/// Returns {{true}} if is less than +let lessThan (a: Int) (b: Int) : Bool = Builtin.intLessThan a b + + +/// Returns {{true}} if is less than or equal to +let lessThanOrEqualTo (a: Int) (b: Int) : Bool = Builtin.intLessThanOrEqualTo a b + + +/// Get the square root of an +let sqrt (a: Int) : Float = Builtin.intSqrt a + + +/// Converts an to a +let toFloat (a: Int) : Float = Builtin.intToFloat a + + +/// Returns the sum of all the ints in the list +let sum (lst: List) : Int = + Stdlib.List.fold lst 0I (fun acc x -> add acc x) + + +/// Returns the higher of and +let max (a: Int) (b: Int) : Int = if greaterThan a b then a else b + + +/// Returns the lower of and +let min (a: Int) (b: Int) : Int = if lessThan a b then a else b + + +/// If is within the range given by and , returns . +/// If is outside the range, returns or , whichever is closer to . +/// and can be provided in any order. +let clamp (value: Int) (limitA: Int) (limitB: Int) : Int = + let min = if lessThan limitA limitB then limitA else limitB + let max = if greaterThan limitA limitB then limitA else limitB + + if lessThan value min then min + else if greaterThan value max then max + else value + + +/// Returns the value of a . Because is +/// arbitrary-precision, the only failure is a badly-formatted string. +let parse (s: String) : Stdlib.Result.Result = Builtin.intParse s + + +/// Stringify +let toString (int: Int) : String = Builtin.intToString int + + +// Conversions from the fixed-width integer types into `Int` (always succeed). + +/// Converts an to an arbitrary-precision +let fromInt8 (a: Int8) : Int = Builtin.intFromInt8 a + +/// Converts a to an arbitrary-precision +let fromUInt8 (a: UInt8) : Int = Builtin.intFromUInt8 a + +/// Converts an to an arbitrary-precision +let fromInt16 (a: Int16) : Int = Builtin.intFromInt16 a + +/// Converts a to an arbitrary-precision +let fromUInt16 (a: UInt16) : Int = Builtin.intFromUInt16 a + +/// Converts an to an arbitrary-precision +let fromInt32 (a: Int32) : Int = Builtin.intFromInt32 a + +/// Converts a to an arbitrary-precision +let fromUInt32 (a: UInt32) : Int = Builtin.intFromUInt32 a + +/// Converts an to an arbitrary-precision +let fromInt64 (a: Int64) : Int = Builtin.intFromInt64 a + +/// Converts a to an arbitrary-precision +let fromUInt64 (a: UInt64) : Int = Builtin.intFromUInt64 a + +/// Converts an to an arbitrary-precision +let fromInt128 (a: Int128) : Int = Builtin.intFromInt128 a + +/// Converts a to an arbitrary-precision +let fromUInt128 (a: UInt128) : Int = Builtin.intFromUInt128 a + + +// Conversions from `Int` into the fixed-width integer types, returning {{None}} +// if the value doesn't fit the target range. + +/// Converts an to an , returning {{None}} if it doesn't fit +let toInt8 (a: Int) : Stdlib.Option.Option = Builtin.intToInt8 a + +/// Converts an to a , returning {{None}} if it doesn't fit +let toUInt8 (a: Int) : Stdlib.Option.Option = Builtin.intToUInt8 a + +/// Converts an to an , returning {{None}} if it doesn't fit +let toInt16 (a: Int) : Stdlib.Option.Option = Builtin.intToInt16 a + +/// Converts an to a , returning {{None}} if it doesn't fit +let toUInt16 (a: Int) : Stdlib.Option.Option = Builtin.intToUInt16 a + +/// Converts an to an , returning {{None}} if it doesn't fit +let toInt32 (a: Int) : Stdlib.Option.Option = Builtin.intToInt32 a + +/// Converts an to a , returning {{None}} if it doesn't fit +let toUInt32 (a: Int) : Stdlib.Option.Option = Builtin.intToUInt32 a + +/// Converts an to an , returning {{None}} if it doesn't +/// fit the 64-bit range +let toInt64 (a: Int) : Stdlib.Option.Option = Builtin.intToInt64 a + +/// Converts an to a , returning {{None}} if it doesn't fit +let toUInt64 (a: Int) : Stdlib.Option.Option = Builtin.intToUInt64 a + +/// Converts an to an , returning {{None}} if it doesn't fit +let toInt128 (a: Int) : Stdlib.Option.Option = Builtin.intToInt128 a + +/// Converts an to a , returning {{None}} if it doesn't fit +let toUInt128 (a: Int) : Stdlib.Option.Option = Builtin.intToUInt128 a diff --git a/tree-sitter-darklang/grammar.js b/tree-sitter-darklang/grammar.js index e44cc96e55..221b57363f 100644 --- a/tree-sitter-darklang/grammar.js +++ b/tree-sitter-darklang/grammar.js @@ -264,6 +264,7 @@ module.exports = grammar({ alias($.uint64_literal, $.uint64), alias($.int128_literal, $.int128), alias($.uint128_literal, $.uint128), + alias($.bigint_literal, $.int), alias($.float_literal, $.float), alias($.char_literal, $.char), alias($.string_literal, $.string), @@ -396,6 +397,7 @@ module.exports = grammar({ $.uint64_literal, $.int128_literal, $.uint128_literal, + $.bigint_literal, $.float_literal, $.string_segment, $.char_literal, @@ -507,6 +509,9 @@ module.exports = grammar({ field("suffix", alias("Z", $.symbol)), ), + // The default, arbitrary-precision `Int` — a suffix-less integer literal. + bigint_literal: $ => field("digits", $.digits), + // // Floats float_literal: $ => /[+-]?[0-9]+\.[0-9]+/, @@ -1026,6 +1031,7 @@ module.exports = grammar({ /UInt64/, /Int128/, /UInt128/, + /Int/, /Float/, /Char/, /String/, diff --git a/tree-sitter-darklang/src/grammar.json b/tree-sitter-darklang/src/grammar.json index ed186ac617..d88c2be6b1 100644 --- a/tree-sitter-darklang/src/grammar.json +++ b/tree-sitter-darklang/src/grammar.json @@ -1175,6 +1175,15 @@ "named": true, "value": "uint128" }, + { + "type": "ALIAS", + "content": { + "type": "SYMBOL", + "name": "bigint_literal" + }, + "named": true, + "value": "int" + }, { "type": "ALIAS", "content": { @@ -1887,6 +1896,10 @@ "type": "SYMBOL", "name": "uint128_literal" }, + { + "type": "SYMBOL", + "name": "bigint_literal" + }, { "type": "SYMBOL", "name": "float_literal" @@ -2353,6 +2366,14 @@ } ] }, + "bigint_literal": { + "type": "FIELD", + "name": "digits", + "content": { + "type": "SYMBOL", + "name": "digits" + } + }, "float_literal": { "type": "PATTERN", "value": "[+-]?[0-9]+\\.[0-9]+" @@ -5784,6 +5805,10 @@ "type": "PATTERN", "value": "UInt128" }, + { + "type": "PATTERN", + "value": "Int" + }, { "type": "PATTERN", "value": "Float" diff --git a/tree-sitter-darklang/src/node-types.json b/tree-sitter-darklang/src/node-types.json index c474f08dd0..7ebbba35ef 100644 --- a/tree-sitter-darklang/src/node-types.json +++ b/tree-sitter-darklang/src/node-types.json @@ -7,6 +7,10 @@ "multiple": true, "required": true, "types": [ + { + "type": "bigint_literal", + "named": true + }, { "type": "bool_literal", "named": true @@ -143,6 +147,22 @@ ] } }, + { + "type": "bigint_literal", + "named": true, + "fields": { + "digits": { + "multiple": false, + "required": true, + "types": [ + { + "type": "digits", + "named": true + } + ] + } + } + }, { "type": "bool", "named": true, @@ -1121,6 +1141,22 @@ } } }, + { + "type": "int", + "named": true, + "fields": { + "digits": { + "multiple": false, + "required": true, + "types": [ + { + "type": "digits", + "named": true + } + ] + } + } + }, { "type": "int128", "named": true, @@ -2058,6 +2094,10 @@ "type": "float", "named": true }, + { + "type": "int", + "named": true + }, { "type": "int128", "named": true @@ -3106,6 +3146,10 @@ "type": "apply", "named": true }, + { + "type": "bigint_literal", + "named": true + }, { "type": "bool_literal", "named": true diff --git a/tree-sitter-darklang/test/corpus/exhaustive/exprs/char.txt b/tree-sitter-darklang/test/corpus/exhaustive/exprs/char.txt index 68b58ee91d..5b72332ca8 100644 --- a/tree-sitter-darklang/test/corpus/exhaustive/exprs/char.txt +++ b/tree-sitter-darklang/test/corpus/exhaustive/exprs/char.txt @@ -124,13 +124,12 @@ char - invalid escape produces ERROR --- (source_file + (ERROR + (symbol) + (UNEXPECTED 'd')) (expression (simple_expression - (char_literal - (symbol) - (ERROR (UNEXPECTED 'd')) - (character) - (MISSING symbol))))) + (variable_identifier)))) ================== diff --git a/tree-sitter-darklang/test/corpus/exhaustive/exprs/ints.txt b/tree-sitter-darklang/test/corpus/exhaustive/exprs/ints.txt index 652624ee44..5fe168e29a 100644 --- a/tree-sitter-darklang/test/corpus/exhaustive/exprs/ints.txt +++ b/tree-sitter-darklang/test/corpus/exhaustive/exprs/ints.txt @@ -1,14 +1,34 @@ ================== -unsupported plain integer +bigint (suffixless) literal ================== 0 --- -(source_file - (ERROR (positive_digits)) -) +(source_file (expression (simple_expression (bigint_literal (digits (positive_digits)))))) + + +================== +bigint (suffixless) literal - large +================== + +12345678901234567890 + +--- + +(source_file (expression (simple_expression (bigint_literal (digits (positive_digits)))))) + + +================== +bigint (suffixless) literal - negative +================== + +-5 + +--- + +(source_file (expression (simple_expression (bigint_literal (digits (negative_digits)))))) ================== diff --git a/tree-sitter-darklang/test/corpus/exhaustive/exprs/let_expr.txt b/tree-sitter-darklang/test/corpus/exhaustive/exprs/let_expr.txt index c2b6557351..f26f9d0aac 100644 --- a/tree-sitter-darklang/test/corpus/exhaustive/exprs/let_expr.txt +++ b/tree-sitter-darklang/test/corpus/exhaustive/exprs/let_expr.txt @@ -630,30 +630,30 @@ nested function sugar on a non-variable pattern is invalid -------------------------------------------------------------------------------- (source_file - (expression + (ERROR + (symbol) + (keyword) + (let_pattern + (unit)) + (ERROR + (symbol) + (symbol) + (symbol) + (symbol)) + (symbol) (simple_expression - (paren_expression - (symbol) - (ERROR - (keyword) - (ERROR - (let_pattern - (unit))) - (symbol) - (let_pattern - (variable_identifier)) - (symbol) - (symbol) - (symbol) - (symbol) - (UNEXPECTED 'x')) + (apply + (qualified_fn_name + (fn_identifier)) + (indent) (expression (simple_expression (int64_literal (digits (positive_digits)) (symbol)))) - (symbol))))) + (dedent))) + (symbol))) ================================================================================ nested function definition - explicit type params