mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-23 20:32:51 +00:00
Implemented format() builtin call
This commit is contained in:
parent
4be0767d7b
commit
e75aa08d0a
7 changed files with 267 additions and 3 deletions
|
@ -349,6 +349,7 @@ xx(VisibleStartAngle)
|
|||
xx(VisibleStartPitch)
|
||||
xx(VisibleEndAngle)
|
||||
xx(VisibleEndPitch)
|
||||
xx(Format)
|
||||
|
||||
// Various actor names which are used internally
|
||||
xx(MapSpot)
|
||||
|
|
|
@ -7115,6 +7115,14 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
|
|||
}
|
||||
}
|
||||
|
||||
// [ZZ] string formatting function
|
||||
if (MethodName == NAME_Format)
|
||||
{
|
||||
FxExpression *x = new FxFormat(ArgList, ScriptPosition);
|
||||
delete this;
|
||||
return x->Resolve(ctx);
|
||||
}
|
||||
|
||||
int min, max, special;
|
||||
if (MethodName == NAME_ACS_NamedExecuteWithResult || MethodName == NAME_CallACS)
|
||||
{
|
||||
|
@ -8341,6 +8349,85 @@ ExpEmit FxFlopFunctionCall::Emit(VMFunctionBuilder *build)
|
|||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxFormat::FxFormat(FArgumentList &args, const FScriptPosition &pos)
|
||||
: FxExpression(EFX_FlopFunctionCall, pos)
|
||||
{
|
||||
ArgList = std::move(args);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxFormat::~FxFormat()
|
||||
{
|
||||
}
|
||||
|
||||
FxExpression *FxFormat::Resolve(FCompileContext& ctx)
|
||||
{
|
||||
CHECKRESOLVED();
|
||||
|
||||
for (int i = 0; i < ArgList.Size(); i++)
|
||||
{
|
||||
ArgList[i] = ArgList[i]->Resolve(ctx);
|
||||
if (ArgList[i] == nullptr)
|
||||
{
|
||||
delete this;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// first argument should be a string
|
||||
if (!i && ArgList[i]->ValueType != TypeString)
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "String was expected for format");
|
||||
delete this;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ArgList[i]->ValueType == TypeName ||
|
||||
ArgList[i]->ValueType == TypeSound)
|
||||
{
|
||||
FxExpression* x = new FxStringCast(ArgList[i]);
|
||||
x = x->Resolve(ctx);
|
||||
if (x == nullptr)
|
||||
{
|
||||
delete this;
|
||||
return nullptr;
|
||||
}
|
||||
ArgList[i] = x;
|
||||
}
|
||||
}
|
||||
|
||||
ValueType = TypeString;
|
||||
return this;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ExpEmit FxFormat::Emit(VMFunctionBuilder *build)
|
||||
{
|
||||
ExpEmit to = ExpEmit(build, REGT_STRING);
|
||||
for (int i = 0; i < ArgList.Size(); i++)
|
||||
{
|
||||
EmitParameter(build, ArgList[i], ScriptPosition);
|
||||
}
|
||||
|
||||
build->Emit(OP_STRFMT, to.RegNum, ArgList.Size(), 0);
|
||||
return to;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
|
|
@ -255,6 +255,7 @@ enum EFxType
|
|||
EFX_MemberFunctionCall,
|
||||
EFX_ActionSpecialCall,
|
||||
EFX_FlopFunctionCall,
|
||||
EFX_FormatFunctionCall,
|
||||
EFX_VMFunctionCall,
|
||||
EFX_Sequence,
|
||||
EFX_CompoundStatement,
|
||||
|
@ -1532,7 +1533,25 @@ public:
|
|||
|
||||
//==========================================================================
|
||||
//
|
||||
// FxFlopFunctionCall
|
||||
// FxFormatFunctionCall
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class FxFormat : public FxExpression
|
||||
{
|
||||
FArgumentList ArgList;
|
||||
|
||||
public:
|
||||
|
||||
FxFormat(FArgumentList &args, const FScriptPosition &pos);
|
||||
~FxFormat();
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
ExpEmit Emit(VMFunctionBuilder *build);
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FxVectorBuiltin
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
@ -1551,7 +1570,7 @@ public:
|
|||
|
||||
//==========================================================================
|
||||
//
|
||||
// FxFlopFunctionCall
|
||||
// FxGetClass
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
@ -1569,7 +1588,7 @@ public:
|
|||
|
||||
//==========================================================================
|
||||
//
|
||||
// FxFlopFunctionCall
|
||||
// FxGetDefaultByType
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
|
|
|
@ -199,6 +199,7 @@ enum EVMAbortException
|
|||
X_ARRAY_OUT_OF_BOUNDS,
|
||||
X_DIVISION_BY_ZERO,
|
||||
X_BAD_SELF,
|
||||
X_FORMAT_ERROR
|
||||
};
|
||||
|
||||
class CVMAbortException : public CDoomError
|
||||
|
|
|
@ -862,6 +862,157 @@ begin:
|
|||
}
|
||||
NEXTOP;
|
||||
|
||||
OP(STRFMT) :
|
||||
{
|
||||
ASSERTS(a);
|
||||
assert(B <= f->NumParam);
|
||||
int countparam = B;
|
||||
assert(countparam >= 1);
|
||||
VMValue* args = reg.param + f->NumParam - B;
|
||||
assert(args[0].Type == REGT_STRING);
|
||||
FString fmtstring = args[0].s().GetChars();
|
||||
|
||||
// note: we don't need a real printf format parser.
|
||||
// enough to simply find the subtitution tokens and feed them to the real printf after checking types.
|
||||
// https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification
|
||||
FString output;
|
||||
bool in_fmt = false;
|
||||
FString fmt_current;
|
||||
int argnum = 1;
|
||||
int argauto = 1;
|
||||
// % = starts
|
||||
// [0-9], -, +, \s, 0, #, . continue
|
||||
// %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate
|
||||
// various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM.
|
||||
// the only combination that is parsed locally is %n$...
|
||||
bool haveargnums = false;
|
||||
for (int i = 0; i < fmtstring.Len(); i++)
|
||||
{
|
||||
char c = fmtstring[i];
|
||||
if (in_fmt)
|
||||
{
|
||||
if ((c >= '0' && c <= '9') ||
|
||||
c == '-' || c == '+' || (c == ' ' && fmt_current[fmt_current.Len() - 1] != ' ') || c == '#' || c == '.')
|
||||
{
|
||||
fmt_current += c;
|
||||
}
|
||||
else if (c == '$') // %number$format
|
||||
{
|
||||
if (!haveargnums && argauto > 1)
|
||||
ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
|
||||
FString argnumstr = fmt_current.Mid(1);
|
||||
if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars());
|
||||
argnum = argnumstr.ToLong();
|
||||
if (argnum < 1 || argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, countparam);
|
||||
fmt_current = "%";
|
||||
haveargnums = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt_current += c;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
// string and char formats
|
||||
case 's':
|
||||
{
|
||||
if (argnum < 0 && haveargnums)
|
||||
ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
|
||||
in_fmt = false;
|
||||
// fail if something was found, but it's not a string
|
||||
if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
|
||||
if (args[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars());
|
||||
// append
|
||||
output.AppendFormat(fmt_current.GetChars(), args[argnum].s().GetChars());
|
||||
if (!haveargnums) argnum = ++argauto;
|
||||
else argnum = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// int formats
|
||||
case 'd':
|
||||
case 'i':
|
||||
case 'u':
|
||||
case 'x':
|
||||
case 'X':
|
||||
case 'o':
|
||||
{
|
||||
if (argnum < 0 && haveargnums)
|
||||
ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
|
||||
in_fmt = false;
|
||||
// fail if something was found, but it's not an int
|
||||
if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
|
||||
if (args[argnum].Type != REGT_INT &&
|
||||
args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
|
||||
// append
|
||||
output.AppendFormat(fmt_current.GetChars(), args[argnum].ToInt());
|
||||
if (!haveargnums) argnum = ++argauto;
|
||||
else argnum = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// double formats
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'g':
|
||||
case 'G':
|
||||
case 'a':
|
||||
case 'A':
|
||||
{
|
||||
if (argnum < 0 && haveargnums)
|
||||
ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
|
||||
in_fmt = false;
|
||||
// fail if something was found, but it's not a float
|
||||
if (argnum >= countparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
|
||||
if (args[argnum].Type != REGT_INT &&
|
||||
args[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
|
||||
// append
|
||||
output.AppendFormat(fmt_current.GetChars(), args[argnum].ToDouble());
|
||||
if (!haveargnums) argnum = ++argauto;
|
||||
else argnum = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// invalid character
|
||||
output += fmt_current;
|
||||
in_fmt = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (c == '%')
|
||||
{
|
||||
if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%')
|
||||
{
|
||||
output += '%';
|
||||
i++;
|
||||
}
|
||||
else
|
||||
{
|
||||
in_fmt = true;
|
||||
fmt_current = "%";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
output += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finalize parameters
|
||||
for (b = B; b != 0; --b)
|
||||
reg.param[--f->NumParam].~VMValue();
|
||||
|
||||
reg.s[a] = output;
|
||||
}
|
||||
NEXTOP;
|
||||
|
||||
OP(SLL_RR):
|
||||
ASSERTD(a); ASSERTD(B); ASSERTD(C);
|
||||
reg.d[a] = reg.d[B] << reg.d[C];
|
||||
|
|
|
@ -549,6 +549,10 @@ CVMAbortException::CVMAbortException(EVMAbortException reason, const char *morei
|
|||
AppendMessage("invalid self pointer.");
|
||||
break;
|
||||
|
||||
case X_FORMAT_ERROR:
|
||||
AppendMessage("string format failed.");
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
size_t len = strlen(m_Message);
|
||||
|
|
|
@ -120,6 +120,7 @@ xx(BOUND_R, bound, RIRI, NOP, 0, 0), // if rA >= rB, throw exception
|
|||
xx(CONCAT, concat, RSRSRS, NOP, 0, 0), // sA = sB..sC
|
||||
xx(LENS, lens, RIRS, NOP, 0, 0), // dA = sB.Length
|
||||
xx(CMPS, cmps, I8RXRX, NOP, 0, 0), // if ((skB op skC) != (A & 1)) then pc++
|
||||
xx(STRFMT, strfmt, RIRIRI, NOP, 0, 0),
|
||||
|
||||
// Integer math.
|
||||
xx(SLL_RR, sll, RIRIRI, NOP, 0, 0), // dA = dkB << diC
|
||||
|
|
Loading…
Reference in a new issue