2017-10-28 10:25:10 +00:00
|
|
|
|
|
|
|
local lexer = require 'lua_lang'
|
|
|
|
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
local function get_assert_body_text (call_info)
|
|
|
|
if call_info.what == 'Lua' then
|
|
|
|
-- Find filetext
|
|
|
|
local filetext = nil
|
|
|
|
if call_info.source:find '^@' then
|
|
|
|
local f = io.open(call_info.short_src, 'r')
|
|
|
|
filetext = f:read '*all'
|
|
|
|
f:close()
|
|
|
|
elseif call_info.short_src:find '^%[string' then
|
|
|
|
filetext = call_info.source
|
|
|
|
else
|
|
|
|
error 'Not implemented yet!'
|
|
|
|
end
|
|
|
|
-- Get lines
|
|
|
|
local filetext = filetext .. '\n'
|
|
|
|
local lines_after, line_i = {}, 0
|
|
|
|
for line in filetext:gmatch '([^\r\n]*)[\r\n]' do
|
|
|
|
line_i = line_i + 1
|
|
|
|
if call_info.currentline == line_i then
|
|
|
|
lines_after[#lines_after+1] = line
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-- Find body exclusively.
|
|
|
|
return table.concat(lines_after, '\n'):match('assert%s*(%b())'):sub(2, -2)
|
|
|
|
end
|
|
|
|
|
|
|
|
error 'Not implemented yet!'
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_assert_body (call_info)
|
|
|
|
local text = get_assert_body_text(call_info)
|
|
|
|
return lexer:lex(text), text
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_variable (var_name, level)
|
|
|
|
-- Local
|
|
|
|
local index = 0
|
|
|
|
repeat
|
|
|
|
index = index + 1
|
|
|
|
local name, val = debug.getlocal(level + 1, index)
|
|
|
|
if name == var_name then
|
|
|
|
local info = debug.getinfo(level + 1)
|
|
|
|
local is_par = index <= info.nparams
|
|
|
|
return val, is_par and ('argument #'..index) or 'local', info.name or is_par and ''
|
|
|
|
end
|
|
|
|
until not name
|
|
|
|
|
|
|
|
-- Up-value
|
|
|
|
local index, func = 0, debug.getinfo(level + 1).func
|
|
|
|
repeat
|
|
|
|
index = index + 1
|
|
|
|
local name, val = debug.getupvalue(func, index)
|
|
|
|
if name == var_name then return val, 'upvalue' end
|
|
|
|
until not name
|
|
|
|
|
|
|
|
-- Global
|
|
|
|
return getfenv(level + 1)[var_name], 'global'
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_value_of_string (string_str)
|
|
|
|
if string_str:sub(1, 1) == '"' or string_str:sub(1, 1) == '\'' then
|
|
|
|
return string_str:sub(2, -2)
|
|
|
|
end
|
|
|
|
assert(false)
|
|
|
|
end
|
|
|
|
|
2017-10-28 11:15:57 +00:00
|
|
|
local function fmt_val (val)
|
|
|
|
if type(val) == 'string' then
|
|
|
|
return string.format('%q', val)
|
|
|
|
else
|
|
|
|
return tostring(val)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function get_variable_and_prefix (gotten_name, level)
|
|
|
|
assert(type(gotten_name) == 'string')
|
|
|
|
assert(type(level) == 'number')
|
|
|
|
--
|
|
|
|
local gotten_val, var_scope, in_func = get_variable(gotten_name, level + 1)
|
|
|
|
local func_name = (in_func == '' and ' to anonymous function') or in_func and (' to \''..in_func..'\'') or ''
|
|
|
|
return gotten_val, ('assertion failed! bad %s \'%s\'%s'):format(var_scope, gotten_name, func_name)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
local CONSTANT_VALUE_TOKEN = {
|
|
|
|
NUMBER = tonumber,
|
|
|
|
STRING = get_value_of_string,
|
|
|
|
TRUE = function() return true end,
|
|
|
|
FALSE = function() return false end,
|
|
|
|
NIL = function() return nil end
|
|
|
|
}
|
|
|
|
|
|
|
|
local PRIMITIVE_VALUES = {
|
|
|
|
['nil'] = true,
|
|
|
|
['boolean'] = true,
|
|
|
|
}
|
|
|
|
|
|
|
|
local COMPLEX_TYPES = {
|
|
|
|
['table'] = true,
|
|
|
|
['userdata'] = true,
|
|
|
|
['cdata'] = true,
|
|
|
|
}
|
|
|
|
|
|
|
|
local function get_value_of_const_token (token)
|
|
|
|
assert(CONSTANT_VALUE_TOKEN[token.token])
|
|
|
|
return CONSTANT_VALUE_TOKEN[token.token](token.text)
|
|
|
|
end
|
|
|
|
|
|
|
|
local function fmt_with_type (val)
|
|
|
|
-- Primitive values ARE their type, and don't need the annotation.
|
|
|
|
if PRIMITIVE_VALUES[type(val)] then return tostring(val) end
|
|
|
|
-- Complex types are already formatted with some type information.
|
|
|
|
if COMPLEX_TYPES[type(val)] then return tostring(val) end
|
|
|
|
-- Numbers and string should have their types with them.
|
|
|
|
return type(val) .. ' ' .. fmt_val(val)
|
|
|
|
end
|
|
|
|
|
2017-10-28 10:25:10 +00:00
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
return function (condition)
|
|
|
|
if condition then return condition end
|
|
|
|
local call_info = debug.getinfo(2)
|
|
|
|
local tokens, body_text = get_assert_body(call_info)
|
|
|
|
|
2017-10-28 11:15:57 +00:00
|
|
|
if #tokens == 6 and tokens[1].text == 'type' and tokens[2].token == 'LPAR' and tokens[3].token == 'IDENTIFIER' and tokens[4].token == 'RPAR'and tokens[5].token == 'EQ'and tokens[6].token == 'STRING' then
|
|
|
|
local gotten_val, prefix = get_variable_and_prefix(tokens[3].text, 2)
|
|
|
|
error(('%s (%s expected, but got %s: %s)'):format(prefix, get_value_of_string(tokens[6].text), type(gotten_val), fmt_val(gotten_val)), 2)
|
|
|
|
elseif #tokens == 3 and tokens[1].token == 'IDENTIFIER' and tokens[2].token == 'EQ' and CONSTANT_VALUE_TOKEN[tokens[3].token] then
|
|
|
|
local gotten_val, prefix = get_variable_and_prefix(tokens[1].text, 2)
|
|
|
|
|
|
|
|
local expected_value = get_value_of_const_token(tokens[3])
|
|
|
|
local type_annotation = (type(expected_value) == type(gotten_val)) and '' or (' '..type(gotten_val))
|
|
|
|
|
|
|
|
error(('%s (%s expected, but got%s: %s)'):format(prefix, fmt_with_type(expected_value), type_annotation, fmt_val(gotten_val)), 2)
|
|
|
|
elseif #tokens == 3 and tokens[1].token == 'IDENTIFIER' and tokens[2].token == 'NEQ' and CONSTANT_VALUE_TOKEN[tokens[3].token] then
|
|
|
|
local gotten_val, prefix = get_variable_and_prefix(tokens[1].text, 2)
|
|
|
|
|
|
|
|
local expected_value = get_value_of_const_token(tokens[3])
|
|
|
|
|
|
|
|
error(('%s (expected anything other than %s, but got %s)'):format(prefix, fmt_with_type(expected_value), fmt_val(gotten_val)), 2)
|
|
|
|
elseif #tokens == 1 and tokens[1].token == 'IDENTIFIER' then
|
|
|
|
local gotten_val, prefix = get_variable_and_prefix(tokens[1].text, 2)
|
|
|
|
error(('%s (truthy expected, but got %s)'):format(prefix, fmt_val(gotten_val)), 2)
|
2017-10-28 10:25:10 +00:00
|
|
|
else
|
2017-10-28 11:15:57 +00:00
|
|
|
print(require'pretty'(tokens))
|
2017-10-28 10:25:10 +00:00
|
|
|
error(('assertion failed! expression `%s` evaluated to %s'):format(body_text, condition), 2)
|
|
|
|
end
|
|
|
|
end
|