├── .gitattributes ├── pawn_errors_version.inc ├── pawn.json ├── .gitignore ├── .vscode └── tasks.json ├── test.pwn ├── README.md └── errors.inc /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pwn linguist-language=Pawn 2 | *.inc linguist-language=Pawn 3 | -------------------------------------------------------------------------------- /pawn_errors_version.inc: -------------------------------------------------------------------------------- 1 | // This file was generated by "sampctl package release" 2 | // DO NOT EDIT THIS FILE MANUALLY! 3 | // To update the version number for a new release, run "sampctl package release" 4 | 5 | #define PAWN_ERRORS_VERSION_MAJOR (1) 6 | #define PAWN_ERRORS_VERSION_MINOR (2) 7 | #define PAWN_ERRORS_VERSION_PATCH (4) 8 | -------------------------------------------------------------------------------- /pawn.json: -------------------------------------------------------------------------------- 1 | { 2 | "user": "Southclaws", 3 | "repo": "pawn-errors", 4 | "entry": "test.pwn", 5 | "output": "test.amx", 6 | "dependencies": [ 7 | "sampctl/samp-stdlib", 8 | "Zeex/samp-plugin-crashdetect:v4.19" 9 | ], 10 | "dev_dependencies": [ 11 | "pawn-lang/YSI-Includes" 12 | ], 13 | "runtime": { 14 | "version": "0.3.7", 15 | "mode": "y_testing", 16 | "rcon_password": "password", 17 | "port": 7777, 18 | "hostname": "SA-MP Server", 19 | "maxplayers": 50, 20 | "language": "" 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # 2 | # Package only files 3 | # 4 | 5 | # Compiled Bytecode, precompiled output and assembly 6 | *.amx 7 | *.lst 8 | *.asm 9 | 10 | # Vendor directory for dependencies 11 | dependencies/ 12 | 13 | # Dependency versions lockfile 14 | pawn.lock 15 | 16 | 17 | # 18 | # Server/gamemode related files 19 | # 20 | 21 | # compiled settings file 22 | # keep `samp.json` file on version control 23 | # but make sure the `rcon_password` field is set externally 24 | # you can use the environment variable `SAMP_RCON_PASSWORD` to do this. 25 | server.cfg 26 | 27 | # Plugins directory 28 | plugins/ 29 | 30 | # binaries 31 | *.exe 32 | *.dll 33 | *.so 34 | announce 35 | samp03svr 36 | samp-npc 37 | 38 | # logs 39 | logs/ 40 | server_log.txt 41 | 42 | # 43 | # Common files 44 | # 45 | 46 | *.sublime-workspace 47 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build only", 6 | "type": "shell", 7 | "command": "sampctl package build", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | }, 12 | "isBackground": false, 13 | "presentation": { 14 | "reveal": "silent", 15 | "panel": "dedicated" 16 | }, 17 | "problemMatcher": "$sampctl" 18 | }, 19 | { 20 | "label": "build watcher", 21 | "type": "shell", 22 | "command": "sampctl package build --watch", 23 | "group": "build", 24 | "isBackground": true, 25 | "presentation": { 26 | "reveal": "silent", 27 | "panel": "dedicated" 28 | }, 29 | "problemMatcher": "$sampctl" 30 | }, 31 | { 32 | "label": "run tests", 33 | "type": "shell", 34 | "command": "sampctl package run", 35 | "group": { 36 | "kind": "test", 37 | "isDefault": true 38 | }, 39 | "isBackground": true, 40 | "presentation": { 41 | "reveal": "silent", 42 | "panel": "dedicated" 43 | }, 44 | "problemMatcher": "$sampctl" 45 | }, 46 | { 47 | "label": "run tests watcher", 48 | "type": "shell", 49 | "command": "sampctl package run --watch", 50 | "group": "test", 51 | "isBackground": true, 52 | "presentation": { 53 | "reveal": "silent", 54 | "panel": "dedicated" 55 | }, 56 | "problemMatcher": "$sampctl" 57 | } 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /test.pwn: -------------------------------------------------------------------------------- 1 | #include "errors.inc" 2 | 3 | #define RUN_TESTS 4 | #include 5 | 6 | 7 | Error:failsOnTrue(bool:fails) { 8 | if(fails) { 9 | return Error(1, "i failed :("); 10 | } 11 | 12 | return Ok(); 13 | } 14 | 15 | Error:failsOn5(input) { 16 | new bool:fails; 17 | if(input == 5) { 18 | fails = true; 19 | } 20 | 21 | new Error:e = failsOnTrue(fails); 22 | if(IsError(e)) { 23 | return Error(1); 24 | } 25 | 26 | return Ok(); 27 | } 28 | 29 | Error:failsOnOdd(input) { 30 | new fails; 31 | if(input % 2 != 0) { 32 | fails = 5; 33 | } 34 | 35 | new Error:e = failsOn5(fails); 36 | if(IsError(e)) { 37 | return Error(1, "value was not odd"); 38 | } 39 | 40 | return Ok(); 41 | } 42 | 43 | Error:failsOn5WarnsOn6(input) { 44 | if(input == 6) { 45 | return Ok(2); 46 | } 47 | if(input == 5) { 48 | return Error(1, "function incomplete, can not continue"); 49 | } 50 | 51 | return Ok(); 52 | } 53 | 54 | Test:ErrorDepth1() { 55 | print("\n\n\n--- ErrorDepth1 ---\n\n\n"); 56 | { 57 | new Error:e; 58 | 59 | e = failsOnTrue(true); 60 | ASSERT(e == Error:1); 61 | 62 | new count = GetErrorCount(); 63 | ASSERT(count == 1); 64 | 65 | new 66 | gotError[512], 67 | wantFind[] = "Error:failsOnTrue (bool:fails=true)"; 68 | GetErrors(gotError); 69 | printf("'%s'", gotError); 70 | ASSERT(strfind(gotError, wantFind) != -1); 71 | 72 | PrintErrors(); 73 | ASSERT(Handled() == 0); 74 | ASSERT(GetErrorCount() == 0); 75 | } 76 | 77 | ASSERT(GetErrorCount() == 0); 78 | ASSERT(Handled(true) == 1); 79 | } 80 | 81 | Test:ErrorDepth2() { 82 | print("\n\n\n--- ErrorDepth2 ---\n\n\n"); 83 | { 84 | new Error:e; 85 | 86 | e = failsOn5(5); 87 | ASSERT(e == Error:1); 88 | 89 | new count = GetErrorCount(); 90 | ASSERT(count == 2); 91 | 92 | new 93 | gotError[1024], 94 | wantFind[] = "(none)"; 95 | GetErrors(gotError); 96 | printf("'%s'", gotError); 97 | ASSERT(strfind(gotError, wantFind) != -1); 98 | 99 | PrintErrors(); 100 | ASSERT(Handled() == 0); 101 | ASSERT(GetErrorCount() == 0); 102 | } 103 | 104 | ASSERT(GetErrorCount() == 0); 105 | ASSERT(Handled(true) == 1); 106 | } 107 | 108 | Test:ErrorDepth3() { 109 | print("\n\n\n--- ErrorDepth3 ---\n\n\n"); 110 | { 111 | new Error:e; 112 | 113 | e = failsOnOdd(5); 114 | ASSERT(e == Error:1); 115 | 116 | new count = GetErrorCount(); 117 | ASSERT(count == 3); 118 | 119 | new 120 | gotError[2048], 121 | wantFind[] = "value was not odd"; 122 | GetErrors(gotError); 123 | print(gotError); 124 | ASSERT(strfind(gotError, wantFind) != -1); 125 | 126 | PrintErrors(); 127 | ASSERT(Handled() == 0); 128 | ASSERT(GetErrorCount() == 0); 129 | } 130 | 131 | ASSERT(GetErrorCount() == 0); 132 | ASSERT(Handled(true) == 1); 133 | } 134 | 135 | Test:ErrorNoneWithCode() { 136 | print("\n\n\n--- ErrorNoneWithCode ---\n\n\n"); 137 | { 138 | new Error:e; 139 | 140 | e = failsOn5WarnsOn6(5); 141 | ASSERT(e == Error:1); 142 | 143 | new count = GetErrorCount(); 144 | printf("%d", count); 145 | ASSERT(count == 1); 146 | 147 | new 148 | gotError[512]; 149 | GetErrors(gotError); 150 | print(gotError); 151 | 152 | Handled(); 153 | 154 | e = failsOn5WarnsOn6(6); 155 | ASSERT(e == Error:2); 156 | 157 | ASSERT(GetErrorCount() == 0); 158 | } 159 | 160 | ASSERT(GetErrorCount() == 0); 161 | } 162 | 163 | Test:ErrorUnhandled() { 164 | print("\n\n\n--- ErrorUnhandled ---\n\n\n"); 165 | { 166 | new Error:e; 167 | 168 | e = failsOnTrue(true); 169 | ASSERT(e == Error:1); 170 | 171 | new count = GetErrorCount(); 172 | ASSERT(count == 1); 173 | 174 | new 175 | gotError[512]; 176 | GetErrors(gotError); 177 | print(gotError); 178 | } 179 | 180 | ASSERT(GetErrorCount() != 0); 181 | } 182 | 183 | Test:GetLastErrorCause() { 184 | print("\n\n\n--- GetLastErrorCause ---\n\n\n"); 185 | { 186 | new Error:e; 187 | 188 | e = failsOnTrue(true); 189 | ASSERT(e == Error:1); 190 | 191 | new cause[128]; 192 | new ret = GetLastErrorCause(cause); 193 | ASSERT(ret == 0); 194 | 195 | new 196 | wantFind[] = "i failed :("; 197 | printf("'%s'", cause); 198 | ASSERT(strfind(cause, wantFind) != -1); 199 | 200 | PrintErrors(); 201 | ASSERT(Handled() == 0); 202 | ASSERT(GetErrorCount() == 0); 203 | } 204 | } 205 | 206 | Test:GetErrorCause() { 207 | print("\n\n\n--- GetErrorCause ---\n\n\n"); 208 | { 209 | new Error:e; 210 | 211 | e = failsOnTrue(true); 212 | ASSERT(e == Error:1); 213 | 214 | e = failsOnTrue(true); 215 | ASSERT(e == Error:1); 216 | 217 | new cause[128]; 218 | new ret = GetErrorCause(0, cause); 219 | ASSERT(ret == 0); 220 | 221 | new 222 | wantFind[] = "i failed :("; 223 | printf("'%s'", cause); 224 | ASSERT(strfind(cause, wantFind) != -1); 225 | 226 | ret = GetErrorCause(1, cause); 227 | printf("'%s'", cause); 228 | ASSERT(strfind(cause, wantFind) != -1); 229 | 230 | PrintErrors(); 231 | ASSERT(Handled() == 0); 232 | ASSERT(GetErrorCount() == 0); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pawn-errors 2 | 3 | [![sampctl](https://shields.southcla.ws/badge/sampctl-pawn--errors-2f2f2f.svg?style=for-the-badge)](https://github.com/Southclaws/pawn-errors) 4 | 5 | This is a simple library for dealing with function-level errors and making sure 6 | unhandled errors don't get quietly ignored. 7 | 8 | There exists a more complex error handling solution which implements try/catch 9 | exceptions. This library aims to be a simple, pure Pawn (no asm) alternative 10 | which does not introduce new syntax. 11 | 12 | The concept is similar to how Go and simple C programs handle errors: functions 13 | should return an _error value_ indicating success or failure. If the value is 14 | zero, everything went smoothly but if the error is anything but zero, an error 15 | occurred. 16 | 17 | This library enhances that pattern through the use of tags, destructors and a 18 | simple raise-handle model that fits the simple procedural nature of Pawn. 19 | 20 | ## Installation 21 | 22 | Simply install to your project: 23 | 24 | ```bash 25 | sampctl package install Southclaws/pawn-errors 26 | ``` 27 | 28 | Include in your code and begin using the library: 29 | 30 | ```pawn 31 | #include 32 | ``` 33 | 34 | ## Usage 35 | 36 | ### Basic Errors 37 | 38 | Functions that could potentially fail should be tagged `Error:` and either 39 | return `Ok()` or `Error()`. For example, this is a function that always 40 | fails: 41 | 42 | ```pawn 43 | Error:thisFunctionFails() { 44 | return Error(1, "I always fail!"); 45 | } 46 | ``` 47 | 48 | The first argument is an error code, this is the actual value that is returned 49 | from the function, it can be used by the call site to determine exactly went 50 | wrong. You can optionally export named constants to simplify the error checking 51 | process. 52 | 53 | This return value should be checked at the call site using `IsError`: 54 | 55 | ```pawn 56 | new Error:e = thisFunctionFails(); 57 | if(IsError(e)) { 58 | printf("ERROR: thisFunctionFails has failed"); 59 | Handled(); 60 | } 61 | ``` 62 | 63 | ### Nested Errors 64 | 65 | If an error has been returned and the call site cannot handle it, you can simply 66 | return another `Error()` to the next caller. Errors will stack along with full 67 | file and line information so when you handle them, you have all the data 68 | available. 69 | 70 | Lets modify the above example to pass an error further up the chain: 71 | 72 | ```pawn 73 | Error:doSomething() { 74 | new Error:e = thisFunctionFails(); 75 | if(IsError(e)) { 76 | return Error(1); // message is optional 77 | } 78 | } 79 | 80 | public OnSomething() { 81 | new Error:e = doSomething(); 82 | if(IsError(e)) { 83 | print("something went wrong"); 84 | Handled(); 85 | } 86 | } 87 | ``` 88 | 89 | ### Marking Errors as Handled 90 | 91 | At the end of these examples, the error is marked handled with `Handled()`. This 92 | will erase the current stack of errors and indicates that the script has 93 | returned to a safe state. 94 | 95 | If a single error or a stack of errors is unhandled, the error information will 96 | be printed once the current stack has returned (in other words, once the current 97 | callback has finished). 98 | 99 | ### Ok (NoError) and Semantics of Return Values 100 | 101 | You might have seen `NoError()` symbol in older versions, this has been deprecated now and usage 102 | will result in deprecated warning, telling developer to use `Ok()` instead. 103 | 104 | You can also return `Ok()` to indicate that the function did not fail, 105 | however this function does take an argument: 106 | 107 | ```pawn 108 | Error:temperature(input) { 109 | if(input > 100) { 110 | return Error(1, "Too hot to survive!"); 111 | } 112 | if(input > 50) { 113 | return Ok(2); // can survive, but too hot to go outside 114 | } 115 | return Ok(); // it's cool 116 | } 117 | ``` 118 | 119 | Here, the semantics are important. The first branch returns a full on error, 120 | something has gone wrong that the function can not deal with internally and must 121 | raise an error. 122 | 123 | The second branch declares that there's no error, but it still returns a 124 | non-zero exit code which indicates to the call site that the function did not 125 | complete but it wasn't because of instability, it was merely something else less 126 | important. 127 | 128 | Take for example an account load function, it has three exit states: 129 | 130 | - 1: Account was corrupt in some way 131 | - 2: Account is banned 132 | - 0: Account is fine 133 | 134 | The first state is an error, something has gone wrong with the system that has 135 | resulted in a corrupt file. The second state is more mild, the account wasn't 136 | loaded because the user is banned, that's not an error that's just a situation 137 | where the function did not complete but it was an expected outcome. And finally 138 | the zero return code is the success state. Functions only ever need a single 139 | success state, otherwise they are too complex. 140 | 141 | Here's the code version of that example: 142 | 143 | ```pawn 144 | stock Error:LoadPlayerAccount(playerid, file[], fileData[]) { 145 | new Error:error; 146 | 147 | error = ReadFile(file, fileData); 148 | if(IsError(error)) { 149 | return Error(1, "failed to read player account file"); 150 | } 151 | 152 | if(fileData[E_PLAYER_BANNED]) { 153 | return Ok(2); // player is banned, no point doing more work 154 | } 155 | 156 | // do some processing on the player's account now that we know that 157 | // 1. it's not corrupt 158 | // 2. they are not banned 159 | 160 | return Ok(); // default value is 0 161 | } 162 | ``` 163 | 164 | This pattern makes use of guard clauses as points in code to catch errors early 165 | and return them up the stack to be handled. 166 | 167 | ## Testing 168 | 169 | To run the tests: 170 | 171 | ```bash 172 | sampctl package run 173 | ``` 174 | -------------------------------------------------------------------------------- /errors.inc: -------------------------------------------------------------------------------- 1 | // built-in include guard removal 2 | // just in case the user has a local dependency with the same file name 3 | #if defined _inc_errors 4 | #undef _inc_errors 5 | #endif 6 | // custom include-guard to ensure we don't duplicate 7 | #if defined _errors_included 8 | #endinput 9 | #endif 10 | #define _errors_included 11 | 12 | 13 | #include 14 | #include 15 | 16 | 17 | // MAX_BACKTRACE_SIZE is the size of the buffer to store backtrace in 18 | #if !defined MAX_BACKTRACE_SIZE 19 | #define MAX_BACKTRACE_SIZE (2048) 20 | #endif 21 | 22 | // MAX_STACKED_ERRORS sets the maximum error depth 23 | #if !defined MAX_STACKED_ERRORS 24 | #define MAX_STACKED_ERRORS (16) 25 | #endif 26 | 27 | // MAX_ERROR_MESSAGE controls the maximum size of an error message 28 | #if !defined MAX_ERROR_MESSAGE 29 | #define MAX_ERROR_MESSAGE (256) 30 | #endif 31 | 32 | 33 | // Error should be called and the return value returned from any function that 34 | // fails to do what it should. 35 | forward Error:Error(code, const what[] = ""); 36 | 37 | // Ok should be called and the return value returned from any function that 38 | // did not fail but exited with some semantic error code. 39 | forward Error:Ok(code = 0); 40 | 41 | // NoError should be called and the return value returned from any function that 42 | // did not fail but exited with some semantic error code. 43 | forward Error:NoError(code = 0); 44 | 45 | // IsError is used for checking both if an `Error:` value contains an error code 46 | // and that there are one or more errors on the error buffer. 47 | bool:IsError(Error:e); 48 | 49 | // Handled should be called on an error when it has been resolved and no longer 50 | // needs to be stored. This erases all current errors and resets error state. If 51 | // called while there are no errors present, will print an error unless `silent` 52 | // is set to true. 53 | forward Handled(silent = false); 54 | 55 | // GetErrorCount returns the amount of errors that are currently stacked up and 56 | // awaiting being `Handled()`. 57 | forward GetErrorCount(); 58 | 59 | // GetLastErrorCause writes the most recent error cause text into `out`. 60 | forward GetLastErrorCause(out[], len = sizeof out); 61 | 62 | // GetErrorCause writes the specified error cause into `out`. 63 | forward GetErrorCause(index, out[], len = sizeof out); 64 | 65 | // GetLastErrorTrace writes the most recent error trace text into `out`. 66 | forward GetLastErrorTrace(out[], len = sizeof out); 67 | 68 | // GetErrorTrace writes the specified error trace into `out`. 69 | forward GetErrorTrace(index, out[], len = sizeof out); 70 | 71 | // GetErrors returns the current error state. 72 | forward GetErrors(output[], len = sizeof output); 73 | 74 | // PrintErrors simply prints the current error state. 75 | forward PrintErrors(); 76 | 77 | 78 | forward _errors_resolveUnhandled(); 79 | 80 | static 81 | // CauseBuffer contains all stacked error messages. 82 | CauseBuffer[MAX_BACKTRACE_SIZE][MAX_ERROR_MESSAGE], 83 | // TraceBuffer contains all stacked error backtraces. 84 | TraceBuffer[MAX_STACKED_ERRORS][MAX_BACKTRACE_SIZE], 85 | // Since Pawn is procedural, Error IDs are simply an incrementing value. 86 | Count, 87 | // Error starts this timer and Handled kills it. 88 | ErrorSourceTimer; 89 | 90 | // 91 characters long: 91 | // 15 + 1 = "AMX backtrace:"" 92 | // 44 + 1 = "#0 native GetBacktrace () in crashdetect.DLL" 93 | const FIRST_LINE_LEN = 91; 94 | 95 | 96 | stock Error:Error(code, const what[] = "") { 97 | if(Count == MAX_STACKED_ERRORS - 1) { 98 | printf("[error] %d too many unhandled errors:", Count); 99 | // If we've hit the error limit, don't squash this error, instead handle 100 | // it and reset the index counter so this error can be buffered. 101 | _errors_resolveUnhandled(); 102 | // Kill the existing deferred handler timer so a new one can be started. 103 | // This will happen because the resolveUnhandled function resets Count. 104 | KillTimer(ErrorSourceTimer); 105 | } 106 | 107 | new bufferIndex = Count; 108 | Count++; 109 | 110 | // store the error cause and backtrace into the buffer. 111 | if(what[0] == EOS) { 112 | format(CauseBuffer[bufferIndex], MAX_ERROR_MESSAGE, "(none)"); 113 | } else { 114 | format(CauseBuffer[bufferIndex], MAX_ERROR_MESSAGE, what); 115 | } 116 | GetBacktrace(TraceBuffer[bufferIndex]); 117 | 118 | // if this is the first error being generated within this call stack, defer 119 | // a function call to handle unresolved errors in the future. 120 | if(bufferIndex == 0) { 121 | ErrorSourceTimer = SetTimer("_errors_resolveUnhandled", 0, false); 122 | } 123 | 124 | return Error:code; 125 | } 126 | 127 | public _errors_resolveUnhandled() { 128 | if(Count == 0) { 129 | return; 130 | } 131 | print("[error] UNHANDLED ERRORS:"); 132 | PrintErrors(); 133 | Count = 0; 134 | } 135 | 136 | stock Error:Ok(code = 0) { 137 | return Error: code; 138 | } 139 | 140 | #pragma deprecated Use Ok instead 141 | stock Error:NoError(code = 0) { 142 | return Error:code; 143 | } 144 | 145 | stock bool:IsError(Error:e) { 146 | if(_:e > 0 && Count > 0) { 147 | return true; 148 | } 149 | return false; 150 | } 151 | 152 | stock Handled(silent = false) { 153 | if(Count == 0) { 154 | if(!silent) { 155 | print("[debug] attempt to handle non-present error"); 156 | PrintAmxBacktrace(); 157 | } 158 | return 1; 159 | } 160 | 161 | Count = 0; 162 | KillTimer(ErrorSourceTimer); 163 | 164 | return 0; 165 | } 166 | 167 | stock GetErrorCount() { 168 | return Count; 169 | } 170 | 171 | stock GetLastErrorCause(out[], len = sizeof out) { 172 | format(out, len, CauseBuffer[Count - 1]); 173 | return 0; 174 | } 175 | 176 | stock GetErrorCause(index, out[], len = sizeof out) { 177 | if(index >= Count) { 178 | return 1; 179 | } 180 | format(out, len, CauseBuffer[index]); 181 | return 0; 182 | } 183 | 184 | stock GetLastErrorTrace(out[], len = sizeof out) { 185 | format(out, len, TraceBuffer[Count - 1]); 186 | return 0; 187 | } 188 | 189 | stock GetErrorTrace(index, out[], len = sizeof out) { 190 | if(index >= Count) { 191 | return 1; 192 | } 193 | format(out, len, TraceBuffer[index]); 194 | return 0; 195 | } 196 | 197 | stock GetErrors(output[], len = sizeof output) { 198 | new 199 | start, end, 200 | line[256]; 201 | 202 | format(output, len, "[error] Error stack with %d errors:\n", Count); 203 | for(new i; i < Count; ++i) { 204 | format(output, len, "%s[error] Error #%d: '%s' trace:\n", output, i, CauseBuffer[i]); 205 | 206 | // skip the first two lines, they are useless. 207 | start = strfind(TraceBuffer[i], "\n", false, end + FIRST_LINE_LEN) + 1; 208 | end = strfind(TraceBuffer[i], "\n", false, start); 209 | if(end == -1) { 210 | end = strlen(TraceBuffer[i]); // when the trace is only 3 lines long 211 | } 212 | do { 213 | start = strfind(TraceBuffer[i], " ", false, start) + 1; 214 | strmid(line, TraceBuffer[i], start, end); 215 | 216 | // this is the only difference between Print and Get 217 | format(line, sizeof line, "[error] %s\n", line); 218 | strcat(output, line, len); 219 | 220 | start = end + 1; 221 | end = strfind(TraceBuffer[i], "\n", false, start); 222 | } while(end != -1); 223 | } 224 | } 225 | 226 | stock PrintErrors() { 227 | new 228 | start, end, 229 | line[256]; 230 | 231 | printf("[error] Error stack with %d errors:", Count); 232 | for(new i; i < Count; ++i) { 233 | printf("[error] Error #%d: '%s' trace:", i, CauseBuffer[i]); 234 | 235 | // skip the first two lines, they are useless. 236 | start = strfind(TraceBuffer[i], "\n", false, end + FIRST_LINE_LEN) + 1; 237 | end = strfind(TraceBuffer[i], "\n", false, start); 238 | if(end == -1) { 239 | end = strlen(TraceBuffer[i]); // when the trace is only 3 lines long 240 | } 241 | do { 242 | start = strfind(TraceBuffer[i], " ", false, start) + 1; 243 | strmid(line, TraceBuffer[i], start, end); 244 | 245 | printf("[error] %s", line); 246 | 247 | start = end + 1; 248 | end = strfind(TraceBuffer[i], "\n", false, start); 249 | } while(end != -1); 250 | } 251 | } 252 | --------------------------------------------------------------------------------