Implemented static methods in String struct. Implemented String.Format and String.AppendFormat. Implemented native vararg methods for the future.

This commit is contained in:
ZZYZX 2017-01-21 02:07:44 +02:00 committed by Christoph Oelckers
parent da3da61b67
commit 6f5fff00a0
14 changed files with 213 additions and 312 deletions

View file

@ -35,6 +35,7 @@ enum
VARF_Ref = (1<<16), // argument is passed by reference.
VARF_Transient = (1<<17), // don't auto serialize field.
VARF_Meta = (1<<18), // static class data (by necessity read only.)
VARF_VarArg = (1<<19), // [ZZ] vararg: don't typecheck values after ... in function signature
};
// Symbol information -------------------------------------------------------

View file

@ -171,6 +171,7 @@ std2:
'export' { RET(TK_Export); }
'virtual' { RET(TK_Virtual); }
'override' { RET(TK_Override); }
'vararg' { RET(TK_VarArg); }
'super' { RET(TK_Super); }
'global' { RET(TK_Global); }
'stop' { RET(TK_Stop); }

View file

@ -112,6 +112,7 @@ xx(TK_Iterator, "'iterator'")
xx(TK_Optional, "'optional'")
xx(TK_Export, "'expert'")
xx(TK_Virtual, "'virtual'")
xx(TK_VarArg, "'vararg'")
xx(TK_Override, "'override'")
xx(TK_Super, "'super'")
xx(TK_Null, "'null'")

View file

@ -7188,14 +7188,6 @@ 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)
{
@ -7466,6 +7458,9 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx)
{
if (ccls != nullptr)
{
// [ZZ] substitute ccls for String internal type.
if (ccls->TypeName == NAME_String)
ccls = TypeStringStruct;
if (!ccls->IsKindOf(RUNTIME_CLASS(PClass)) || static_cast<PClass *>(ccls)->bExported)
{
cls = ccls;
@ -8056,6 +8051,7 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
for (unsigned i = 0; i < ArgList.Size(); i++)
{
// Varargs must all have the same type as the last typed argument. A_Jump is the only function using it.
// [ZZ] Varargs MAY have arbitrary types if the method is marked vararg.
if (!foundvarargs)
{
if (argtypes[i + implicit] == nullptr) foundvarargs = true;
@ -8129,7 +8125,20 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
}
FxExpression *x;
if (!(flag & (VARF_Ref|VARF_Out)))
if (foundvarargs && (Function->Variants[0].Flags & VARF_VarArg))
{
// only cast implicit-string types for vararg, leave everything else as-is
// this was outright copypasted from FxFormat
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i]->ValueType == TypeName ||
ArgList[i]->ValueType == TypeSound)
{
FxExpression* x = new FxStringCast(ArgList[i]);
x = x->Resolve(ctx);
}
else x = ArgList[i];
}
else if (!(flag & (VARF_Ref|VARF_Out)))
{
x = new FxTypeCast(ArgList[i], type, false);
x = x->Resolve(ctx);
@ -8475,280 +8484,6 @@ ExpEmit FxFlopFunctionCall::Emit(VMFunctionBuilder *build)
return to;
}
//==========================================================================
//
//
//
//==========================================================================
FxFormat::FxFormat(FArgumentList &args, const FScriptPosition &pos)
: FxExpression(EFX_Format, pos)
{
EmitTail = false;
ArgList = std::move(args);
}
//==========================================================================
//
//
//
//==========================================================================
FxFormat::~FxFormat()
{
}
//==========================================================================
//
//
//
//==========================================================================
PPrototype *FxFormat::ReturnProto()
{
EmitTail = true;
return FxExpression::ReturnProto();
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxFormat::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
for (unsigned 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;
}
//==========================================================================
//
//
//==========================================================================
static int BuiltinFormat(VMValue *args, TArray<VMValue> &defaultparam, int numparam, VMReturn *ret, int numret)
{
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 (size_t 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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, numparam);
fmt_current = "%";
haveargnums = true;
}
else
{
fmt_current += c;
switch (c)
{
// string
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 >= numparam) 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;
}
// pointer
case 'p':
{
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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
if (args[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars());
// append
output.AppendFormat(fmt_current.GetChars(), args[argnum].a);
if (!haveargnums) argnum = ++argauto;
else argnum = -1;
break;
}
// int formats (including char)
case 'd':
case 'i':
case 'u':
case 'x':
case 'X':
case 'o':
case 'c':
{
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 >= numparam) 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 >= numparam) 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;
}
}
}
ACTION_RETURN_STRING(output);
}
ExpEmit FxFormat::Emit(VMFunctionBuilder *build)
{
// Call DecoRandom to generate a random number.
VMFunction *callfunc;
PSymbol *sym = FindBuiltinFunction(NAME_BuiltinFormat, BuiltinFormat);
assert(sym->IsKindOf(RUNTIME_CLASS(PSymbolVMFunction)));
assert(((PSymbolVMFunction *)sym)->Function != nullptr);
callfunc = ((PSymbolVMFunction *)sym)->Function;
if (build->FramePointer.Fixed) EmitTail = false; // do not tail call if the stack is in use
int opcode = (EmitTail ? OP_TAIL_K : OP_CALL_K);
for (unsigned i = 0; i < ArgList.Size(); i++)
EmitParameter(build, ArgList[i], ScriptPosition);
build->Emit(opcode, build->GetConstantAddress(callfunc, ATAG_OBJECT), ArgList.Size(), 1);
if (EmitTail)
{
ExpEmit call;
call.Final = true;
return call;
}
ExpEmit out(build, REGT_STRING);
build->Emit(OP_RESULT, 0, REGT_STRING, out.RegNum);
return out;
}
//==========================================================================
//
//

View file

@ -255,7 +255,6 @@ enum EFxType
EFX_MemberFunctionCall,
EFX_ActionSpecialCall,
EFX_FlopFunctionCall,
EFX_Format,
EFX_VMFunctionCall,
EFX_Sequence,
EFX_CompoundStatement,
@ -1534,26 +1533,6 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxFormat
//
//==========================================================================
class FxFormat : public FxExpression
{
FArgumentList ArgList;
bool EmitTail;
public:
FxFormat(FArgumentList &args, const FScriptPosition &pos);
~FxFormat();
FxExpression *Resolve(FCompileContext&);
PPrototype *ReturnProto();
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxVectorBuiltin

View file

@ -945,3 +945,176 @@ DEFINE_ACTION_FUNCTION(FString, Replace)
return 0;
}
static FString FStringFormat(VM_ARGS)
{
assert(param[0].Type == REGT_STRING);
FString fmtstring = param[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 (size_t 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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum, numparam);
fmt_current = "%";
haveargnums = true;
}
else
{
fmt_current += c;
switch (c)
{
// string
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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
if (param[argnum].Type != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars());
// append
output.AppendFormat(fmt_current.GetChars(), param[argnum].s().GetChars());
if (!haveargnums) argnum = ++argauto;
else argnum = -1;
break;
}
// pointer
case 'p':
{
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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
if (param[argnum].Type != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars());
// append
output.AppendFormat(fmt_current.GetChars(), param[argnum].a);
if (!haveargnums) argnum = ++argauto;
else argnum = -1;
break;
}
// int formats (including char)
case 'd':
case 'i':
case 'u':
case 'x':
case 'X':
case 'o':
case 'c':
{
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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
if (param[argnum].Type != REGT_INT &&
param[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
// append
output.AppendFormat(fmt_current.GetChars(), param[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 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
if (param[argnum].Type != REGT_INT &&
param[argnum].Type != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
// append
output.AppendFormat(fmt_current.GetChars(), param[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;
}
}
}
return output;
}
DEFINE_ACTION_FUNCTION(FString, Format)
{
PARAM_PROLOGUE;
FString s = FStringFormat(param, defaultparam, numparam, ret, numret);
ACTION_RETURN_STRING(s);
}
DEFINE_ACTION_FUNCTION(FString, AppendFormat)
{
PARAM_SELF_STRUCT_PROLOGUE(FString);
// first parameter is the self pointer
FString s = FStringFormat(param+1, defaultparam, numparam-1, ret, numret);
(*self) += s;
return 0;
}

View file

@ -722,7 +722,7 @@ type_name1(X) ::= BOOL(T). { X.Int = ZCC_Bool; X.SourceLoc = T.SourceLoc; }
type_name1(X) ::= int_type(X).
type_name1(X) ::= FLOAT(T). { X.Int = ZCC_FloatAuto; X.SourceLoc = T.SourceLoc; }
type_name1(X) ::= DOUBLE(T). { X.Int = ZCC_Float64; X.SourceLoc = T.SourceLoc; }
type_name1(X) ::= STRING(T). { X.Int = ZCC_String; X.SourceLoc = T.SourceLoc; }
//type_name1(X) ::= STRING(T). { X.Int = ZCC_String; X.SourceLoc = T.SourceLoc; } // [ZZ] it's handled elsewhere. this particular line only causes troubles in the form of String.Format being invalid.
type_name1(X) ::= VECTOR2(T). { X.Int = ZCC_Vector2; X.SourceLoc = T.SourceLoc; }
type_name1(X) ::= VECTOR3(T). { X.Int = ZCC_Vector3; X.SourceLoc = T.SourceLoc; }
type_name1(X) ::= NAME(T). { X.Int = ZCC_Name; X.SourceLoc = T.SourceLoc; }
@ -994,6 +994,7 @@ decl_flag(X) ::= READONLY(T). { X.Int = ZCC_ReadOnly; X.SourceLoc = T.SourceLoc
decl_flag(X) ::= DEPRECATED(T). { X.Int = ZCC_Deprecated; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= VIRTUAL(T). { X.Int = ZCC_Virtual; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= OVERRIDE(T). { X.Int = ZCC_Override; X.SourceLoc = T.SourceLoc; }
decl_flag(X) ::= VARARG(T). { X.Int = ZCC_VarArg; X.SourceLoc = T.SourceLoc; }
func_const(X) ::= . { X.Int = 0; X.SourceLoc = stat->sc->GetMessageLine(); }
func_const(X) ::= CONST(T). { X.Int = ZCC_FuncConst; X.SourceLoc = T.SourceLoc; }

View file

@ -1423,10 +1423,11 @@ bool ZCCCompiler::CompileProperties(PClass *type, TArray<ZCC_Property *> &Proper
FString ZCCCompiler::FlagsToString(uint32_t flags)
{
const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "funcconst", "abstract" };
const char *flagnames[] = { "native", "static", "private", "protected", "latent", "final", "meta", "action", "deprecated", "readonly", "funcconst", "abstract", "extension", "virtual", "override", "transient", "vararg" };
FString build;
for (int i = 0; i < 12; i++)
for (int i = 0; i < countof(flagnames); i++)
{
if (flags & (1 << i))
{
@ -2287,6 +2288,11 @@ void ZCCCompiler::CompileFunction(ZCC_StructWork *c, ZCC_FuncDeclarator *f, bool
if (f->Flags & ZCC_Deprecated) varflags |= VARF_Deprecated;
if (f->Flags & ZCC_Virtual) varflags |= VARF_Virtual;
if (f->Flags & ZCC_Override) varflags |= VARF_Override;
if (f->Flags & ZCC_VarArg) varflags |= VARF_VarArg;
if ((f->Flags & ZCC_VarArg) && !(f->Flags & ZCC_Native))
{
Error(f, "'VarArg' can only be used with native methods");
}
if (f->Flags & ZCC_Action)
{
// Non-Actors cannot have action functions.

View file

@ -136,6 +136,7 @@ static void InitTokenMap()
TOKENDEF (TK_Protected, ZCC_PROTECTED);
TOKENDEF (TK_Latent, ZCC_LATENT);
TOKENDEF (TK_Virtual, ZCC_VIRTUAL);
TOKENDEF (TK_VarArg, ZCC_VARARG);
TOKENDEF (TK_Override, ZCC_OVERRIDE);
TOKENDEF (TK_Final, ZCC_FINAL);
TOKENDEF (TK_Meta, ZCC_META);

View file

@ -36,6 +36,7 @@ enum
ZCC_Virtual = 1 << 13,
ZCC_Override = 1 << 14,
ZCC_Transient = 1 << 15,
ZCC_VarArg = 1 << 16
};
// Function parameter modifiers

View file

@ -308,6 +308,8 @@ enum EPickStart
struct String native
{
native void Replace(String pattern, String replacement);
native static vararg String Format(String fmt, ...);
native vararg void AppendFormat(String fmt, ...);
}
class Floor : Thinker native

View file

@ -50,7 +50,7 @@ extend class PlayerPawn
let player = self.player;
if (PlayerNumber() != consoleplayer)
A_Log(format ("%s is a cheater: give %s\n", player.GetUserName(), name));
A_Log(String.Format ("%s is a cheater: give %s\n", player.GetUserName(), name));
if (player.mo == NULL || player.health <= 0)
{
@ -254,7 +254,7 @@ extend class PlayerPawn
if (type == NULL)
{
if (PlayerNumber() == consoleplayer)
A_Log(format("Unknown item \"%s\"\n", name));
A_Log(String.Format("Unknown item \"%s\"\n", name));
}
else
{
@ -391,7 +391,7 @@ extend class PlayerPawn
if (type == NULL)
{
if (PlayerNumber() == consoleplayer)
A_Log(format("Unknown item \"%s\"\n", name));
A_Log(String.Format("Unknown item \"%s\"\n", name));
}
else
{

View file

@ -159,7 +159,7 @@ class CustomSprite : Actor
String name;
Super.BeginPlay ();
format(name, "BTIL%04d", args[0] & 0xffff);
//format(name, "BTIL%04d", args[0] & 0xffff); // note: this does NOTHING and did NOTHING. what?
picnum = TexMan.CheckForTexture (name, TexMan.TYPE_Build);
if (!picnum.Exists())
{

View file

@ -115,7 +115,7 @@ class SkyPicker : Actor
if (box == null && args[0] != 0)
{
A_Log(format("Can't find SkyViewpoint %d for sector %d\n", args[0], CurSector.Index()));
A_Log(String.Format("Can't find SkyViewpoint %d for sector %d\n", args[0], CurSector.Index()));
}
else
{