mirror of
https://github.com/VCMP-SqMod/SqMod.git
synced 2024-11-08 00:37:15 +01:00
960 lines
36 KiB
C++
960 lines
36 KiB
C++
// ------------------------------------------------------------------------------------------------
|
|
#include "Library/Format.hpp"
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
namespace SqMod {
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Common implementation of formattable script types.
|
|
*/
|
|
struct SqObjectFmt
|
|
{
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Object VM.
|
|
*/
|
|
HSQUIRRELVM mVM{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Script table object.
|
|
*/
|
|
HSQOBJECT mObj{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Base constructor.
|
|
*/
|
|
SqObjectFmt(HSQUIRRELVM vm, SQInteger idx, SQInteger & res)
|
|
: mVM(vm), mObj()
|
|
{
|
|
res = sq_getstackobj(vm, idx, &mObj);
|
|
// Not checking if this is the correct type because it shouldn't get here if it wasn't
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy constructor.
|
|
*/
|
|
SqObjectFmt(const SqObjectFmt & o) = default;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move constructor.
|
|
*/
|
|
SqObjectFmt(SqObjectFmt && o) noexcept = default;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Destructor.
|
|
*/
|
|
~SqObjectFmt() = default;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Copy assignment operator.
|
|
*/
|
|
SqObjectFmt & operator = (const SqObjectFmt & o) = default;
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Move assignment operator.
|
|
*/
|
|
SqObjectFmt & operator = (SqObjectFmt && o) noexcept = default;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for array type.
|
|
*/
|
|
struct SqArrayFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for table type.
|
|
*/
|
|
struct SqTableFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for class type.
|
|
*/
|
|
struct SqClassFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for instance type.
|
|
*/
|
|
struct SqInstanceFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for closure type.
|
|
*/
|
|
struct SqClosureFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for generator type.
|
|
*/
|
|
struct SqGeneratorFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Specialization of SqObjectFmt for weak reference type.
|
|
*/
|
|
struct SqWeakReferenceFmt : public SqObjectFmt
|
|
{
|
|
using SqObjectFmt::SqObjectFmt;
|
|
using SqObjectFmt::operator =;
|
|
};
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
SQInteger FormatContext::Process(HSQUIRRELVM vm, SQInteger text, SQInteger args, SQInteger end)
|
|
{
|
|
// Is there any argument limit?
|
|
if (end < 0) end = sq_gettop(vm);
|
|
// Common value storage
|
|
SQInteger i = 0;
|
|
SQBool b = 0;
|
|
SQFloat f = 0;
|
|
const SQChar * s = nullptr;
|
|
// Attempt to retrieve the string value from the stack
|
|
mRes = sq_getstringandsize(vm, text, &s, &i);
|
|
// By any chance we failed to retrieve string?
|
|
if (SQ_FAILED(mRes))
|
|
{
|
|
return mRes;
|
|
// Drop previous arguments, if any
|
|
} else mArgs.clear();
|
|
// Remember the format string and assume it lives on the stack
|
|
mStr = fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i));
|
|
// Process the arguments in the specified range
|
|
for(SQInteger idx = args; SQ_SUCCEEDED(mRes) && idx <= end; ++idx)
|
|
{
|
|
switch(sq_gettype(vm, idx))
|
|
{
|
|
// Null?
|
|
case OT_NULL: {
|
|
mArgs.push_back(nullptr);
|
|
} break;
|
|
// Integer?
|
|
case OT_INTEGER: {
|
|
mRes = sq_getinteger(vm, idx, &i);
|
|
mArgs.push_back(i);
|
|
} break;
|
|
// Float?
|
|
case OT_FLOAT: {
|
|
mRes = sq_getfloat(vm, idx, &f);
|
|
mArgs.push_back(f);
|
|
} break;
|
|
// Boolean?
|
|
case OT_BOOL: {
|
|
mRes = sq_getbool(vm, idx, &b);
|
|
mArgs.push_back(static_cast< bool >(b));
|
|
} break;
|
|
// String?
|
|
case OT_STRING: {
|
|
mRes = sq_getstringandsize(vm, idx, &s, &i);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
|
|
} break;
|
|
// Table?
|
|
case OT_TABLE: {
|
|
SqTableFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// Array?
|
|
case OT_ARRAY: {
|
|
SqArrayFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// User data?
|
|
case OT_USERDATA: {
|
|
SQUserPointer p = nullptr;
|
|
mRes = sq_getuserdata(vm, idx, &p, nullptr);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(static_cast< void * >(p));
|
|
} break;
|
|
// Closure?
|
|
case OT_CLOSURE:
|
|
case OT_NATIVECLOSURE: {
|
|
SqClosureFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// Generator?
|
|
case OT_GENERATOR: {
|
|
SqGeneratorFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// User pointer?
|
|
case OT_USERPOINTER: {
|
|
SQUserPointer p = nullptr;
|
|
mRes = sq_getuserpointer(vm, idx, &p);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(static_cast< void * >(p));
|
|
} break;
|
|
// Class?
|
|
case OT_CLASS: {
|
|
SqClassFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// Instance?
|
|
case OT_INSTANCE: {
|
|
SqInstanceFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// Weak reference?
|
|
case OT_WEAKREF: {
|
|
SqWeakReferenceFmt o(vm, idx, mRes);
|
|
if (SQ_SUCCEEDED(mRes)) mArgs.push_back(o);
|
|
} break;
|
|
// Unknown!
|
|
default: mRes = sq_throwerror(vm, "Unknown or unsupported object type");
|
|
}
|
|
}
|
|
// Return result
|
|
return mRes;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
template < class FormatContext >
|
|
inline auto FormatObjectInContext(HSQUIRRELVM vm, SQInteger idx, SQInteger src, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Identify what type of value we should format
|
|
switch(sq_gettype(vm, idx))
|
|
{
|
|
// Null?
|
|
case OT_NULL: {
|
|
return fmt::format_to(ctx.out(), "0x0");
|
|
}
|
|
// Integer?
|
|
case OT_INTEGER: {
|
|
SQInteger i = 0;
|
|
sq_getinteger(vm, idx, &i);
|
|
return fmt::format_to(ctx.out(), "{}", i);
|
|
}
|
|
// Float?
|
|
case OT_FLOAT: {
|
|
SQFloat f = 0;
|
|
sq_getfloat(vm, idx, &f);
|
|
return fmt::format_to(ctx.out(), "{}", f);
|
|
}
|
|
// Boolean?
|
|
case OT_BOOL: {
|
|
SQBool b = 0;
|
|
sq_getbool(vm, idx, &b);
|
|
return fmt::format_to(ctx.out(), "{}", b ? "true" : "false");
|
|
}
|
|
// String?
|
|
case OT_STRING: {
|
|
SQInteger i = 0;
|
|
const SQChar * s = nullptr;
|
|
sq_getstringandsize(vm, idx, &s, &i);
|
|
return fmt::format_to(ctx.out(), "{}", fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
|
|
}
|
|
// Table? TODO: generate json-style string!
|
|
case OT_TABLE: throw fmt::format_error("no viable conversion from table to string");
|
|
// Array? TODO: generate json-style string!
|
|
case OT_ARRAY: throw fmt::format_error("no viable conversion from array to string");
|
|
case OT_USERDATA: {
|
|
SQUserPointer p = nullptr;
|
|
sq_getuserdata(vm, idx, &p, nullptr);
|
|
return fmt::format_to(ctx.out(), "{:p}", p);
|
|
}
|
|
// Closure?
|
|
case OT_CLOSURE:
|
|
case OT_NATIVECLOSURE: {
|
|
SQInteger n = 0, x;
|
|
// Retrieve the number of parameters that this closure expects
|
|
if (SQ_SUCCEEDED(sq_getclosureinfo(vm, idx, &n, &x)))
|
|
{
|
|
// Does this closure need any parameters?
|
|
if (n <= 1)
|
|
{
|
|
const SQObjectType st = sq_gettype(vm, src);
|
|
// is the source is a class or instance?
|
|
if (st == OT_CLASS || st == OT_INSTANCE)
|
|
{
|
|
sq_push(vm, src); // Use that as environment
|
|
}
|
|
else
|
|
{
|
|
sq_push(vm, 1); // Use the current environment
|
|
}
|
|
// Attempt to invoke the function
|
|
if (SQ_SUCCEEDED(sq_call(vm, 1, SQTrue, SQFalse)))
|
|
{
|
|
// At this point we have a new object on the stack that must be removed
|
|
SqPopTopGuard tpg(vm);
|
|
// Invoke this function again on the returned value
|
|
return FormatObjectInContext(vm, -1, src, ctx);
|
|
} else throw fmt::format_error(LastErrorString(vm));
|
|
}
|
|
else
|
|
{
|
|
std::string s("closure requires ");
|
|
fmt::format_int f(n);
|
|
s.append(f.data(), f.size());
|
|
s.append(" parameters and cannot be invoked");
|
|
throw fmt::format_error(s);
|
|
}
|
|
} else throw fmt::format_error("failed to retrieve closure information");
|
|
}
|
|
// Generator?
|
|
case OT_GENERATOR: {
|
|
// Guard the stack size
|
|
StackGuard sg(vm);
|
|
// Is the generator at the top of the stack?
|
|
if (idx != -1 && idx != sq_gettop(vm))
|
|
{
|
|
sq_push(vm, idx); // Then push it
|
|
}
|
|
// Keep resuming the generator until null is returned
|
|
while (true)
|
|
{
|
|
if (SQ_SUCCEEDED(sq_resume(vm, SQTrue, SQFalse)))
|
|
{
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard rpg(vm);
|
|
// Is the returned value not null?
|
|
if (sq_gettype(vm, -1) != OT_NULL)
|
|
{
|
|
// Invoke this function again on the returned value
|
|
return FormatObjectInContext(vm, -1, src, ctx);
|
|
} else break; // Stop
|
|
} else throw fmt::format_error(LastErrorString(vm));
|
|
}
|
|
}
|
|
// user pointer?
|
|
case OT_USERPOINTER: {
|
|
SQUserPointer p = nullptr;
|
|
sq_getuserpointer(vm, idx, &p);
|
|
return fmt::format_to(ctx.out(), "{:p}", p);
|
|
}
|
|
// Class?
|
|
case OT_CLASS: {
|
|
// Attempt to retrieve the type name of the specified value
|
|
if (SQ_SUCCEEDED(sq_typeof(vm, idx)))
|
|
{
|
|
// At this point we have a new object on the stack that must be removed
|
|
SqPopTopGuard tpg(vm);
|
|
// The value on the stack may not be a string
|
|
if (SQ_SUCCEEDED(sq_tostring(vm, -1)))
|
|
{
|
|
SQInteger i = 0;
|
|
const SQChar * s = nullptr;
|
|
// At this point we have a new object on the stack that must be removed
|
|
SqPopTopGuard spg(vm);
|
|
// Attempt to obtain the string pointer
|
|
if (SQ_SUCCEEDED(sq_getstringandsize(vm, -1, &s, &i)))
|
|
{
|
|
return fmt::format_to(ctx.out(), "{}", fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
|
|
} else throw fmt::format_error("failed to retrieve type name string from stack");
|
|
} else throw fmt::format_error("failed to convert class type name to string");
|
|
} else throw fmt::format_error("no viable conversion from class to string");
|
|
}
|
|
// Instance?
|
|
case OT_INSTANCE: {
|
|
// Attempt to convert the value from the stack to a string
|
|
if (SQ_SUCCEEDED(sq_tostring(vm, idx)))
|
|
{
|
|
SQInteger i = 0;
|
|
const SQChar * s = nullptr;
|
|
// At this point we have a new object on the stack that must be removed
|
|
SqPopTopGuard spg(vm);
|
|
// Attempt to obtain the string pointer
|
|
if (SQ_SUCCEEDED(sq_getstringandsize(vm, -1, &s, &i)))
|
|
{
|
|
return fmt::format_to(ctx.out(), "{}", fmt::basic_string_view< SQChar >(s, static_cast< size_t >(i)));
|
|
} else throw fmt::format_error("failed to retrieve instance string representation from stack");
|
|
} else throw fmt::format_error("no viable conversion from instance to string");
|
|
}
|
|
// Weak reference?
|
|
case OT_WEAKREF: {
|
|
// Push the referenced object on the stack
|
|
if (SQ_SUCCEEDED(sq_getweakrefval(vm, idx)))
|
|
{
|
|
// At this point we have a new object on the stack that must be removed
|
|
SqPopTopGuard vpg(vm);
|
|
// Invoke this function again on the referenced object
|
|
return FormatObjectInContext(vm, -1, src, ctx);
|
|
} else throw fmt::format_error("failed to retrieve weak reference value");
|
|
}
|
|
// Unknown
|
|
default: break;
|
|
}
|
|
// Unknown type
|
|
throw fmt::format_error("unknown or unsupported value type");
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
void ExtendedFormatProcess(StackStrF & ss, SQInteger top)
|
|
{
|
|
FormatContext ctx;
|
|
// Attempt to perform the format
|
|
ss.mRes = ctx.Proc(ss.mVM, ss.mIdx, ss.mIdx + 1, top);
|
|
// Did format succeed?
|
|
if (SQ_SUCCEEDED(ss.mRes))
|
|
{
|
|
// Transform the string into a script object
|
|
sq_pushstring(ss.mVM, ctx.mOut.data(), static_cast<SQInteger>(ctx.mOut.size()));
|
|
// At this point we have a new object on the stack that must be removed
|
|
SqPopTopGuard spg(ss.mVM);
|
|
// Obtain a reference to the string object
|
|
ss.mRes = sq_getstackobj(ss.mVM, -1, &ss.mObj);
|
|
// Could we retrieve the object from the stack?
|
|
if (SQ_SUCCEEDED(ss.mRes))
|
|
{
|
|
// Keep a strong reference to the object
|
|
sq_addref(ss.mVM, &ss.mObj);
|
|
// Attempt to retrieve the string value from the stack
|
|
ss.mRes = sq_getstringandsize(ss.mVM, -1, &ss.mPtr, &ss.mLen);
|
|
}
|
|
// Did the retrieval succeeded but ended up with a null string pointer?
|
|
if (SQ_SUCCEEDED(ss.mRes) && !ss.mPtr)
|
|
{
|
|
ss.mRes = sq_throwerror(ss.mVM, "Unable to retrieve the string");
|
|
}
|
|
}
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
static SQInteger SqFormat(HSQUIRRELVM vm)
|
|
{
|
|
const SQInteger top = sq_gettop(vm);
|
|
// Make sure we have enough parameters
|
|
if (top < 2)
|
|
{
|
|
return sq_throwerror(vm, "Insufficient parameters");
|
|
}
|
|
|
|
FormatContext ctx;
|
|
// Attempt to generate the formatted string
|
|
if (SQ_FAILED(ctx.Proc(vm, 2, 3, top)) || SQ_FAILED(ctx.Generate(vm)))
|
|
{
|
|
return ctx.mRes;
|
|
}
|
|
// Push it on the stack
|
|
sq_pushstring(vm, ctx.mOut.data(), static_cast< SQInteger >(ctx.mOut.size()));
|
|
// Specify that we returned a value
|
|
return 1;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
static SQInteger SqLocaleFormat(HSQUIRRELVM vm)
|
|
{
|
|
const SQInteger top = sq_gettop(vm);
|
|
// Make sure we have enough parameters
|
|
if (top < 3)
|
|
{
|
|
return sq_throwerror(vm, "Insufficient parameters");
|
|
}
|
|
|
|
StackStrF loc(vm, 2);
|
|
// Attempt to generate the locale string
|
|
if (SQ_FAILED(loc.Proc(false)))
|
|
{
|
|
return loc.mRes;
|
|
}
|
|
FormatContext ctx;
|
|
// Attempt to generate the formatted string
|
|
if (SQ_FAILED(ctx.Proc(vm, 3, 4, top)) || SQ_FAILED(ctx.GenerateLoc(vm, loc.mPtr)))
|
|
{
|
|
return ctx.mRes;
|
|
}
|
|
// Push it on the stack
|
|
sq_pushstring(vm, ctx.mOut.data(), static_cast< SQInteger >(ctx.mOut.size()));
|
|
// Specify that we returned a value
|
|
return 1;
|
|
}
|
|
|
|
// ================================================================================================
|
|
void Register_Format(HSQUIRRELVM vm)
|
|
{
|
|
RootTable(vm).SquirrelFunc(_SC("fmt"), SqFormat);
|
|
RootTable(vm).SquirrelFunc(_SC("locale_fmt"), SqLocaleFormat);
|
|
}
|
|
|
|
} // Namespace:: SqMod
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
namespace fmt {
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script arrays.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqArrayFmt >
|
|
{
|
|
size_t mIndex{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if an index was specified
|
|
else if (it == begin)
|
|
{
|
|
mIndex = 0; // Default to first element
|
|
}
|
|
else
|
|
{
|
|
char * p_end = nullptr;
|
|
// Transform the value into an index
|
|
#ifdef _SQ64
|
|
mIndex = std::strtoull(&(*begin), &p_end, 10);
|
|
#else
|
|
mIndex = std::strtoul(&(*begin), &p_end, 10);
|
|
#endif
|
|
// Validate index range
|
|
if (errno == ERANGE)
|
|
{
|
|
throw format_error("index out of range");
|
|
}
|
|
// Validate index value
|
|
else if (*p_end != '}')
|
|
{
|
|
throw format_error("invalid integer index");
|
|
}
|
|
}
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqArrayFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Remember the stack size
|
|
SQInteger top = sq_gettop(o.mVM);
|
|
// Push the array object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard apg(o.mVM);
|
|
// Push the index on the stack
|
|
sq_pushinteger(o.mVM, static_cast< SQInteger >(mIndex));
|
|
// Attempt to retrieve the index at the specified index
|
|
if (SQ_FAILED(sq_get(o.mVM, -2)))
|
|
{
|
|
std::string s("no such index in array: ");
|
|
fmt::format_int f(mIndex);
|
|
s.append(f.data(), f.size());
|
|
throw format_error(s);
|
|
}
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard epg(o.mVM);
|
|
// Run the format on the object that was pushed on the stack
|
|
return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script tables.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqTableFmt >
|
|
{
|
|
std::string mIndex{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if a name was specified
|
|
else if (it == begin)
|
|
{
|
|
throw format_error("must specify a table index");
|
|
}
|
|
// Store the index name
|
|
mIndex.assign(begin, it);
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqTableFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Remember the stack size
|
|
SQInteger top = sq_gettop(o.mVM);
|
|
// Push the array object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard apg(o.mVM);
|
|
// Push the index on the stack (only works with string keys/elements)
|
|
sq_pushstring(o.mVM, mIndex.data(), static_cast< SQInteger >(mIndex.size()));
|
|
// Attempt to retrieve the index at the specified index
|
|
if (SQ_FAILED(sq_get(o.mVM, -2)))
|
|
{
|
|
std::string s("no such index in table: ");
|
|
s.append(mIndex);
|
|
throw format_error(s);
|
|
}
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard epg(o.mVM);
|
|
// Run the format on the object that was pushed on the stack
|
|
return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script classes.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqClassFmt >
|
|
{
|
|
std::string mIndex{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if a identifier was specified
|
|
else if (it == begin)
|
|
{
|
|
throw format_error("must specify a member identifier");
|
|
}
|
|
// Validate first character
|
|
else if (std::isalpha(*begin) == 0 && *begin != '_')
|
|
{
|
|
throw format_error("member names must start with an alphabetic or underscore character");
|
|
}
|
|
// Validate member identifier
|
|
else if (!std::all_of(begin, it, [](int c) { return std::isalnum(c) != 0 || c == '_'; }))
|
|
{
|
|
throw format_error("member names can only contain alpha-numeric or underscore characters");
|
|
}
|
|
// Store the member identifier
|
|
mIndex.assign(begin, it);
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqClassFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Remember the stack size
|
|
SQInteger top = sq_gettop(o.mVM);
|
|
// Push the class object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard apg(o.mVM);
|
|
// Push the index on the stack (only works with string keys/elements)
|
|
sq_pushstring(o.mVM, mIndex.data(), static_cast< SQInteger >(mIndex.size()));
|
|
// Attempt to retrieve the index at the specified index
|
|
if (SQ_FAILED(sq_get(o.mVM, -2)))
|
|
{
|
|
std::string s("no such identifier in class: ");
|
|
s.append(mIndex);
|
|
throw format_error(s);
|
|
}
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard epg(o.mVM);
|
|
// Run the format on the object that was pushed on the stack
|
|
return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script instances.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqInstanceFmt >
|
|
{
|
|
std::string mIndex{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if a identifier was specified
|
|
else if (it == begin)
|
|
{
|
|
throw format_error("must specify a member identifier");
|
|
}
|
|
// Validate first character
|
|
else if (std::isalpha(*begin) == 0 && *begin != '_')
|
|
{
|
|
throw format_error("member names must start with an alphabetic or underscore character");
|
|
}
|
|
// Validate member identifier
|
|
else if (!std::all_of(begin, it, [](int c) { return std::isalnum(c) != 0 || c == '_'; }))
|
|
{
|
|
throw format_error("member names can only contain alpha-numeric or underscore characters");
|
|
}
|
|
// Store the member identifier
|
|
mIndex.assign(begin, it);
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqInstanceFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Remember the stack size
|
|
SQInteger top = sq_gettop(o.mVM);
|
|
// Push the class object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard apg(o.mVM);
|
|
// Push the index on the stack (only works with string keys/elements)
|
|
sq_pushstring(o.mVM, mIndex.data(), static_cast< SQInteger >(mIndex.size()));
|
|
// Attempt to retrieve the index at the specified index
|
|
if (SQ_FAILED(sq_get(o.mVM, -2)))
|
|
{
|
|
std::string s("no such identifier in instance: ");
|
|
s.append(mIndex);
|
|
throw format_error(s);
|
|
}
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard epg(o.mVM);
|
|
// Run the format on the object that was pushed on the stack
|
|
return ::SqMod::FormatObjectInContext(o.mVM, -1, top + 1, ctx);
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script closures.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqClosureFmt >
|
|
{
|
|
std::string mParameter{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if a parameter was specified
|
|
else if (it != begin)
|
|
{
|
|
mParameter.assign(begin, it); // Store the parameter
|
|
}
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqClosureFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
SQInteger n = 0, x;
|
|
// Push the closure object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard opg(o.mVM);
|
|
// Retrieve the number of parameters that this closure expects
|
|
if (SQ_SUCCEEDED(sq_getclosureinfo(o.mVM, -1, &n, &x)))
|
|
{
|
|
// Does this closure need any parameters?
|
|
if (n <= 2)
|
|
{
|
|
// Use the current environment
|
|
sq_push(o.mVM, 1);
|
|
// If it requires a parameter then we push the string anyway
|
|
if (n == 2 || !mParameter.empty())
|
|
{
|
|
sq_pushstring(o.mVM, mParameter.data(), static_cast< SQInteger >(mParameter.size()));
|
|
x = 2; // Include string in parameter count
|
|
} else x = 1; // Exclude string from parameter count
|
|
// Attempt to invoke the function
|
|
if (SQ_SUCCEEDED(sq_call(o.mVM, x, SQTrue, SQFalse)))
|
|
{
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard tpg(o.mVM);
|
|
// Invoke this function again on the returned value
|
|
::SqMod::FormatObjectInContext(o.mVM, -1, 1, ctx);
|
|
} else throw fmt::format_error(::SqMod::LastErrorString(o.mVM));
|
|
}
|
|
else
|
|
{
|
|
std::string s("closure requires ");
|
|
fmt::format_int f(n);
|
|
s.append(f.data(), f.size());
|
|
s.append(" parameters and cannot be invoked");
|
|
throw fmt::format_error(s);
|
|
}
|
|
} else throw fmt::format_error("failed to retrieve closure information");
|
|
// Return the output iterator
|
|
return ctx.out();
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script generators.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqGeneratorFmt >
|
|
{
|
|
std::string mDelimiter{};
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin())
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if a delimiter was specified
|
|
else if (it != begin)
|
|
{
|
|
mDelimiter.assign(begin, it); // Store the delimiter
|
|
}
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqGeneratorFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Push the generator object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard opg(o.mVM);
|
|
// Keep resuming the generator until null is returned
|
|
for (SQInteger n = 0; /*...*/ ; ++n)
|
|
{
|
|
if (SQ_SUCCEEDED(sq_resume(o.mVM, SQTrue, SQFalse)))
|
|
{
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard rpg(o.mVM);
|
|
// Is the returned value not null?
|
|
if (sq_gettype(o.mVM, -1) != OT_NULL)
|
|
{
|
|
// Do not include the delimiter before the first or after last value
|
|
if (n != 0) format_to(ctx.out(), "{:s}", mDelimiter);
|
|
// Run the format on the object that was pushed on the stack
|
|
::SqMod::FormatObjectInContext(o.mVM, -1, 1, ctx);
|
|
} else break; // Stop
|
|
} else throw format_error(::SqMod::LastErrorString(o.mVM));
|
|
}
|
|
// Return the output iterator
|
|
return ctx.out();
|
|
}
|
|
};
|
|
|
|
/* ------------------------------------------------------------------------------------------------
|
|
* Implements formatting for script generators.
|
|
*/
|
|
template < > struct formatter< ::SqMod::SqWeakReferenceFmt >
|
|
{
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Parse the specified string range.
|
|
*/
|
|
auto parse(format_parse_context & ctx) -> decltype(ctx.begin()) // NOLINT(readability-convert-member-functions-to-static)
|
|
{
|
|
// Retrieve the range that to be parsed
|
|
auto it = ctx.begin(), begin = it, end = ctx.end();
|
|
// Find the closing brace
|
|
while (it != end && *it != '}') ++it;
|
|
// Check if closing brace was found
|
|
if (it != end && *it != '}')
|
|
{
|
|
throw format_error("missing curly brace }");
|
|
}
|
|
// Check if a delimiter was specified
|
|
else if (it != begin)
|
|
{
|
|
throw format_error("weak references don't accept format specifiers");
|
|
}
|
|
// Return an iterator past the end of the parsed range:
|
|
return it;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------------------------------
|
|
* Perform the requested format.
|
|
*/
|
|
template < class FormatContext >
|
|
auto format(const ::SqMod::SqWeakReferenceFmt & o, FormatContext & ctx) -> decltype(ctx.out())
|
|
{
|
|
// Push the reference object on the stack
|
|
sq_pushobject(o.mVM, o.mObj);
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard opg(o.mVM);
|
|
// Push the referenced object on the stack
|
|
if (SQ_FAILED(sq_getweakrefval(o.mVM, -1)))
|
|
{
|
|
throw fmt::format_error("failed to retrieve weak reference value");
|
|
}
|
|
// At this point we have a new object on the stack that must be removed
|
|
::SqMod::SqPopTopGuard vpg(o.mVM);
|
|
// Run the format on the object that was pushed on the stack
|
|
return ::SqMod::FormatObjectInContext(o.mVM, -1, 1, ctx);
|
|
}
|
|
};
|
|
|
|
} // Namespace:: fmt
|