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