diff --git a/.gitattributes b/.gitattributes index da901ce..b9b08fd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,6 @@ # SCM syntax highlighting & preventing 3-way merges pixi.lock merge=binary linguist-language=YAML linguist-generated=true -magic.lock merge=binary linguist-language=YAML linguist-generated=true \ No newline at end of file +magic.lock merge=binary linguist-language=YAML linguist-generated=true + +# Force LF (Unix-style) line endings +* text=auto eol=lf \ No newline at end of file diff --git a/pixi.toml b/pixi.toml index 226330d..50ec81f 100644 --- a/pixi.toml +++ b/pixi.toml @@ -9,7 +9,7 @@ readme = "README.md" version = "0.5.0" [dependencies] -max = ">25.4" +mojo = "*" [tasks] # format the code @@ -41,4 +41,4 @@ buint_debug = "clear && pixi run package && cd benches/biguint && pixi run mojo bdec_debug = "clear && pixi run package && cd benches/bigdecimal && pixi run mojo run -I ../ -D ASSERT=all bench.mojo && cd ../.. && pixi run clean" # doc -doc = "clear && pixi run mojo doc -o docs/doc.json --diagnose-missing-doc-strings --validate-doc-strings src/decimojo" \ No newline at end of file +doc = "clear && pixi run mojo doc -o docs/doc.json --diagnose-missing-doc-strings --validate-doc-strings src/decimojo" diff --git a/src/decimojo/bigint/arithmetics.mojo b/src/decimojo/bigint/arithmetics.mojo index fa42408..04897e8 100644 --- a/src/decimojo/bigint/arithmetics.mojo +++ b/src/decimojo/bigint/arithmetics.mojo @@ -20,10 +20,11 @@ Implements basic arithmetic functions for the BigInt type. from decimojo.bigint.bigint import BigInt from decimojo.biguint.biguint import BigUInt +from decimojo.errors import DeciMojoError from decimojo.rounding_mode import RoundingMode -fn add(x1: BigInt, x2: BigInt) raises -> BigInt: +fn add(x1: BigInt, x2: BigInt) -> BigInt: """Returns the sum of two BigInts. Args: @@ -57,7 +58,7 @@ fn add(x1: BigInt, x2: BigInt) raises -> BigInt: return BigInt(magnitude^, sign=x1.sign) -fn add_inplace(mut x1: BigInt, x2: BigInt) raises -> None: +fn add_inplace(mut x1: BigInt, x2: BigInt) -> None: """Increments a BigInt number by another BigInt number in place. Args: @@ -67,15 +68,17 @@ fn add_inplace(mut x1: BigInt, x2: BigInt) raises -> None: # If signs are different, delegate to `subtract` if x1.sign != x2.sign: - x1 = subtract(x1, -x2) + x1 = subtract(x1, negative(x2)) return # Same sign: add magnitudes in place else: - x1.magnitude += x2.magnitude + decimojo.biguint.arithmetics.add_inplace(x1.magnitude, x2.magnitude) + return -fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt: + +fn subtract(x1: BigInt, x2: BigInt) -> BigInt: """Returns the difference of two numbers. Args: @@ -102,7 +105,7 @@ fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt: # If signs are different, delegate to `add` if x1.sign != x2.sign: - return x1 + (-x2) + return add(x1, negative(x2)) # Same sign, compare magnitudes to determine result sign and operation var comparison_result = x1.magnitude.compare(x2.magnitude) @@ -114,12 +117,18 @@ fn subtract(x1: BigInt, x2: BigInt) raises -> BigInt: var sign: Bool if comparison_result > 0: # |x1| > |x2| # Subtract smaller from larger - magnitude = x1.magnitude - x2.magnitude + magnitude = x1.magnitude + decimojo.biguint.arithmetics.subtract_inplace_no_check( + magnitude, x2.magnitude + ) sign = x1.sign else: # |x1| < |x2| # Subtract larger from smaller and negate the result - magnitude = x2.magnitude - x1.magnitude + magnitude = x2.magnitude + decimojo.biguint.arithmetics.subtract_inplace_no_check( + magnitude, x1.magnitude + ) sign = not x1.sign return BigInt(magnitude^, sign=sign) @@ -165,7 +174,7 @@ fn absolute(x: BigInt) -> BigInt: return x -fn multiply(x1: BigInt, x2: BigInt) raises -> BigInt: +fn multiply(x1: BigInt, x2: BigInt) -> BigInt: """Returns the product of two BigInt numbers. Args: @@ -197,25 +206,51 @@ fn floor_divide(x1: BigInt, x2: BigInt) raises -> BigInt: Returns: The quotient of x1 / x2, rounded toward negative infinity. - """ - if x2.is_zero(): - raise Error("Error in `floor_divide`: Division by zero") - - if x1.is_zero(): - return BigInt() + Raises: + DeciMojoError: If `decimojo.biguint.arithmetics.floor_divide()` fails. + DeciMojoError: If `decimojo.biguint.arithmetics.ceil_divide()` fails. + """ # For floor division, the sign rules are: # (1) Same signs: result is positive, use `floor_divide` on magnitudes # (1) Different signs: result is negative, use `ceil_divide` on magnitudes + var magnitude: BigUInt + if x1.sign == x2.sign: - # Use floor (truncate) division between magnitudes - return BigInt(x1.magnitude.floor_divide(x2.magnitude), sign=False) + # Use floor division of the magnitudes + try: + magnitude = decimojo.biguint.arithmetics.floor_divide( + x1.magnitude, x2.magnitude + ) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/bigint/arithmetics", + function="floor_divide()", + message=None, + previous_error=e, + ), + ) + return BigInt(magnitude^, sign=False) else: # Use ceil division of the magnitudes - return BigInt(x1.magnitude.ceil_divide(x2.magnitude), sign=True) + try: + magnitude = decimojo.biguint.arithmetics.ceil_divide( + x1.magnitude, x2.magnitude + ) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/bigint/arithmetics", + function="floor_divide()", + message=None, + previous_error=e, + ), + ) + return BigInt(magnitude^, sign=True) fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: @@ -231,15 +266,22 @@ fn truncate_divide(x1: BigInt, x2: BigInt) raises -> BigInt: The quotient of x1 / x2, truncated toward zero. Raises: - ValueError: If the divisor is zero. + DeciMojoError: If `decimojo.biguint.arithmetics.floor_divide()` fails. """ - if x2.is_zero(): - raise Error("Error in `truncate_divide`: Division by zero") - - if x1.is_zero(): - return BigInt() # Return zero - - var magnitude = x1.magnitude.floor_divide(x2.magnitude) + var magnitude: BigUInt + try: + magnitude = decimojo.biguint.arithmetics.floor_divide( + x1.magnitude, x2.magnitude + ) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/bigint/arithmetics", + function="truncate_divide()", + message=None, + previous_error=e, + ), + ) return BigInt(magnitude^, sign=x1.sign != x2.sign) @@ -254,21 +296,47 @@ fn floor_modulo(x1: BigInt, x2: BigInt) raises -> BigInt: Returns: The remainder of x1 being divided by x2, with the same sign as x2. - """ - if x2.is_zero(): - raise Error("Error in `floor_modulo`: Division by zero") + Raises: + DeciMojoError: If `decimojo.biguint.arithmetics.floor_modulo()` fails. + DeciMojoError: If `decimojo.biguint.arithmetics.ceil_modulo()` fails. + """ - if x1.is_zero(): - return BigInt() # Return zero + var magnitude: BigUInt if x1.sign == x2.sign: # Use floor (truncate) division between magnitudes - return BigInt(x1.magnitude.floor_modulo(x2.magnitude), sign=x2.sign) + try: + magnitude = decimojo.biguint.arithmetics.floor_modulo( + x1.magnitude, x2.magnitude + ) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/bigint/arithmetics", + function="floor_modulo()", + message=None, + previous_error=e, + ), + ) + return BigInt(magnitude^, sign=x2.sign) else: # Use ceil division of the magnitudes - return BigInt(x1.magnitude.ceil_modulo(x2.magnitude), sign=x2.sign) + try: + magnitude = decimojo.biguint.arithmetics.ceil_modulo( + x1.magnitude, x2.magnitude + ) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/bigint/arithmetics", + function="floor_modulo()", + message=None, + previous_error=e, + ), + ) + return BigInt(magnitude^, sign=x2.sign) fn truncate_modulo(x1: BigInt, x2: BigInt) raises -> BigInt: @@ -284,13 +352,20 @@ fn truncate_modulo(x1: BigInt, x2: BigInt) raises -> BigInt: The remainder of x1 being divided by x2, with the same sign as x1. Raises: - ValueError: If the divisor is zero. + DeciMojoError: If `decimojo.biguint.arithmetics.floor_modulo()` fails. """ - if x2.is_zero(): - raise Error("Error in `truncate_modulo`: Division by zero") - - if x1.is_zero(): - return BigInt() # Return zero - - var magnitude = x1.magnitude.floor_modulo(x2.magnitude) + var magnitude: BigUInt + try: + magnitude = decimojo.biguint.arithmetics.floor_modulo( + x1.magnitude, x2.magnitude + ) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/bigint/arithmetics", + function="truncate_modulo()", + message=None, + previous_error=e, + ), + ) return BigInt(magnitude^, sign=x1.sign) diff --git a/src/decimojo/bigint/bigint.mojo b/src/decimojo/bigint/bigint.mojo index de382a2..804baef 100644 --- a/src/decimojo/bigint/bigint.mojo +++ b/src/decimojo/bigint/bigint.mojo @@ -29,6 +29,7 @@ import decimojo.bigint.arithmetics import decimojo.bigint.comparison from decimojo.bigdecimal.bigdecimal import BigDecimal from decimojo.biguint.biguint import BigUInt +from decimojo.errors import DeciMojoError import decimojo.str # Type aliases @@ -463,24 +464,44 @@ struct BigInt( # ===------------------------------------------------------------------=== # @always_inline - fn __add__(self, other: Self) raises -> Self: + fn __add__(self, other: Self) -> Self: return decimojo.bigint.arithmetics.add(self, other) @always_inline - fn __sub__(self, other: Self) raises -> Self: + fn __sub__(self, other: Self) -> Self: return decimojo.bigint.arithmetics.subtract(self, other) @always_inline - fn __mul__(self, other: Self) raises -> Self: + fn __mul__(self, other: Self) -> Self: return decimojo.bigint.arithmetics.multiply(self, other) @always_inline fn __floordiv__(self, other: Self) raises -> Self: - return decimojo.bigint.arithmetics.floor_divide(self, other) + try: + return decimojo.bigint.arithmetics.floor_divide(self, other) + except e: + raise Error( + DeciMojoError( + message=None, + function="BigInt.__floordiv__()", + file="src/decimojo/bigint/bigint.mojo", + previous_error=e, + ) + ) @always_inline fn __mod__(self, other: Self) raises -> Self: - return decimojo.bigint.arithmetics.floor_modulo(self, other) + try: + return decimojo.bigint.arithmetics.floor_modulo(self, other) + except e: + raise Error( + DeciMojoError( + message=None, + function="BigInt.__mod__()", + file="src/decimojo/bigint/bigint.mojo", + previous_error=e, + ) + ) @always_inline fn __pow__(self, exponent: Self) raises -> Self: @@ -493,15 +514,15 @@ struct BigInt( # ===------------------------------------------------------------------=== # @always_inline - fn __radd__(self, other: Self) raises -> Self: + fn __radd__(self, other: Self) -> Self: return decimojo.bigint.arithmetics.add(self, other) @always_inline - fn __rsub__(self, other: Self) raises -> Self: + fn __rsub__(self, other: Self) -> Self: return decimojo.bigint.arithmetics.subtract(other, self) @always_inline - fn __rmul__(self, other: Self) raises -> Self: + fn __rmul__(self, other: Self) -> Self: return decimojo.bigint.arithmetics.multiply(self, other) @always_inline @@ -524,11 +545,11 @@ struct BigInt( # ===------------------------------------------------------------------=== # @always_inline - fn __iadd__(mut self, other: Self) raises: + fn __iadd__(mut self, other: Self): decimojo.bigint.arithmetics.add_inplace(self, other) @always_inline - fn __iadd__(mut self, other: Int) raises: + fn __iadd__(mut self, other: Int): # Optimize the case `i += 1` if (self >= 0) and (other >= 0) and (other <= 999_999_999): decimojo.biguint.arithmetics.add_inplace_by_uint32( @@ -538,11 +559,11 @@ struct BigInt( decimojo.bigint.arithmetics.add_inplace(self, other) @always_inline - fn __isub__(mut self, other: Self) raises: + fn __isub__(mut self, other: Self): self = decimojo.bigint.arithmetics.subtract(self, other) @always_inline - fn __imul__(mut self, other: Self) raises: + fn __imul__(mut self, other: Self): self = decimojo.bigint.arithmetics.multiply(self, other) @always_inline diff --git a/src/decimojo/biguint/arithmetics.mojo b/src/decimojo/biguint/arithmetics.mojo index d41caca..c87eb83 100644 --- a/src/decimojo/biguint/arithmetics.mojo +++ b/src/decimojo/biguint/arithmetics.mojo @@ -24,6 +24,7 @@ from memory import memcpy, memset_zero from decimojo.biguint.biguint import BigUInt import decimojo.biguint.comparison +from decimojo.errors import DeciMojoError, OverflowError, ZeroDivisionError from decimojo.rounding_mode import RoundingMode alias CUTOFF_KARATSUBA = 64 @@ -98,8 +99,12 @@ fn negative(x: BigUInt) raises -> BigUInt: len(x.words) == 1, "negative(): leading zero words" ) raise Error( - "biguint.arithmetics.negative(): Negative of non-zero unsigned" - " integer is undefined" + OverflowError( + file="src/decimojo/biguint/arithmetics.mojo", + function="negative()", + message="Negative of non-zero unsigned integer is undefined", + previous_error=None, + ) ) return BigUInt() # Return zero @@ -480,7 +485,7 @@ fn subtract_school(x: BigUInt, y: BigUInt) raises -> BigUInt: y: The second unsigned integer (subtrahend). Raises: - Error: If y is greater than x, resulting in an underflow. + OverflowError: If y is greater than x. Returns: The result of subtracting y from x. @@ -505,7 +510,17 @@ fn subtract_school(x: BigUInt, y: BigUInt) raises -> BigUInt: # |x| = |y| return BigUInt() # Return zero if comparison_result < 0: - raise Error("biguint.arithmetics.subtract(): Underflow due to x < y") + raise Error( + OverflowError( + file="src/decimojo/biguint/arithmetics.mojo", + function="subtract_school()", + message=( + "biguint.arithmetics.subtract(): Result is negative due to" + " x < y" + ), + previous_error=None, + ) + ) # Now it is safe to subtract the smaller number from the larger one # The result will have no more words than the first number @@ -554,7 +569,7 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: y: The second unsigned integer (subtrahend). Raises: - Error: If y is greater than x, resulting in an underflow. + OverflowError: If y is greater than x. Returns: The result of subtracting y from x. @@ -592,7 +607,17 @@ fn subtract_simd(x: BigUInt, y: BigUInt) raises -> BigUInt: # |x| = |y| return BigUInt() # Return zero if comparison_result < 0: - raise Error("biguint.arithmetics.subtract(): Underflow due to x < y") + raise Error( + OverflowError( + file="src/decimojo/biguint/arithmetics.mojo", + function="subtract()", + message=( + "biguint.arithmetics.subtract(): Result is negative due to" + " x < y" + ), + previous_error=None, + ) + ) # Now it is safe to subtract the smaller number from the larger one # The result will have no more words than the first number @@ -648,7 +673,15 @@ fn subtract_inplace(mut x: BigUInt, y: BigUInt) raises -> None: x.words[0] = UInt32(0) # Result is zero elif comparison_result < 0: raise Error( - "biguint.arithmetics.subtract_inplace(): Underflow due to x < y" + OverflowError( + file="src/decimojo/biguint/arithmetics.mojo", + function="subtract_inplace()", + message=( + "biguint.arithmetics.subtract(): Result is negative due to" + " x < y" + ), + previous_error=None, + ) ) # Now it is safe to subtract the smaller number from the larger one @@ -1479,7 +1512,14 @@ fn floor_divide(x: BigUInt, y: BigUInt) raises -> BigUInt: # CASE: y is zero if y.is_zero(): - raise Error("biguint.arithmetics.floor_divide(): Division by zero") + raise Error( + ZeroDivisionError( + file="src/decimojo/biguint/arithmetics.mojo", + function="floor_divide()", + message="Division by zero", + previous_error=None, + ) + ) # CASE: Dividend is zero if x.is_zero(): @@ -2671,7 +2711,9 @@ fn floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: The remainder of x1 being divided by x2. Raises: - ValueError: If the divisor is zero. + ZeroDivisionError: If the divisor is zero. + Error: If `floor_divide()` raises an error. + Error: If `subtract()` raises an error. Notes: It is equal to floored modulo for positive numbers. @@ -2682,7 +2724,14 @@ fn floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: len(x2.words) == 1, "truncate_modulo(): leading zero words", ) - raise Error("Error in `truncate_modulo`: Division by zero") + raise Error( + ZeroDivisionError( + file="src/decimojo/biguint/arithmetics.py", + function="floor_modulo()", + message="Division by zero", + previous_error=None, + ) + ) # CASE: Dividend is zero if x1.is_zero(): @@ -2700,10 +2749,32 @@ fn floor_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: return x1 # Calculate quotient with truncation - var quotient = floor_divide(x1, x2) + var quotient: BigUInt + try: + quotient = floor_divide(x1, x2) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/arithmetics.py", + function="floor_modulo()", + message=None, + previous_error=e, + ) + ) # Calculate remainder: dividend - (divisor * quotient) - var remainder = subtract(x1, multiply(x2, quotient)) + var remainder: BigUInt + try: + remainder = subtract(x1, multiply(x2, quotient)) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/arithmetics.py", + function="floor_modulo()", + message=None, + previous_error=e, + ) + ) return remainder^ @@ -2713,8 +2784,28 @@ fn truncate_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: """Returns the remainder of two BigUInt numbers, truncating toward zero. It is equal to floored modulo for unsigned numbers. See `floor_modulo` for more details. + + Args: + x1: The dividend. + x2: The divisor. + + Returns: + The remainder of x1 being divided by x2. + + Raises: + Error: If `floor_modulo()` raises an OverflowError. """ - return floor_modulo(x1, x2) + try: + return floor_modulo(x1, x2) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/arithmetics.py", + function="truncate_modulo()", + message=None, + previous_error=e, + ) + ) fn ceil_modulo(x1: BigUInt, x2: BigUInt) raises -> BigUInt: @@ -2781,15 +2872,26 @@ fn floor_divide_modulo( The quotient of x1 / x2, truncated toward zero and the remainder. Raises: - ValueError: If the divisor is zero. + Error: If `floor_divide()` raises an error. + Error: If `subtract()` raises an error. Notes: It is equal to truncated division for positive numbers. """ - var quotient = floor_divide(x1, x2) - var remainder = subtract(x1, multiply(x2, quotient)) - return (quotient^, remainder^) + try: + var quotient = floor_divide(x1, x2) + var remainder = subtract(x1, multiply(x2, quotient)) + return (quotient^, remainder^) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/arithmetics.py", + function="floor_divide_modulo()", + message=None, + previous_error=e, + ) + ) # ===----------------------------------------------------------------------=== # @@ -2948,10 +3050,25 @@ fn normalize_borrows(mut x: BigUInt): fn power_of_10(n: Int) raises -> BigUInt: - """Calculates 10^n efficiently.""" + """Calculates 10^n efficiently for non-negative n. + + Args: + n: The exponent, must be non-negative. + + Returns: + A BigUInt representing 10 raised to the power of n. + + Raises: + DeciMojoError: If n is negative. + """ if n < 0: raise Error( - "biguint.arithmetics.power_of_10(): Negative exponent not supported" + DeciMojoError( + file="src/decimojo/biguint/arithmetics.py", + function="power_of_10()", + message="Negative exponent not supported", + previous_error=None, + ) ) if n == 0: diff --git a/src/decimojo/biguint/biguint.mojo b/src/decimojo/biguint/biguint.mojo index 3b4c9e2..d7477c7 100644 --- a/src/decimojo/biguint/biguint.mojo +++ b/src/decimojo/biguint/biguint.mojo @@ -27,6 +27,13 @@ from memory import UnsafePointer, memcpy import decimojo.biguint.arithmetics import decimojo.biguint.comparison +from decimojo.errors import ( + DeciMojoError, + ConversionError, + ValueError, + IndexError, + OverflowError, +) import decimojo.str # Type aliases @@ -196,9 +203,12 @@ struct BigUInt( self = Self.from_int(value) except e: raise Error( - "`BigUInt.__init__()`: Error calling" - " `BigUInt.from_int()`.\nTrace back:" - + String(e) + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__init__(value: Int)", + message=None, + previous_error=e, + ) ) @implicit @@ -224,9 +234,27 @@ struct BigUInt( fn __init__(out self, value: String, ignore_sign: Bool = False) raises: """Initializes a BigUInt from a string representation. - See `from_string()` for more information. + + Args: + value: The string representation of the BigUInt. + ignore_sign: A Bool value indicating whether to ignore the sign. + If True, the sign is ignored. + If False, the sign is considered. + + Raises: + Error: If an error occurs in `from_string()`. """ - self = Self.from_string(value, ignore_sign=ignore_sign) + try: + self = Self.from_string(value, ignore_sign=ignore_sign) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__init__(value: String)", + message=None, + previous_error=e, + ) + ) # ===------------------------------------------------------------------=== # # Constructing methods that are not dunders @@ -249,6 +277,9 @@ struct BigUInt( Each UInt32 word represents digits ranging from 0 to 10^9 - 1. The words are stored in little-endian order. + Raises: + Error: If any word is larger than `999_999_999`. + Returns: The BigUInt representation of the list of UInt32 words. """ @@ -260,8 +291,16 @@ struct BigUInt( for word in words: if word > UInt32(999_999_999): raise Error( - "Error in `BigUInt.from_list()`: Word value exceeds maximum" - " value of 999_999_999" + OverflowError( + message=( + "Word value " + + String(word) + + " exceeds maximum value of 999_999_999" + ), + function="BigUInt.from_list()", + file="src/decimojo/biguint/biguint.mojo", + previous_error=None, + ) ) return Self(words^) @@ -275,11 +314,15 @@ struct BigUInt( Each UInt32 word represents digits ranging from 0 to 10^9 - 1. The words are stored in little-endian order. + Raises: + Error: If any word is larger than `999_999_999`. + Notes: This method validates whether the words are smaller than `999_999_999`. Example: + ```console BigUInt.from_words(123456789, 987654321) # 987654321_123456789 ``` @@ -292,8 +335,16 @@ struct BigUInt( for word in words: if word > UInt32(999_999_999): raise Error( - "Error in `BigUInt.__init__()`: Word value exceeds maximum" - " value of 999_999_999" + OverflowError( + message=( + "Word value " + + String(word) + + " exceeds maximum value of 999_999_999" + ), + function="BigUInt.from_words()", + file="src/decimojo/biguint/biguint.mojo", + previous_error=None, + ) ) else: list_of_words.append(word) @@ -341,16 +392,32 @@ struct BigUInt( @staticmethod fn from_int(value: Int) raises -> Self: - """Creates a BigUInt from an integer.""" + """Creates a BigUInt from an integer. + + Args: + value: The integer value to be converted to BigUInt. + + Returns: + The BigUInt representation of the integer value. + + Raises: + Error: If the input value is negative. + """ if value == 0: return Self() if value < 0: raise Error( - "`BigUInt.from_int()`: The input value ", - value, - " is negative and is not compatible with BigUInt.", - sep="", + OverflowError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.from_int(value: Int)", + message=( + "The input value " + + String(value) + + " is negative and is not compatible with BigUInt." + ), + previous_error=None, + ) ) var list_of_words = List[UInt32]() @@ -522,6 +589,14 @@ struct BigUInt( If True, the sign is ignored. If False, the sign is considered. + Raises: + OverflowError: If the input value is negative and `ignore_sign` is + False. + ConversionError: If the input value is not a valid integer string. + The scale is larger than the number of digits. + ConversionError: If the input value is not an integer string. + The fractional part is not zero. + Returns: The BigUInt representation of the string. """ @@ -531,7 +606,20 @@ struct BigUInt( coef, scale, sign = decimojo.str.parse_numeric_string(value) if (not ignore_sign) and sign: - raise Error("Error in `from_string()`: The value is negative") + raise Error( + OverflowError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.from_string(value: String)", + message=( + 'The input value "' + + value + + '" is negative but `ignore_sign` is False.\n' + + "Consider using `ignore_sign=True` to ignore the" + " sign." + ), + previous_error=None, + ) + ) # Check if the number is zero if len(coef) == 1 and coef[0] == UInt8(0): @@ -543,12 +631,32 @@ struct BigUInt( if scale > 0: if scale >= len(coef): raise Error( - "Error in `from_string`: The number is not an integer." + ConversionError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.from_string(value: String)", + message=( + 'The input value "' + + value + + '" is not an integer.\n' + + "The scale is larger than the number of digits." + ), + previous_error=None, + ) ) for i in range(1, scale + 1): if coef[-i] != 0: raise Error( - "Error in `from_string`: The number is not an integer." + ConversionError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.from_string(value: String)", + message=( + 'The input value "' + + value + + '" is not an integer.\n' + + "The fractional part is not zero." + ), + previous_error=None, + ) ) coef.resize(len(coef) - scale, UInt8(0)) scale = 0 @@ -615,9 +723,24 @@ struct BigUInt( fn __int__(self) raises -> Int: """Returns the number as Int. - See `to_int()` for more information. + + Returns: + The number as Int. + + Raises: + Error: If to_int() raises an error. """ - return self.to_int() + try: + return self.to_int() + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__int__()", + message=None, + previous_error=e, + ) + ) fn __str__(self) -> String: """Returns string representation of the BigUInt. @@ -646,27 +769,31 @@ struct BigUInt( The number as Int. Raises: - Error: If the number is too large or too small to fit in Int. + OverflowError: If the number exceeds the size of Int (2^63-1). """ # 2^63-1 = 9_223_372_036_854_775_807 # is larger than 10^18 -1 but smaller than 10^27 - 1 - if len(self.words) > 3: - raise Error( - "Error in `BigUInt.to_int()`: The number exceeds the size" - " of Int" + var overflow_error: Error = Error( + OverflowError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.to_int()", + message="The number exceeds the size of Int (" + + String(Int.MAX) + + ")", + previous_error=None, ) + ) + if len(self.words) > 3: + raise overflow_error var value: Int128 = 0 for i in range(len(self.words)): value += Int128(self.words[i]) * Int128(1_000_000_000) ** i if value > Int128(Int.MAX): - raise Error( - "Error in `BigUInt.to_int()`: The number exceeds the size" - " of Int" - ) + raise overflow_error return Int(value) @@ -677,12 +804,20 @@ struct BigUInt( The number as UInt64. Raises: - Error: If the number is too large or too small to fit in Int. + Error: If the number exceeds the size of UInt64. """ if self.is_uint64_overflow(): raise Error( - "`BigUInt.to_int()`: The number exceeds the size" - " of UInt64 (18446744073709551615)" + OverflowError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.to_uint64()", + message=( + "The number exceeds the size of UInt64 (" + + String(UInt64.MAX) + + ")" + ), + previous_error=None, + ) ) if len(self.words) == 1: @@ -897,7 +1032,7 @@ struct BigUInt( return decimojo.biguint.arithmetics.negative(self) @always_inline - fn __rshift__(self, shift_amount: Int) raises -> Self: + fn __rshift__(self, shift_amount: Int) -> Self: """Returns the result of floored divison by 2 to the power of `shift_amount`. """ var result = self @@ -917,7 +1052,17 @@ struct BigUInt( @always_inline fn __sub__(self, other: Self) raises -> Self: - return decimojo.biguint.arithmetics.subtract(self, other) + try: + return decimojo.biguint.arithmetics.subtract(self, other) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__sub__(other: Self)", + message=None, + previous_error=e, + ) + ) @always_inline fn __mul__(self, other: Self) -> Self: @@ -925,28 +1070,88 @@ struct BigUInt( @always_inline fn __floordiv__(self, other: Self) raises -> Self: - return decimojo.biguint.arithmetics.floor_divide(self, other) + try: + return decimojo.biguint.arithmetics.floor_divide(self, other) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__floordiv__(other: Self)", + message=None, + previous_error=e, + ) + ) @always_inline fn __ceildiv__(self, other: Self) raises -> Self: """Returns the result of ceiling division.""" - return decimojo.biguint.arithmetics.ceil_divide(self, other) + try: + return decimojo.biguint.arithmetics.ceil_divide(self, other) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__ceildiv__(other: Self)", + message=None, + previous_error=e, + ) + ) @always_inline fn __mod__(self, other: Self) raises -> Self: - return decimojo.biguint.arithmetics.floor_modulo(self, other) + try: + return decimojo.biguint.arithmetics.floor_modulo(self, other) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__mod__(other: Self)", + message=None, + previous_error=e, + ) + ) @always_inline fn __divmod__(self, other: Self) raises -> Tuple[Self, Self]: - return decimojo.biguint.arithmetics.floor_divide_modulo(self, other) + try: + return decimojo.biguint.arithmetics.floor_divide_modulo(self, other) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__divmod__(other: Self)", + message=None, + previous_error=e, + ) + ) @always_inline fn __pow__(self, exponent: Self) raises -> Self: - return self.power(exponent) + try: + return self.power(exponent) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__pow__(exponent: Self)", + message=None, + previous_error=e, + ) + ) @always_inline fn __pow__(self, exponent: Int) raises -> Self: - return self.power(exponent) + try: + return self.power(exponent) + except e: + raise Error( + DeciMojoError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.__pow__(exponent: Int)", + message=None, + previous_error=e, + ) + ) # ===------------------------------------------------------------------=== # # Basic binary right-side arithmetic operation dunders @@ -1147,7 +1352,7 @@ struct BigUInt( decimojo.biguint.arithmetics.multiply_inplace_by_power_of_ten(self, n) @always_inline - fn floor_divide_by_power_of_ten(self, n: Int) raises -> Self: + fn floor_divide_by_power_of_ten(self, n: Int) -> Self: """Returns the result of floored dividing this number by 10^n (n>=0). It is equal to removing the last n digits of the number. See `floor_divide_by_power_of_ten()` for more information. @@ -1175,20 +1380,45 @@ struct BigUInt( exponent: The exponent to raise the number to. Returns: - The result of raising this number to the power of `exponent`. + ValueError: If the exponent is negative. + ValueError: If the exponent is too large. Raises: Error: If the exponent is negative. Error: If the exponent is too large, e.g., larger than 1_000_000_000. """ if exponent < 0: - raise Error("Error in `BigUInt.power()`: The exponent is negative") + raise Error( + ValueError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.power(exponent: Int)", + message=( + "The exponent " + + String(exponent) + + " is negative.\n" + + "Consider using a non-negative exponent." + ), + previous_error=None, + ) + ) if exponent == 0: return Self(1) - if exponent > 1_000_000_000: - raise Error("Error in `BigUInt.power()`: The exponent is too large") + if exponent >= 1_000_000_000: + raise Error( + ValueError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.power(exponent: Int)", + message=( + "The exponent " + + String(exponent) + + " is too large.\n" + + "Consider using an exponent below 1_000_000_000." + ), + previous_error=None, + ) + ) var result = Self(1) var base = self @@ -1203,9 +1433,30 @@ struct BigUInt( fn power(self, exponent: Self) raises -> Self: """Returns the result of raising this number to the power of `exponent`. + + Args: + exponent: The exponent to raise the number to. + + Raises: + ValueError: If the exponent is too large. + + Returns: + The result of raising this number to the power of `exponent`. """ - if exponent > BigUInt(UInt32(0), UInt32(1)): - raise Error("Error in `BigUInt.power()`: The exponent is too large") + if len(exponent.words) > 1: + raise Error( + ValueError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.power(exponent: BigUInt)", + message=( + "The exponent " + + String(exponent) + + " is too large.\n" + + "Consider using an exponent below 1_000_000_000." + ), + previous_error=None, + ) + ) var exponent_as_int = exponent.to_int() return self.power(exponent_as_int) @@ -1429,6 +1680,7 @@ struct BigUInt( @always_inline fn ith_digit(self, i: Int) raises -> UInt8: """Returns the ith least significant digit of the BigUInt. + If the index is more than the number of digits, it returns 0. Args: i: The index of the digit to return. The least significant digit @@ -1438,11 +1690,22 @@ struct BigUInt( The ith least significant digit of the BigUInt. Raises: - Error: If the index is negative or larger than the number of digits - in the BigUInt. + IndexError: If the index is negative. """ if i < 0: - raise Error("Error in `ith_digit()`: The index is negative") + raise Error( + IndexError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.ith_digit(i: Int)", + message=( + "The index " + + String(i) + + " is negative.\n" + + "Consider using a non-negative index." + ), + previous_error=None, + ) + ) if i >= len(self.words) * 9: return 0 var word_index = i // 9 @@ -1524,7 +1787,7 @@ struct BigUInt( rounding_mode: RoundingMode, remove_extra_digit_due_to_rounding: Bool, ) raises -> Self: - """Removes trailing digits from the BigUInt. + """Removes trailing digits from the BigUInt with rounding. Args: ndigits: The number of digits to remove. @@ -1539,6 +1802,9 @@ struct BigUInt( Returns: The BigUInt with the trailing digits removed. + Raises: + ValueError: If the number of digits to remove is negative. + Notes: Rounding can result in an extra digit. Exmaple: remove last 1 digit of @@ -1547,19 +1813,38 @@ struct BigUInt( """ if ndigits < 0: raise Error( - "Error in `remove_trailing_digits()`: The number of digits to" - " remove is negative" + ValueError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.remove_trailing_digits_with_rounding()", + message=( + "The number of digits to remove is negative: " + + String(ndigits) + ), + previous_error=None, + ) ) if ndigits == 0: return self if ndigits > self.number_of_digits(): raise Error( - "Error in `remove_trailing_digits()`: The number of digits to" - " remove is larger than the number of digits in the BigUInt" + ValueError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.remove_trailing_digits_with_rounding()", + message=( + "The number of digits to remove is larger than the " + "number of digits in the BigUInt: " + + String(ndigits) + + " > " + + String(self.number_of_digits()) + ), + previous_error=None, + ) ) # floor_divide_by_power_of_ten is the same as removing the last n digits - var result = self.floor_divide_by_power_of_ten(ndigits) + var result = decimojo.biguint.arithmetics.floor_divide_by_power_of_ten( + self, ndigits + ) var round_up: Bool = False if rounding_mode == RoundingMode.ROUND_DOWN: @@ -1583,7 +1868,12 @@ struct BigUInt( round_up = self.ith_digit(ndigits) % 2 == 1 else: raise Error( - "Error in `remove_trailing_digits()`: Unknown rounding mode" + ValueError( + file="src/decimojo/biguint/biguint.mojo", + function="BigUInt.remove_trailing_digits_with_rounding()", + message=("Unknown rounding mode: " + String(rounding_mode)), + previous_error=None, + ) ) if round_up: @@ -1593,7 +1883,7 @@ struct BigUInt( # Check whether rounding results in extra digit if result.is_power_of_10(): if remove_extra_digit_due_to_rounding: - result = result.floor_divide_by_power_of_ten( - 1, + result = decimojo.biguint.arithmetics.floor_divide_by_power_of_ten( + result, 1 ) return result^ diff --git a/src/decimojo/errors.mojo b/src/decimojo/errors.mojo index 97d870e..7b830ae 100644 --- a/src/decimojo/errors.mojo +++ b/src/decimojo/errors.mojo @@ -15,43 +15,176 @@ # ===----------------------------------------------------------------------=== # """ -Implement error handling for DeciMojo. +Implements error handling for DeciMojo. """ +from pathlib.path import cwd + +alias OverflowError = DeciMojoError[error_type="OverflowError"] +"""Type for overflow errors in DeciMojo. + +Fields: + +file: The file where the error occurred.\\ +function: The function where the error occurred.\\ +message: An optional message describing the error.\\ +previous_error: An optional previous error that caused this error. +""" + +alias IndexError = DeciMojoError[error_type="IndexError"] +"""Type for index errors in DeciMojo. + +Fields: + +file: The file where the error occurred.\\ +function: The function where the error occurred.\\ +message: An optional message describing the error.\\ +previous_error: An optional previous error that caused this error. +""" + +alias KeyError = DeciMojoError[error_type="KeyError"] +"""Type for key errors in DeciMojo. + +Fields: + +file: The file where the error occurred.\\ +function: The function where the error occurred.\\ +message: An optional message describing the error.\\ +previous_error: An optional previous error that caused this error. +""" + +alias ValueError = DeciMojoError[error_type="ValueError"] +"""Type for value errors in DeciMojo. + +Fields: + +file: The file where the error occurred.\\ +function: The function where the error occurred.\\ +message: An optional message describing the error.\\ +previous_error: An optional previous error that caused this error. +""" + + +alias ZeroDivisionError = DeciMojoError[error_type="ZeroDivisionError"] + +"""Type for divided-by-zero errors in DeciMojo. + +Fields: + +file: The file where the error occurred.\\ +function: The function where the error occurred.\\ +message: An optional message describing the error.\\ +previous_error: An optional previous error that caused this error. +""" + +alias ConversionError = DeciMojoError[error_type="ConversionError"] + +"""Type for conversion errors in DeciMojo. + +Fields: + +file: The file where the error occurred.\\ +function: The function where the error occurred.\\ +message: An optional message describing the error.\\ +previous_error: An optional previous error that caused this error. +""" + +alias HEADER_OF_ERROR_MESSAGE = """ +--------------------------------------------------------------------------- +DeciMojoError Traceback (most recent call last) +""" + + +struct DeciMojoError[error_type: String = "DeciMojoError"]( + Stringable, Writable +): + """Base type for all DeciMojo errors. + + Parameters: + error_type: The type of the error, e.g., "OverflowError", "IndexError". + + Fields: + + file: The file where the error occurred.\\ + function: The function where the error occurred.\\ + message: An optional message describing the error.\\ + previous_error: An optional previous error that caused this error. + """ -struct DeciError(Stringable): - var error_type: String - var message: String - var function: String var file: String - var line: Int + var function: String + var message: Optional[String] + var previous_error: Optional[String] fn __init__( out self, - error_type: String, - message: String, - function: String, file: String, - line: Int, + function: String, + message: Optional[String], + previous_error: Optional[Error], ): - self.error_type = error_type - self.message = message - self.function = function self.file = file - self.line = line + self.function = function + self.message = message + if previous_error is None: + self.previous_error = None + else: + self.previous_error = "\n".join( + previous_error.value().as_string_slice().split("\n")[3:] + ) fn __str__(self) -> String: - return ( - "Traceback (most recent call last):\n" - + ' File "' - + String(self.file) - + '", line ' - + String(self.line) - + " in " - + String(self.function) - + "\n" - + String(self.error_type) - + ": " - + String(self.message) - + '"' - ) + if self.message is None: + return ( + "Traceback (most recent call last):\n" + + ' File "' + + self.file + + '"' + + " in " + + self.function + + "\n\n" + ) + + else: + return ( + "Traceback (most recent call last):\n" + + ' File "' + + self.file + + '"' + + " in " + + self.function + + "\n\n" + + String(error_type) + + ": " + + self.message.value() + + "\n" + ) + + fn write_to[W: Writer](self, mut writer: W): + writer.write("\n") + writer.write(("-" * 80)) + writer.write("\n") + writer.write(error_type.ljust(47, " ")) + writer.write("Traceback (most recent call last)\n") + writer.write('File "') + try: + writer.write(String(cwd())) + except e: + pass + finally: + writer.write("/") + writer.write(self.file) + writer.write('"\n') + writer.write("----> ") + writer.write(self.function) + if self.message is None: + writer.write("\n") + else: + writer.write("\n\n") + writer.write(error_type) + writer.write(": ") + writer.write(self.message.value()) + writer.write("\n") + if self.previous_error is not None: + writer.write("\n") + writer.write(self.previous_error.value()) diff --git a/src/tomlmojo/tokenizer.mojo b/src/tomlmojo/tokenizer.mojo index 10a1962..faa42e6 100644 --- a/src/tomlmojo/tokenizer.mojo +++ b/src/tomlmojo/tokenizer.mojo @@ -21,7 +21,6 @@ needed for test case parsing. """ alias WHITESPACE = " \t" -alias NEWLINE = "\n\r" alias COMMENT_START = "#" alias QUOTE = '"' alias LITERAL_QUOTE = "'" @@ -85,7 +84,7 @@ struct TokenType(Copyable, Movable): alias ARRAY_OF_TABLES_START = TokenType.array_of_tables_start() alias EQUAL = TokenType.equal() alias COMMA = TokenType.comma() - alias NEWLINE = TokenType.newline() # Renamed to avoid conflict with NEWLINE constant + alias NEWLINE = TokenType.newline() alias DOT = TokenType.dot() alias EOF = TokenType.eof() alias ERROR = TokenType.error() @@ -208,7 +207,16 @@ struct Tokenizer: fn _skip_comment(mut self): """Skip comment lines.""" if self.current_char == COMMENT_START: - while self.current_char and self.current_char not in NEWLINE: + while self.current_char: + # Stop at LF or CR + if self.current_char == "\n": + break + if self.current_char == "\r": + # If next char is \n, treat as CRLF and break + if self._get_char(self.position.index + 1) == "\n": + break + else: + break self._advance() fn _read_string(mut self) -> Token: @@ -296,10 +304,32 @@ struct Tokenizer: self._skip_comment() return self.next_token() - if self.current_char in NEWLINE: + # Handle CRLF and LF newlines + if self.current_char == "\r": + # Check for CRLF + if self._get_char(self.position.index + 1) == "\n": + token = Token( + TokenType.NEWLINE, + "\r\n", + self.position.line, + self.position.column, + ) + self._advance() # Skip \r + self._advance() # Skip \n + return token + else: + token = Token( + TokenType.NEWLINE, + "\r", + self.position.line, + self.position.column, + ) + self._advance() + return token + elif self.current_char == "\n": token = Token( TokenType.NEWLINE, - self.current_char, + "\n", self.position.line, self.position.column, )