diff --git a/LICENSE b/LICENSE index 137069b..ae188d1 100644 --- a/LICENSE +++ b/LICENSE @@ -58,7 +58,8 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright [yyyy] [name of copyright owner] +Copyright [2023] [EndMove] +Copyright [2025] [Varmix] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 44af317..552a9c2 100644 --- a/README.md +++ b/README.md @@ -406,19 +406,236 @@ print(coroutine.resume(routine, 7, 8, 9)) -- 1+2+3 = '6' ### Error handling -Lua allows low-level error handling with the `error` function and high-level error handling with the `assert` function. The `error` function raises an error and handles it with the `pcall` or `xpcall` function. The `assert` function checks a condition and raises an error if the condition is not met. +Lua provides several core functions for error handling: `assert`, `error`, `pcall` (protected call), and `xpcall` (extended protected call), each offering different levels of control. +The examples below will explain and illustrate how each method is intended to be used. -````lua +#### assert + +`assert` is used to check a condition. If the result is `false` or `nil`, it triggers an error with a custom message. +It's especially useful when validating user input or enforcing preconditions. + +```lua +local function isMajorUser(age) + assert(age >= 18, "You are not allowed to buy this item, you need to be 18 years old") +end + +isMajorUser(15) +-- Output : You are not allowed to buy this item, you need to be 18 years old ! +``` + +#### error(message [, level]) + +The `error` function allows you to manually raise an error, similar to `throw` in other languages. It immediately stops the current execution flow. +You can provide a custom error message, as well as a `level` parameter to control where the traceback starts in the call stack. + +Possible values for `level`: + +- **0**: No call stack information is shown. + +```lua +error("Simple error without trace", 0) +-- Result: Simple error without trace +``` + +- **1** *(default)*: The error appears at the exact place where `error` is called. + +```lua +local function triggerError() + error("An error has occurred", 1) +end + +triggerError() + +-- Result +--[[ +lua: script.lua:2: An error has occurred +stack traceback: + script.lua:2: in function 'triggerError' + script.lua:5: in main chunk +]] +``` + +- **2**: The error appears at the place where the function **calling `error`** was itself called. + +```lua +local function triggerError() + error("Error reported to caller", 2) +end + +local function wrapper() + triggerError() +end + +wrapper() + +-- Result +--[[ +lua: script.lua:7: Error reported to caller +stack traceback: + script.lua:7: in function 'wrapper' + script.lua:10: in main chunk +]] +``` + +#### pcall (protected call) + +As its name suggests, `pcall` allows you to execute a function in **protected mode**, catching any potential error that might occur. +It acts similarly to a `try-catch` block found in other programming languages. + +The result of calling `pcall` returns two values, typically stored in two variables like `success` and `result`, though you can name them as you wish. + +- If the call succeeds: + - `success` will be `true`, + - `result` will contain the function’s returned value(s). + +- If an error occurs: + - `success` will be `false`, + - `result` will contain the error message. + +If the function does not return anything, `result` will be `nil`. + +```lua +local function divideANumberByAnOther(a, b) + return a / b -- it would be better to validate parameters, but this is for example purposes +end + + +------------------------------- +-- First scenario: error case +------------------------------- +local success, result = pcall(divideANumberByAnOther, 1, 0) + +if not success then + print("An error has occured :", result) +end + +-- Output: An error has occured : + + +------------------------------- +-- Second scenario: valid case +------------------------------- + +local success, result = pcall(divideANumberByAnOther, 10, 2) + +if success then + print("Result is :", result) +end + +-- Output: Result is : 5 --- Throw an error if the first argument is false --- the second argument is the error message. -assert(type(firstvariable) = 'string', 'not a string') +--------------------------------------------------------------------- +-- Third scenario: combine pcall with assert for critical operations +--------------------------------------------------------------------- --- TODO to continue +local function readFile(fileName) + local fileToRead = assert(io.open(fileName, "r"), "Impossible to read the file") + -- Continue processing if successfully opened + fileToRead:close() +end -```` +local success, result = pcall(readFile, "data.txt") -- providing a file name that doesn't exist + +if not success then + print("An error has occured :", result) +end + +-- Output: An error has occured : Impossible to read the file + +-- Here we use assert to robustly manage errors: if the file cannot be opened, +-- assert triggers an error and immediately stops the program with the message. + + + +----------------------------------------------------------------------- +-- Fourth scenario: pcall with a function that returns multiple values +----------------------------------------------------------------------- +local function sumAndProduct(a, b) + return a + b, a * b +end + +local success, sum, product = pcall(sumAndProduct, 3, 4) + +if success then + print("Sum :", sum) -- 7 + print("Product :", product) -- 12 +else + print("Error : ", sum) -- sum would contain the error message if one occurred +end + +-- Reminder: pcall always returns two things: +-- 1. A boolean indicating if the call was successful. +-- 2. All values returned by the function if successful. +``` + +#### xpcall (extended protected call) + +`xpcall` is the big brother of `pcall`. It works in the same way but with an extra feature: +we can provide a **custom error handler function** that will be executed if an error occurs during the protected function call. + +This is especially useful when we want to customize how errors are displayed or logged, for example by adding stack traces or additional context. + +```lua +local function errorHandler(err) + print("πŸ”΄ Error caught:", err) + print(debug.traceback("Stack trace:", 2)) +end + +local function riskyFunction() + print("🟒 In riskyFunction") + error("Something went wrong!") +end + +local function wrapper() + print("🟑 Calling riskyFunction()") + riskyFunction() + print("🟑 After riskyFunction") -- never reached +end + +print("1. Start") + +local success, result = xpcall(wrapper, errorHandler) + +print("2. Execution finished") +print("3. Success:", success) +print("4. Result:", result) + +-- Output: +--[[ +1. Start +🟑 Calling riskyFunction() +🟒 In riskyFunction +πŸ”΄ Error caught: Something went wrong! +Stack trace: +stack traceback: + script.lua:6: in function 'riskyFunction' + script.lua:11: in function 'wrapper' + ... +2. Execution finished +3. Success: false +4. Result: nil +]] + +--[[ +=========================== +debug.traceback Cheatsheet +=========================== + +debug.traceback([message], [level]) + +- message (string) β†’ optional message shown before the stack trace +- level (number) β†’ determines where the trace starts in the call stack: + + level = 0 β†’ shows EVERYTHING, including debug.traceback itself + level = 1 β†’ (default) starts trace inside errorHandler() (not very useful) + level = 2 β†’ starts trace at the point where the error actually occurred (e.g., riskyFunction) + +Recommended usage: + debug.traceback("Optional message", 2) +]] +``` ### Modules @@ -476,3 +693,9 @@ g() -- Prints out '343', nothing printed before now. ```` +### Credits + +- EndMove - [contact@endmove.eu](mailto:contact@endmove.eu) +- Varmix - [contact@varmix.fr](mailto:contact@varmix.fr) + +