Last active
July 13, 2021 13:41
-
-
Save edubart/6e87181693e71c6e9603fa2aaf875170 to your computer and use it in GitHub Desktop.
Experimental pcall implementation for Nelua
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
##[[ | |
local typedefs = require 'nelua.typedefs' | |
typedefs.function_annots.noerror = true | |
local cgenerator = require 'nelua.cgenerator' | |
local CEmitter = require 'nelua.cemitter' | |
local orig_Call = cgenerator.visitors.Call | |
function cgenerator.visitors.Call(context, node, emitter, ...) | |
local isblockcall = context:get_visiting_node(1).tag == 'Block' | |
local funcscope = context.scope:get_up_scope_of_kind('is_function') | |
local funcsym = funcscope and funcscope.funcsym | |
local calleesym = node.attr.calleesym | |
local calleetype = node.attr.calleetype | |
if calleesym and not calleesym.noerror and | |
funcsym and not funcsym.noerror and not context.pragmas.noerror then | |
if isblockcall then | |
orig_Call(context, node, emitter, ...) | |
emitter:add_indent('if(nelua_error_status) ') | |
if #funcsym.type.rettypes > 0 then | |
local rettypename = context:funcrettypename(funcsym.type) | |
emitter:add_ln('return (',rettypename,'){0};') | |
else | |
emitter:add_ln('return;') | |
end | |
else | |
emitter:add_ln('({') | |
emitter:inc_indent() | |
local callrettypename = context:funcrettypename(calleetype) | |
emitter:add_indent(callrettypename, ' __callret = ') | |
orig_Call(context, node, emitter, ...) | |
emitter:add_ln(';') | |
emitter:add_indent('if(nelua_error_status) ') | |
if #funcsym.type.rettypes > 0 then | |
local rettypename = context:funcrettypename(funcsym.type) | |
emitter:add_ln('return (',rettypename,'){0};') | |
else | |
emitter:add_ln('return;') | |
end | |
emitter:add_indent_ln('__callret;') | |
emitter:dec_indent() | |
emitter:add_indent('})') | |
end | |
else | |
orig_Call(context, node, emitter, ...) | |
end | |
end | |
]] | |
require 'string' | |
-- TODO: handle defer | |
-- TODO: handle call method | |
local function default_pcall_error_handler(msg: string): string <noerror> | |
-- TODO: add runtime traceback | |
return msg | |
end | |
-- Current raised error message. | |
local nelua_error_msg: string <codename 'nelua_error_msg'> | |
-- Current raised error status, `true` if an error was raised. | |
local nelua_error_status: boolean <codename 'nelua_error_status'> | |
-- Current error handler. | |
local nelua_error_handler: function(string): string <codename 'nelua_error_handler'> | |
--[[ | |
Raises an error with message `msg`. This function never returns. | |
Information about the error position is added at the beginning of the message. | |
Raised errors can be caught with `pcall` or `xpcall`. | |
]] | |
global function error(msg: facultative(string)): void <alwayseval,noerror> | |
--TODO: error level | |
## local locmsg = context.state.inpolyeval.srcnode:format_message('runtime error', 'error!') | |
## if msg.type.is_niltype then | |
local msg: string = string.copy(#[locmsg]#) | |
## else | |
local sb: stringbuilder | |
sb:write(#[locmsg:match('^(.*)error!')]#) | |
sb:write(msg) | |
sb:write(#[locmsg:match('error!(.*)$')]#) | |
msg = sb:promote() | |
## end | |
if nelua_error_handler then | |
local error_handler: auto = nelua_error_handler | |
nelua_error_handler = nilptr | |
nelua_error_status = true | |
nelua_error_msg = error_handler(msg) | |
else | |
panic(msg) | |
end | |
end | |
--[[ | |
Raises an error if the value `v` is evaluated to `false`, otherwise, returns `v`. | |
In case of error, `msg` is the error msg, when absent defaults to `"assertion failed!"`. | |
]] | |
global function assert(v: auto, msg: facultative(string)) <alwayseval,noerror> | |
if unlikely(not v) then | |
## local locmsg = context.state.inpolyeval.srcnode:format_message('runtime assertion', 'assertion failed!') | |
## if msg.type.is_niltype then | |
local msg: string = string.copy(#[locmsg]#) | |
## else | |
local sb: stringbuilder | |
sb:write(#[locmsg:match('^(.*)assertion failed!')]#) | |
sb:write(msg) | |
sb:write(#[locmsg:match('assertion failed!(.*)$')]#) | |
msg = sb:promote() | |
## end | |
if nelua_error_handler then | |
local error_handler: auto = nelua_error_handler | |
nelua_error_handler = nilptr | |
nelua_error_status = true | |
nelua_error_msg = error_handler(msg) | |
else | |
panic(msg) | |
end | |
end | |
## if not v.type.is_niltype then | |
return v | |
## end | |
end | |
--[[ | |
Calls the function `f` with the given arguments in protected mode. | |
This means that any error inside `f` is not propagated, instead, | |
`pcall` catches the error and returns a status code. | |
Its first result is the status, which is `true` if the call succeeds without errors. | |
Its second result is the error message, which is empty if the call succeeds without errors. | |
After the second result, `pcall` returns all results from the call. | |
In case of any error, `pcall` returns `false` plus the error message. | |
]] | |
global function pcall(f: auto <comptime>, ...: varargs) <noerror> | |
local oldmsghandler: auto = nelua_error_handler | |
nelua_error_handler = default_pcall_error_handler | |
local ret: auto = f(...) | |
nelua_error_handler = oldmsghandler | |
if nelua_error_status then | |
local errmsg: string = nelua_error_msg | |
nelua_error_status = false | |
nelua_error_msg = (@string){} | |
return false, errmsg, ret | |
end | |
return true, (@string){}, ret | |
end | |
--[[ | |
Like `pcall`, but it sets a new message handler `msghandler`. | |
In case of runtime errors, this handler will be called with the error message | |
and its return value will be the message returned by `xpcall`. | |
Typically, the message handler is used to add more debug information to the error message, | |
such as a stack traceback. | |
Such information cannot be gathered after the return of `pcall`, | |
since by then the stack has unwound. | |
]] | |
global function xpcall(f: auto <comptime>, msghandler: function(string): string, ...: varargs) | |
assert(msghandler ~= nilptr, 'bad message handler') | |
## pragmapush{noerror=true} | |
local oldmsghandler: auto = nelua_error_handler | |
nelua_error_handler = msghandler | |
local ret: auto = f(...) | |
nelua_error_handler = oldmsghandler | |
## pragmapop() | |
if nelua_error_status then | |
local errmsg: string = nelua_error_msg | |
nelua_error_status = false | |
nelua_error_msg = (@string){} | |
return false, errmsg, ret | |
end | |
return true, (@string){}, ret | |
end | |
-- Function that throws an error. | |
local function f(x: integer) | |
print('f', x) | |
error('f error') | |
return 0 | |
end | |
do -- test `pcall` | |
local ok, err, ret = pcall(f, 1) | |
print(ok, err, ret) | |
end | |
do -- test `xpcall` | |
local function errhandler(msg: string): string | |
print('got error: ', msg) | |
return msg | |
end | |
local ok, err, ret = xpcall(f, errhandler, 1) | |
print(ok, err, ret) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment