Skip to content

Instantly share code, notes, and snippets.

@edubart
Last active July 13, 2021 13:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save edubart/6e87181693e71c6e9603fa2aaf875170 to your computer and use it in GitHub Desktop.
Save edubart/6e87181693e71c6e9603fa2aaf875170 to your computer and use it in GitHub Desktop.
Experimental pcall implementation for Nelua
##[[
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