Merge pull request #1712 from marauder2k9-torque/ScriptBackend-Changes-and-Cleanup
Some checks failed
Linux Build / Ubuntu Latest GCC (push) Has been cancelled
MacOSX Build / MacOSX Latest Clang (push) Has been cancelled
Windows Build / Windows Latest MSVC (push) Has been cancelled

Scripting language changes, cleanup and documentation
This commit is contained in:
Brian Roberts 2026-04-21 14:23:25 -05:00 committed by GitHub
commit 8407fa360c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 2429 additions and 2185 deletions

View file

@ -120,12 +120,12 @@ typedef const char *StringTableEntry;
enum ConsoleValueType enum ConsoleValueType
{ {
cvNULL = -5, cvNULL = -5,
cvInteger = -4, cvInteger = -4,
cvFloat = -3, cvFloat = -3,
cvString = -2, cvString = -2, ///< Heap-allocated, owned (dMalloc/dFree)
cvSTEntry = -1, cvSTEntry = -1, ///< StringTable pointer, NOT owned
cvConsoleValueType = 0 cvConsoleValueType = 0 ///< First valid engine console type ID
}; };
class ConsoleValue class ConsoleValue
@ -148,6 +148,7 @@ public:
EnumTable* enumTable; EnumTable* enumTable;
}; };
}; };
#pragma warning(pop)
S32 type; S32 type;
U32 bufferLen; U32 bufferLen;
@ -160,7 +161,10 @@ public:
TORQUE_FORCEINLINE void cleanupData() TORQUE_FORCEINLINE void cleanupData()
{ {
if (type <= cvString && bufferLen > 0) // Only cvString strings are heap-allocated and owned by this value.
// cvSTEntry points into the StringTable (managed externally).
// Numeric types use the f/i union fields — s is not valid for them.
if (type == ConsoleValueType::cvString && bufferLen > 0)
{ {
dFree(s); dFree(s);
bufferLen = 0; bufferLen = 0;
@ -176,57 +180,38 @@ public:
bufferLen = 0; bufferLen = 0;
} }
ConsoleValue(const ConsoleValue& ref) ConsoleValue(const ConsoleValue& other)
: type(ConsoleValueType::cvSTEntry)
, bufferLen(0)
{ {
type = ConsoleValueType::cvSTEntry;
s = const_cast<char*>(StringTable->EmptyString()); s = const_cast<char*>(StringTable->EmptyString());
bufferLen = 0; copyFrom(other);
switch (ref.type)
{
case cvNULL:
std::cout << "Ref already cleared!";
break;
case cvInteger:
setInt(ref.i);
break;
case cvFloat:
setFloat(ref.f);
break;
case cvSTEntry:
setStringTableEntry(ref.s);
break;
case cvString:
setString(ref.s);
break;
default:
setConsoleData(ref.type, ref.dataPtr, ref.enumTable);
break;
}
} }
ConsoleValue& operator=(const ConsoleValue& ref) /// Move constructor — steals the heap buffer rather than copying it.
/// After the move, `other` is left as an empty-string value.
ConsoleValue(ConsoleValue&& other) noexcept
: type(other.type)
, bufferLen(other.bufferLen)
{ {
switch (ref.type) transferFrom(other);
}
ConsoleValue& operator=(const ConsoleValue& other)
{
if (this != &other)
copyFrom(other);
return *this;
}
ConsoleValue& operator=(ConsoleValue&& other) noexcept
{
if (this != &other)
{ {
case cvNULL: cleanupData();
std::cout << "Ref already cleared!"; type = other.type;
break; bufferLen = other.bufferLen;
case cvInteger: transferFrom(other);
setInt(ref.i);
break;
case cvFloat:
setFloat(ref.f);
break;
case cvSTEntry:
setStringTableEntry(ref.s);
break;
case cvString:
setString(ref.s);
break;
default:
setConsoleData(ref.type, ref.dataPtr, ref.enumTable);
break;
} }
return *this; return *this;
} }
@ -243,75 +228,105 @@ public:
TORQUE_FORCEINLINE F64 getFloat() const TORQUE_FORCEINLINE F64 getFloat() const
{ {
if (type == ConsoleValueType::cvFloat) switch (type)
{
case ConsoleValueType::cvFloat:
return f; return f;
if (type == ConsoleValueType::cvInteger) case ConsoleValueType::cvInteger:
return i; return static_cast<F64>(i);
if (type == ConsoleValueType::cvSTEntry) case ConsoleValueType::cvSTEntry:
return s == StringTable->EmptyString() ? 0.0f : dAtof(s); return (s == StringTable->EmptyString()) ? 0.0 : dAtof(s);
if (type == ConsoleValueType::cvString) case ConsoleValueType::cvString:
return dStrcmp(s, "") == 0 ? 0.0f : dAtof(s); return (s[0] == '\0') ? 0.0 : dAtof(s);
return dAtof(getConsoleData()); case ConsoleValueType::cvNULL:
return 0.0;
default:
return dAtof(getConsoleData());
}
} }
TORQUE_FORCEINLINE S64 getInt() const TORQUE_FORCEINLINE S64 getInt() const
{ {
if (type == ConsoleValueType::cvInteger) switch (type)
{
case ConsoleValueType::cvInteger:
return i; return i;
if (type == ConsoleValueType::cvFloat) case ConsoleValueType::cvFloat:
return f; return static_cast<S64>(f);
if (type == ConsoleValueType::cvSTEntry) case ConsoleValueType::cvSTEntry:
return s == StringTable->EmptyString() ? 0 : dAtoi(s); return (s == StringTable->EmptyString()) ? S64(0) : static_cast<S64>(dAtoi(s));
if (type == ConsoleValueType::cvString) case ConsoleValueType::cvString:
return dStrcmp(s, "") == 0 ? 0 : dAtoi(s); return (s[0] == '\0') ? S64(0) : static_cast<S64>(dAtoi(s));
case ConsoleValueType::cvNULL:
return dAtoi(getConsoleData()); return 0;
} default:
return static_cast<S64>(dAtoi(getConsoleData()));
TORQUE_FORCEINLINE const char* getString() const }
{
if (isStringType())
return s;
if (isNumberType())
return convertToBuffer();
return getConsoleData();
}
TORQUE_FORCEINLINE operator const char* () const
{
return getString();
} }
TORQUE_FORCEINLINE bool getBool() const TORQUE_FORCEINLINE bool getBool() const
{ {
if (type == ConsoleValueType::cvInteger) switch (type)
return (bool)i; {
if (type == ConsoleValueType::cvFloat) case ConsoleValueType::cvInteger:
return (bool)f; return (i != 0);
if (type == ConsoleValueType::cvSTEntry) case ConsoleValueType::cvFloat:
return s == StringTable->EmptyString() ? false : dAtob(s); return (f != 0.0);
if (type == ConsoleValueType::cvString) case ConsoleValueType::cvSTEntry:
return dStrcmp(s, "") == 0 ? false : dAtob(s); return (s != StringTable->EmptyString()) && dAtob(s);
return dAtob(getConsoleData()); case ConsoleValueType::cvString:
return (s[0] != '\0') && dAtob(s);
case ConsoleValueType::cvNULL:
return false;
default:
return dAtob(getConsoleData());
}
} }
TORQUE_FORCEINLINE void setFloat(const F64 val) TORQUE_FORCEINLINE const char* getString() const
{
switch (type)
{
case ConsoleValueType::cvSTEntry:
case ConsoleValueType::cvString:
return s;
case ConsoleValueType::cvNULL:
return StringTable->EmptyString();
case ConsoleValueType::cvFloat:
case ConsoleValueType::cvInteger:
return convertToBuffer();
default:
return getConsoleData();
}
}
TORQUE_FORCEINLINE operator const char* () const { return getString(); }
TORQUE_FORCEINLINE void setFloat(F64 val)
{ {
cleanupData(); cleanupData();
type = ConsoleValueType::cvFloat; type = ConsoleValueType::cvFloat;
f = val; f = val;
// bufferLen is already 0 after cleanupData — correct for non-string types
} }
TORQUE_FORCEINLINE void setInt(const S64 val) TORQUE_FORCEINLINE void setInt(S64 val)
{ {
cleanupData(); cleanupData();
type = ConsoleValueType::cvInteger; type = ConsoleValueType::cvInteger;
i = val; i = val;
} }
TORQUE_FORCEINLINE void setBool(bool val)
{
cleanupData();
type = ConsoleValueType::cvInteger;
i = val ? S64(1) : S64(0);
}
TORQUE_FORCEINLINE void setString(const char* val) TORQUE_FORCEINLINE void setString(const char* val)
{ {
setString(val, val != NULL ? dStrlen(val) : 0); setString(val, val ? static_cast<S32>(dStrlen(val)) : 0);
} }
TORQUE_FORCEINLINE void setString(const char* val, S32 len) TORQUE_FORCEINLINE void setString(const char* val, S32 len)
@ -321,67 +336,83 @@ public:
setEmptyString(); setEmptyString();
return; return;
} }
cleanupData(); cleanupData();
type = ConsoleValueType::cvString; type = ConsoleValueType::cvString;
bufferLen = static_cast<U32>(len) + 1u; // allocation size, always > 0
s = (char*)dMalloc(len + 1); s = static_cast<char*>(dMalloc(bufferLen));
bufferLen = len + 1;
s[len] = '\0'; s[len] = '\0';
dStrcpy(s, val, len + 1); dMemcpy(s, val, static_cast<dsize_t>(len));
} }
TORQUE_FORCEINLINE void setStringRef(const char* ref, S32 len) /// Transfer ownership of a dMalloc'd buffer to this value.
///
/// @param ownedBuf Buffer allocated with dMalloc. Must have a null
/// terminator at ownedBuf[len]. This value will call
/// dFree(ownedBuf) when it is cleaned up.
/// @param len String length NOT including the null terminator.
/// If len == 0 the buffer still gets freed correctly
/// because bufferLen is stored as len+1.
TORQUE_FORCEINLINE void setStringOwned(char* ownedBuf, S32 len)
{ {
cleanupData(); cleanupData();
type = ConsoleValueType::cvString; type = ConsoleValueType::cvString;
s = (char*)std::move(ref); bufferLen = static_cast<U32>(len) + 1; // always > 0 → cleanupData will free
bufferLen = len; s = ownedBuf;
} }
TORQUE_FORCEINLINE void setBool(const bool val) /// @deprecated Use setStringOwned(). Kept so existing call sites compile.
/// The old name "Ref" implied a non-owning borrow, which was
/// the opposite of the actual semantics.
TORQUE_FORCEINLINE void setStringRef(const char* ownedBuf, S32 len)
{ {
cleanupData(); setStringOwned(const_cast<char*>(ownedBuf), len);
type = ConsoleValueType::cvInteger;
i = (int)val;
} }
TORQUE_FORCEINLINE void setStringTableEntry(StringTableEntry val) TORQUE_FORCEINLINE void setStringTableEntry(StringTableEntry val)
{ {
cleanupData(); cleanupData();
type = ConsoleValueType::cvSTEntry; type = ConsoleValueType::cvSTEntry;
s = (char*)(StringTable->insert(val)); // StringTable::insert accepts NULL and returns EmptyString
bufferLen = 0; s = const_cast<char*>(StringTable->insert(val ? val : ""));
bufferLen = 0; // NOT owned — StringTable manages this memory
} }
TORQUE_FORCEINLINE void setEmptyString() TORQUE_FORCEINLINE void setEmptyString()
{ {
setStringTableEntry(StringTable->EmptyString()); // cleanupData already sets s = EmptyString and type = cvNULL.
// We then promote the type to cvSTEntry so queries return a valid
// empty string rather than having to special-case cvNULL everywhere.
cleanupData();
type = ConsoleValueType::cvSTEntry;
} }
TORQUE_FORCEINLINE void setConsoleData(S32 inConsoleType, void* inDataPtr, const EnumTable* inEnumTable) TORQUE_FORCEINLINE void setConsoleData(S32 inType, void* inDataPtr, const EnumTable* inEnumTable)
{ {
cleanupData(); cleanupData();
type = inConsoleType; type = inType;
dataPtr = inDataPtr; dataPtr = inDataPtr;
enumTable = const_cast<EnumTable*>(inEnumTable); enumTable = const_cast<EnumTable*>(inEnumTable);
}; bufferLen = 0;
TORQUE_FORCEINLINE S32 getType() const
{
return type;
} }
TORQUE_FORCEINLINE bool isStringType() const TORQUE_FORCEINLINE void setFastFloat(F64 val) { type = ConsoleValueType::cvFloat; f = val; }
TORQUE_FORCEINLINE F64 getFastFloat() const { return f; }
TORQUE_FORCEINLINE void setFastInt(S64 val) { type = ConsoleValueType::cvInteger; i = val; }
TORQUE_FORCEINLINE S64 getFastInt() const { return i; }
TORQUE_FORCEINLINE S32 getType() const { return type; }
TORQUE_FORCEINLINE bool isStringType() const
{ {
return type == ConsoleValueType::cvString || type == ConsoleValueType::cvSTEntry; return type == ConsoleValueType::cvString
|| type == ConsoleValueType::cvSTEntry;
} }
TORQUE_FORCEINLINE bool isNumberType() const TORQUE_FORCEINLINE bool isNumberType() const
{ {
return type == ConsoleValueType::cvFloat || type == ConsoleValueType::cvInteger; return type == ConsoleValueType::cvFloat
|| type == ConsoleValueType::cvInteger;
} }
TORQUE_FORCEINLINE bool isConsoleType() const TORQUE_FORCEINLINE bool isConsoleType() const
@ -391,40 +422,89 @@ public:
TORQUE_FORCEINLINE S32 getConsoleType() const TORQUE_FORCEINLINE S32 getConsoleType() const
{ {
if(type >= ConsoleValueType::cvConsoleValueType) return (type >= ConsoleValueType::cvConsoleValueType) ? type : 0;
{
return type;
}
else
{
return NULL;
}
}
TORQUE_FORCEINLINE void setFastFloat(F64 flt)
{
type = ConsoleValueType::cvFloat;
f = flt;
}
TORQUE_FORCEINLINE F64 getFastFloat() const
{
return f;
}
TORQUE_FORCEINLINE void setFastInt(S64 flt)
{
type = ConsoleValueType::cvInteger;
i = flt;
}
TORQUE_FORCEINLINE S64 getFastInt() const
{
return i;
} }
static void init(); static void init();
static void resetConversionBuffer(); static void resetConversionBuffer();
private:
/// Deep-copy from `other` into `this` (assumes `this` has already been
/// cleaned up or is freshly constructed).
void copyFrom(const ConsoleValue& other)
{
switch (other.type)
{
case ConsoleValueType::cvNULL:
// Another value was already cleaned up. Treat as empty string.
// Do NOT assert here — cvNULL is a valid transient state that can
// appear e.g. when an entry is moved out of.
setEmptyString();
break;
case ConsoleValueType::cvInteger:
setInt(other.i);
break;
case ConsoleValueType::cvFloat:
setFloat(other.f);
break;
case ConsoleValueType::cvSTEntry:
// s already points into StringTable — just share the pointer.
setStringTableEntry(other.s);
break;
case ConsoleValueType::cvString:
{
// bufferLen == allocation size (len+1), so string length == bufferLen-1.
// Guard defensively: if somehow bufferLen is 0 (pre-fix bug state),
// fall back to dStrlen.
S32 strLen = (other.bufferLen > 0)
? static_cast<S32>(other.bufferLen) - 1
: static_cast<S32>(dStrlen(other.s));
setString(other.s, strLen);
break;
}
default:
setConsoleData(other.type, other.dataPtr, other.enumTable);
break;
}
}
/// Steal the payload from `other` (which must already have its type and
/// bufferLen copied into `this`), then leave `other` in a safe empty state.
/// Called only from move constructor / move assignment after copying type.
TORQUE_FORCEINLINE void transferFrom(ConsoleValue& other) noexcept
{
// Copy the right union field based on the type we already copied.
switch (type)
{
case ConsoleValueType::cvFloat:
f = other.f;
break;
case ConsoleValueType::cvInteger:
i = other.i;
break;
case ConsoleValueType::cvString:
case ConsoleValueType::cvSTEntry:
case ConsoleValueType::cvNULL:
s = other.s;
break;
default:
dataPtr = other.dataPtr;
enumTable = other.enumTable;
break;
}
// Leave `other` as a valid empty-string value.
// Critically: if we stole a cvString buffer, other must NOT keep a
// non-zero bufferLen, or its destructor will double-free.
other.s = const_cast<char*>(StringTable->EmptyString());
other.type = ConsoleValueType::cvSTEntry;
other.bufferLen = 0;
}
}; };
// Transparently converts ConsoleValue[] to const char** // Transparently converts ConsoleValue[] to const char**

View file

@ -672,13 +672,13 @@ Namespace::Entry::Entry()
mPackage = StringTable->EmptyString(); mPackage = StringTable->EmptyString();
mToolOnly = false; mToolOnly = false;
VECTOR_SET_ASSOCIATION(mArgFlags); VECTOR_SET_ASSOCIATION(mArgFlags);
VECTOR_SET_ASSOCIATION(mDefaultValues); VECTOR_SET_ASSOCIATION(mDefaultOffsets);
} }
void Namespace::Entry::clear() void Namespace::Entry::clear()
{ {
mArgFlags.clear(); mArgFlags.clear();
mDefaultValues.clear(); mDefaultOffsets.clear();
if (mModule) if (mModule)
{ {

View file

@ -132,7 +132,7 @@ public:
// Offsets to get default values for arguments. // Offsets to get default values for arguments.
Vector<U32> mArgFlags; Vector<U32> mArgFlags;
Vector<ConsoleValue> mDefaultValues; Vector<U32> mDefaultOffsets;
/// If it's a script function, this is the line of the declaration in code. /// If it's a script function, this is the line of the declaration in code.
/// @note 0 for functions read from legacy DSOs that have no line number information. /// @note 0 for functions read from legacy DSOs that have no line number information.

View file

@ -81,51 +81,45 @@ extern int CMDdebug;
rwSWITCHSTR = 282, /* rwSWITCHSTR */ rwSWITCHSTR = 282, /* rwSWITCHSTR */
rwCASEOR = 283, /* rwCASEOR */ rwCASEOR = 283, /* rwCASEOR */
rwPACKAGE = 284, /* rwPACKAGE */ rwPACKAGE = 284, /* rwPACKAGE */
rwNAMESPACE = 285, /* rwNAMESPACE */ rwASSERT = 285, /* rwASSERT */
rwCLASS = 286, /* rwCLASS */ ILLEGAL_TOKEN = 286, /* ILLEGAL_TOKEN */
rwASSERT = 287, /* rwASSERT */ CHRCONST = 287, /* CHRCONST */
ILLEGAL_TOKEN = 288, /* ILLEGAL_TOKEN */ INTCONST = 288, /* INTCONST */
CHRCONST = 289, /* CHRCONST */ TTAG = 289, /* TTAG */
INTCONST = 290, /* INTCONST */ VAR = 290, /* VAR */
TTAG = 291, /* TTAG */ IDENT = 291, /* IDENT */
VAR = 292, /* VAR */ TYPEIDENT = 292, /* TYPEIDENT */
IDENT = 293, /* IDENT */ DOCBLOCK = 293, /* DOCBLOCK */
TYPEIDENT = 294, /* TYPEIDENT */ STRATOM = 294, /* STRATOM */
DOCBLOCK = 295, /* DOCBLOCK */ TAGATOM = 295, /* TAGATOM */
STRATOM = 296, /* STRATOM */ FLTCONST = 296, /* FLTCONST */
TAGATOM = 297, /* TAGATOM */ opINTNAME = 297, /* opINTNAME */
FLTCONST = 298, /* FLTCONST */ opINTNAMER = 298, /* opINTNAMER */
opINTNAME = 299, /* opINTNAME */ opMINUSMINUS = 299, /* opMINUSMINUS */
opINTNAMER = 300, /* opINTNAMER */ opPLUSPLUS = 300, /* opPLUSPLUS */
opMINUSMINUS = 301, /* opMINUSMINUS */ opSHL = 301, /* opSHL */
opPLUSPLUS = 302, /* opPLUSPLUS */ opSHR = 302, /* opSHR */
STMT_SEP = 303, /* STMT_SEP */ opPLASN = 303, /* opPLASN */
opSHL = 304, /* opSHL */ opMIASN = 304, /* opMIASN */
opSHR = 305, /* opSHR */ opMLASN = 305, /* opMLASN */
opPLASN = 306, /* opPLASN */ opDVASN = 306, /* opDVASN */
opMIASN = 307, /* opMIASN */ opMODASN = 307, /* opMODASN */
opMLASN = 308, /* opMLASN */ opANDASN = 308, /* opANDASN */
opDVASN = 309, /* opDVASN */ opXORASN = 309, /* opXORASN */
opMODASN = 310, /* opMODASN */ opORASN = 310, /* opORASN */
opANDASN = 311, /* opANDASN */ opSLASN = 311, /* opSLASN */
opXORASN = 312, /* opXORASN */ opSRASN = 312, /* opSRASN */
opORASN = 313, /* opORASN */ opCAT = 313, /* opCAT */
opSLASN = 314, /* opSLASN */ opEQ = 314, /* opEQ */
opSRASN = 315, /* opSRASN */ opNE = 315, /* opNE */
opCAT = 316, /* opCAT */ opGE = 316, /* opGE */
opEQ = 317, /* opEQ */ opLE = 317, /* opLE */
opNE = 318, /* opNE */ opAND = 318, /* opAND */
opGE = 319, /* opGE */ opOR = 319, /* opOR */
opLE = 320, /* opLE */ opSTREQ = 320, /* opSTREQ */
opAND = 321, /* opAND */ opSTRNE = 321, /* opSTRNE */
opOR = 322, /* opOR */ opCOLONCOLON = 322, /* opCOLONCOLON */
opSTREQ = 323, /* opSTREQ */ UNARY = 323 /* UNARY */
opCOLONCOLON = 324, /* opCOLONCOLON */
opMDASN = 325, /* opMDASN */
opNDASN = 326, /* opNDASN */
opNTASN = 327, /* opNTASN */
opSTRNE = 328, /* opSTRNE */
UNARY = 329 /* UNARY */
}; };
typedef enum yytokentype yytoken_kind_t; typedef enum yytokentype yytoken_kind_t;
#endif #endif
@ -134,7 +128,7 @@ extern int CMDdebug;
#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED #if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
union YYSTYPE union YYSTYPE
{ {
#line 87 "CMDgram.y" #line 107 "CMDgram.y"
Token< char > c; Token< char > c;
Token< int > i; Token< int > i;
@ -152,7 +146,7 @@ union YYSTYPE
AssignDecl asn; AssignDecl asn;
IfStmtNode* ifnode; IfStmtNode* ifnode;
#line 156 "CMDgram.h" #line 150 "CMDgram.h"
}; };
typedef union YYSTYPE YYSTYPE; typedef union YYSTYPE YYSTYPE;

View file

@ -46,20 +46,28 @@ struct Token
}; };
%} %}
%{ %{
/* Reserved Word Definitions */ /* Reserved word token definitions */
%} %}
%token <i> rwDEFINE rwENDDEF rwDECLARE rwDECLARESINGLETON %token <i> rwDEFINE rwENDDEF rwDECLARE rwDECLARESINGLETON
%token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL %token <i> rwBREAK rwELSE rwCONTINUE rwGLOBAL
%token <i> rwIF rwNIL rwRETURN rwWHILE rwDO %token <i> rwIF rwNIL rwRETURN rwWHILE rwDO
%token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT %token <i> rwENDIF rwENDWHILE rwENDFOR rwDEFAULT
%token <i> rwFOR rwFOREACH rwFOREACHSTR rwIN rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR %token <i> rwFOR rwFOREACH rwFOREACHSTR rwIN rwDATABLOCK rwSWITCH rwCASE rwSWITCHSTR
%token <i> rwCASEOR rwPACKAGE rwNAMESPACE rwCLASS %token <i> rwCASEOR rwPACKAGE
%token <i> rwASSERT %token <i> rwASSERT
%token ILLEGAL_TOKEN %token ILLEGAL_TOKEN
// NOTE: rwNAMESPACE and rwCLASS were declared here previously but had no
// lexer rules and appeared in no grammar productions. They have been
// removed. If namespace/class syntax is added in future, add both the
// token declaration AND the lexer rule at the same time.
%{ %{
/* Constants and Identifier Definitions */ /* Constant and identifier token definitions */
%} %}
%token <c> CHRCONST %token <c> CHRCONST
%token <i> INTCONST %token <i> INTCONST
%token <s> TTAG %token <s> TTAG
@ -72,16 +80,28 @@ struct Token
%token <f> FLTCONST %token <f> FLTCONST
%{ %{
/* Operator Definitions */ /* Operator token definitions */
%} %}
%token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%' %token <i> '+' '-' '*' '/' '<' '>' '=' '.' '|' '&' '%'
%token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@' %token <i> '(' ')' ',' ':' ';' '{' '}' '^' '~' '!' '@'
%token <i> opINTNAME opINTNAMER %token <i> opINTNAME opINTNAMER
%token <i> opMINUSMINUS opPLUSPLUS %token <i> opMINUSMINUS opPLUSPLUS
%token <i> STMT_SEP
// NOTE: STMT_SEP was declared here but never returned by the lexer and never
// used in any grammar production. Removed to prevent confusion.
%token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN %token <i> opSHL opSHR opPLASN opMIASN opMLASN opDVASN opMODASN opANDASN
%token <i> opXORASN opORASN opSLASN opSRASN opCAT %token <i> opXORASN opORASN opSLASN opSRASN opCAT
%token <i> opEQ opNE opGE opLE opAND opOR opSTREQ %token <i> opEQ opNE opGE opLE opAND opOR
// FIX: opSTREQ and opSTRNE must be declared with their semantic type <i>.
// Previously opSTRNE was only mentioned in the %left precedence line, which
// does declare it as a token but gives it no type — causing a silent type
// mismatch when used in grammar rules (even if $2 isn't used in the action,
// the generated parser code is technically undefined behaviour).
%token <i> opSTREQ opSTRNE
%token <i> opCOLONCOLON %token <i> opCOLONCOLON
%union { %union {
@ -143,8 +163,13 @@ struct Token
%type <var> var_list_decl %type <var> var_list_decl
%type <asn> assign_op_struct %type <asn> assign_op_struct
// Operator precedence — lowest to highest.
// FIX: opMDASN, opNDASN, opNTASN were listed here but were never defined
// as tokens anywhere and were never returned by the lexer. They appear to
// be leftovers from an earlier revision. Removed to prevent compiler
// warnings about undeclared token names.
%left '[' %left '['
%right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opMDASN opNDASN opNTASN opORASN opSLASN opSRASN '=' %right opMODASN opANDASN opXORASN opPLASN opMIASN opMLASN opDVASN opORASN opSLASN opSRASN '='
%left '?' ':' %left '?' ':'
%left opOR %left opOR
%left opAND %left opAND
@ -229,17 +254,19 @@ stmt
; ;
fn_decl_stmt fn_decl_stmt
// Global function
: rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}' : rwDEFINE IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc( $1.lineNumber, $2.value, NULL, $4, $7 ); } { $$ = FunctionDeclStmtNode::alloc( $1.lineNumber, $2.value, NULL, $4, $7 ); }
| rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}' // Namespaced method: function Namespace::name(...) { }
{ $$ = FunctionDeclStmtNode::alloc( $1.lineNumber, $4.value, $2.value, $6, $9 ); } | rwDEFINE IDENT opCOLONCOLON IDENT '(' var_list_decl ')' '{' statement_list '}'
{ $$ = FunctionDeclStmtNode::alloc( $1.lineNumber, $4.value, $2.value, $6, $9 ); }
; ;
var_list_decl var_list_decl
: :
{ $$ = NULL; } { $$ = NULL; }
| var_list | var_list
{ $$ = $1; } { $$ = $1; }
; ;
var_list var_list
@ -249,27 +276,31 @@ var_list
{ $$ = $1; ((StmtNode*)($1))->append((StmtNode*)$3 ); } { $$ = $1; ((StmtNode*)($1))->append((StmtNode*)$3 ); }
; ;
// Parameter declaration forms:
//
// %var — required parameter
// %var ? — optional parameter, evaluates to "" / 0 when absent
// %var = expr — optional parameter with default value
// %var ? = expr — same as above; the '?' makes the optionality explicit
//
// NOTE: the default `expr` can be any valid expression, including function
// calls and variable references. At present these are evaluated once at
// declaration time (global scope). The planned codelet change (see
// FunctionDeclStmtNode::compileStmt in ast.cpp) will evaluate them at
// each call site instead — no grammar change is required for that fix.
param param
: VAR : VAR
{ { $$ = VarNode::allocParam($1.lineNumber, $1.value, NULL); }
$$ = VarNode::allocParam($1.lineNumber, $1.value, NULL); | VAR '?'
} { $$ = VarNode::allocParam($1.lineNumber, $1.value, NULL); }
| VAR '?' | VAR '=' expr
{ { $$ = VarNode::allocParam($1.lineNumber, $1.value, $3); }
$$ = VarNode::allocParam($1.lineNumber, $1.value, NULL); | VAR '?' '=' expr
} { $$ = VarNode::allocParam($1.lineNumber, $1.value, $4); }
| VAR '=' expr ;
{
$$ = VarNode::allocParam($1.lineNumber, $1.value, $3);
}
| VAR '?' '=' expr
{
$$ = VarNode::allocParam($1.lineNumber, $1.value, $4);
}
;
datablock_decl datablock_decl
: rwDATABLOCK class_name_expr '(' expr parent_block ')' '{' slot_assign_list_opt '}' ';' : rwDATABLOCK class_name_expr '(' expr parent_block ')' '{' slot_assign_list_opt '}' ';'
{ $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, NULL, $5.value, $8, NULL, true, false, false); } { $$ = ObjectDeclNode::alloc( $1.lineNumber, $2, $4, NULL, $5.value, $8, NULL, true, false, false); }
; ;
@ -341,6 +372,9 @@ switch_stmt
{ $$ = $6; $6->propagateSwitchExpr($3, true); } { $$ = $6; $6->propagateSwitchExpr($3, true); }
; ;
// NOTE: propagateSwitchExpr builds a recursive OR expression tree that is
// O(n) deep for n cases. Large switch statements (100+ cases) can overflow
// the compiler stack.
case_block case_block
: rwCASE case_expr ':' statement_list : rwCASE case_expr ':' statement_list
{ $$ = IfStmtNode::alloc( $1.lineNumber, $2, $4, NULL, false); } { $$ = IfStmtNode::alloc( $1.lineNumber, $2, $4, NULL, false); }
@ -352,9 +386,9 @@ case_block
case_expr case_expr
: expr : expr
{ $$ = $1;} { $$ = $1; }
| case_expr rwCASEOR expr | case_expr rwCASEOR expr
{ ($1)->append($3); $$=$1; } { ($1)->append($3); $$ = $1; }
; ;
if_stmt if_stmt
@ -455,6 +489,12 @@ expr
{ $$ = StreqExprNode::alloc( $1->dbgLineNumber, $1, $3, true); } { $$ = StreqExprNode::alloc( $1->dbgLineNumber, $1, $3, true); }
| expr opSTRNE expr | expr opSTRNE expr
{ $$ = StreqExprNode::alloc( $1->dbgLineNumber, $1, $3, false); } { $$ = StreqExprNode::alloc( $1->dbgLineNumber, $1, $3, false); }
// The '@' operator covers four cases via token value encoding in the lexer:
// '@' → value 0 (plain concatenation)
// NL → value '\n'
// TAB → value '\t'
// SPC → value ' '
// The appendChar is stored in $2.value and forwarded to StrcatExprNode.
| expr '@' expr | expr '@' expr
{ $$ = StrcatExprNode::alloc( $1->dbgLineNumber, $1, $3, $2.value); } { $$ = StrcatExprNode::alloc( $1->dbgLineNumber, $1, $3, $2.value); }
| '!' expr | '!' expr
@ -482,23 +522,6 @@ expr
| VAR '[' aidx_expr ']' | VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc( $1.lineNumber, $1.value, $3 ); } { $$ = (ExprNode*)VarNode::alloc( $1.lineNumber, $1.value, $3 ); }
; ;
/*
| rwDEFINE '(' var_list_decl ')' '{' statement_list '}'
{
const U32 bufLen = 64;
UTF8 buffer[bufLen];
dSprintf(buffer, bufLen, "__anonymous_function%d", gAnonFunctionID++);
StringTableEntry fName = StringTable->insert(buffer);
StmtNode *fndef = FunctionDeclStmtNode::alloc($1.lineNumber, fName, NULL, $3, $6);
if(!gAnonFunctionList)
gAnonFunctionList = fndef;
else
gAnonFunctionList->append(fndef);
$$ = StrConstNode::alloc( $1.lineNumber, (UTF8*)fName, false );
}
*/
slot_acc slot_acc
: expr '.' IDENT : expr '.' IDENT
@ -509,9 +532,9 @@ slot_acc
intslot_acc intslot_acc
: expr opINTNAME class_name_expr : expr opINTNAME class_name_expr
{ $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotExpr = $3; $$.recurse = false; } { $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotExpr = $3; $$.recurse = false; }
| expr opINTNAMER class_name_expr | expr opINTNAMER class_name_expr
{ $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotExpr = $3; $$.recurse = true; } { $$.lineNumber = $1->dbgLineNumber; $$.object = $1; $$.slotExpr = $3; $$.recurse = true; }
; ;
class_name_expr class_name_expr
@ -572,18 +595,18 @@ stmt_expr
; ;
funcall_expr funcall_expr
// Global function call: name(args)
: IDENT '(' expr_list_decl ')' : IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc( $1.lineNumber, $1.value, NULL, $3, false); } { $$ = FuncCallExprNode::alloc( $1.lineNumber, $1.value, NULL, $3, false); }
// Static/namespace call: Namespace::name(args)
| IDENT opCOLONCOLON IDENT '(' expr_list_decl ')' | IDENT opCOLONCOLON IDENT '(' expr_list_decl ')'
{ $$ = FuncCallExprNode::alloc( $1.lineNumber, $3.value, $1.value, $5, false); } { $$ = FuncCallExprNode::alloc( $1.lineNumber, $3.value, $1.value, $5, false); }
// Method call: object.method(args)
// The object expression is prepended to the arg list so that exec() can
// find it as callArgv[1] (the implicit 'this').
| expr '.' IDENT '(' expr_list_decl ')' | expr '.' IDENT '(' expr_list_decl ')'
{ $1->append($5); $$ = FuncCallExprNode::alloc( $1->dbgLineNumber, $3.value, NULL, $1, true); } { $1->append($5); $$ = FuncCallExprNode::alloc( $1->dbgLineNumber, $3.value, NULL, $1, true); }
; ;
/*
| expr '(' expr_list_decl ')'
{ $$ = FuncPointerCallExprNode::alloc( $1->dbgLineNumber, $1, $3); }
;
*/
assert_expr assert_expr
: rwASSERT '(' expr ')' : rwASSERT '(' expr ')'
@ -633,36 +656,44 @@ slot_assign
{ $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, $4, $2.value, $7, $1.value); } { $$ = SlotAssignNode::alloc( $1.lineNumber, NULL, $4, $2.value, $7, $1.value); }
; ;
// Array index expressions. Multiple comma-separated indices get
// concatenated with '_' separators at runtime (e.g. arr[1,2] → "arr_1_2").
aidx_expr aidx_expr
: expr : expr
{ $$ = $1; } { $$ = $1; }
| aidx_expr ',' expr | aidx_expr ',' expr
{ $$ = CommaCatExprNode::alloc( $1->dbgLineNumber, $1, $3); } { $$ = CommaCatExprNode::alloc( $1->dbgLineNumber, $1, $3); }
; ;
%% %%
int int
yyreport_syntax_error (const yypcontext_t *ctx) yyreport_syntax_error(const yypcontext_t *ctx)
{ {
int ret = 0; int ret = 0;
String output; String output;
const YYLTYPE *loc = yypcontext_location (ctx); const YYLTYPE *loc = yypcontext_location(ctx);
output += "syntax error: "; output += "syntax error: ";
yysymbol_kind_t nxt = yypcontext_token(ctx); yysymbol_kind_t nxt = yypcontext_token(ctx);
if (nxt != YYSYMBOL_YYEMPTY) if (nxt != YYSYMBOL_YYEMPTY)
output += String::ToString("unexpected: %s at column: %d", yysymbol_name(nxt), loc->first_column); output += String::ToString("unexpected: %s at column: %d",
yysymbol_name(nxt), loc->first_column);
enum { TOKENMAX = 10 }; enum { TOKENMAX = 10 };
yysymbol_kind_t expected[TOKENMAX]; yysymbol_kind_t expected[TOKENMAX];
int exp = yypcontext_expected_tokens(ctx, expected, TOKENMAX); int exp = yypcontext_expected_tokens(ctx, expected, TOKENMAX);
if (exp < 0) if (exp < 0)
{
ret = exp; ret = exp;
}
else else
{ {
for (int i = 0; i < exp; ++i) for (int i = 0; i < exp; ++i)
output += String::ToString("%s %s", i == 0 ? ": expected" : "or", yysymbol_name(expected[i])); output += String::ToString("%s %s",
i == 0 ? ": expected" : "or",
yysymbol_name(expected[i]));
} }
if (lines.size() > 0) if (lines.size() > 0)
@ -671,12 +702,12 @@ yyreport_syntax_error (const yypcontext_t *ctx)
for (int i = 0; i < lines.size(); i++) for (int i = 0; i < lines.size(); i++)
{ {
int line = lines.size() - i; int line = lines.size() - i;
output += String::ToString("%5d | ", loc->first_line - (line-1)) + lines[i] + "\n"; output += String::ToString("%5d | ", loc->first_line - (line - 1))
+ lines[i] + "\n";
} }
output += String::ToString("%5s | %*s", "", loc->first_column, "^"); output += String::ToString("%5s | %*s", "", loc->first_column, "^");
} }
yyerror("%s", output.c_str()); yyerror("%s", output.c_str());
return ret; return ret;
} }

File diff suppressed because it is too large Load diff

View file

@ -56,6 +56,19 @@ static int Sc_ScanIdent();
#endif #endif
Vector<String> lines; Vector<String> lines;
static S32 gCachedLineContextCount = -1; // -1 = needs refresh
static S32 getLineContextCount()
{
if (gCachedLineContextCount < 0)
gCachedLineContextCount = Con::getIntVariable("$scriptErrorLineCount", 10);
return gCachedLineContextCount;
}
void CMDFlushLineContextCache()
{
gCachedLineContextCount = -1;
}
// Install our own input code... // Install our own input code...
#undef CMDgetc #undef CMDgetc
@ -65,24 +78,26 @@ int CMDgetc();
#ifndef isatty #ifndef isatty
inline int isatty(int) { return 0; } inline int isatty(int) { return 0; }
#endif #endif
static int yycolumn = 1; static int yycolumn = 1;
// Wrap our getc, so that lex doesn't try to do its own buffering/file IO.
#define YY_INPUT(buf,result,max_size) \ #define YY_INPUT(buf, result, max_size) \
{ \ { \
int c = '*', n; \ int c = '*', n; \
for ( n = 0; n < max_size && \ for (n = 0; n < max_size && \
(c = CMDgetc()) != EOF && c != '\n'; ++n ) \ (c = CMDgetc()) != EOF && c != '\n'; ++n) \
buf[n] = (char) c; \ buf[n] = (char)c; \
if ( c == '\n' ) \ if (c == '\n') { buf[n++] = (char)c; yycolumn = 1; } \
buf[n++] = (char) c; yycolumn = 1;\ result = n; \
result = n; \
} }
#define YY_USER_ACTION do { \ #define YY_USER_ACTION \
CMDlloc.first_line = CMDlloc.last_line = yylineno; \ do { \
CMDlloc.first_column = yycolumn; CMDlloc.last_column = yycolumn + yyleng - 1; \ CMDlloc.first_line = CMDlloc.last_line = yylineno; \
yycolumn += yyleng; \ CMDlloc.first_column = yycolumn; \
} while(0); CMDlloc.last_column = yycolumn + yyleng - 1; \
yycolumn += yyleng; \
} while (0);
// File state // File state
void CMDSetScanBuffer(const char *sb, const char *fn); void CMDSetScanBuffer(const char *sb, const char *fn);
@ -111,69 +126,85 @@ SPACE [ \t\v\f]
HEXDIGIT [a-fA-F0-9] HEXDIGIT [a-fA-F0-9]
%% %%
;
{SPACE}+ { } {SPACE}+ { /* consume whitespace */ }
("///"([^/\n\r][^\n\r]*)?[\n\r]+)+ { return(Sc_ScanDocBlock()); }
"//"[^\n\r]* ; ("///"([^/\n\r][^\n\r]*)?[\n\r]+)+ { return Sc_ScanDocBlock(); }
[\r] ; "//"[^\n\r]* { /* line comment — discard */ }
\n.* { [\r] { /* bare CR — discard */ }
\n.* {
yycolumn = 1; yycolumn = 1;
lines.push_back(String::ToString("%s", yytext+1));
if (lines.size() > Con::getIntVariable("$scriptErrorLineCount", 10)) // Push the line text (everything after the newline) into the error
// context buffer, then trim to the configured maximum.
lines.push_back(String::ToString("%s", yytext + 1));
S32 maxLines = getLineContextCount();
while (lines.size() > maxLines)
lines.erase(lines.begin()); lines.erase(lines.begin());
yyless(1); yyless(1);
} }
\"(\\.|[^\\"\n\r])*\" { return(Sc_ScanString(STRATOM)); }
\'(\\.|[^\\'\n\r])*\' { return(Sc_ScanString(TAGATOM)); }
"==" { CMDlval.i = MakeToken< int >( opEQ, yylineno ); return opEQ; }
"!=" { CMDlval.i = MakeToken< int >( opNE, yylineno ); return opNE; }
">=" { CMDlval.i = MakeToken< int >( opGE, yylineno ); return opGE; }
"<=" { CMDlval.i = MakeToken< int >( opLE, yylineno ); return opLE; }
"&&" { CMDlval.i = MakeToken< int >( opAND, yylineno ); return opAND; }
"||" { CMDlval.i = MakeToken< int >( opOR, yylineno ); return opOR; }
"::" { CMDlval.i = MakeToken< int >( opCOLONCOLON, yylineno ); return opCOLONCOLON; }
"--" { CMDlval.i = MakeToken< int >( opMINUSMINUS, yylineno ); return opMINUSMINUS; }
"++" { CMDlval.i = MakeToken< int >( opPLUSPLUS, yylineno ); return opPLUSPLUS; }
"$=" { CMDlval.i = MakeToken< int >( opSTREQ, yylineno ); return opSTREQ; }
"!$=" { CMDlval.i = MakeToken< int >( opSTRNE, yylineno ); return opSTRNE; }
"<<" { CMDlval.i = MakeToken< int >( opSHL, yylineno ); return opSHL; }
">>" { CMDlval.i = MakeToken< int >( opSHR, yylineno ); return opSHR; }
"+=" { CMDlval.i = MakeToken< int >( opPLASN, yylineno ); return opPLASN; }
"-=" { CMDlval.i = MakeToken< int >( opMIASN, yylineno ); return opMIASN; }
"*=" { CMDlval.i = MakeToken< int >( opMLASN, yylineno ); return opMLASN; }
"/=" { CMDlval.i = MakeToken< int >( opDVASN, yylineno ); return opDVASN; }
"%=" { CMDlval.i = MakeToken< int >( opMODASN, yylineno ); return opMODASN; }
"&=" { CMDlval.i = MakeToken< int >( opANDASN, yylineno ); return opANDASN; }
"^=" { CMDlval.i = MakeToken< int >( opXORASN, yylineno ); return opXORASN; }
"|=" { CMDlval.i = MakeToken< int >( opORASN, yylineno ); return opORASN; }
"<<=" { CMDlval.i = MakeToken< int >( opSLASN, yylineno ); return opSLASN; }
">>=" { CMDlval.i = MakeToken< int >( opSRASN, yylineno ); return opSRASN; }
"->" { CMDlval.i = MakeToken< int >( opINTNAME, yylineno ); return opINTNAME; }
"-->" { CMDlval.i = MakeToken< int >( opINTNAMER, yylineno ); return opINTNAMER; }
"NL" { CMDlval.i = MakeToken< int >( '\n', yylineno ); return '@'; }
"TAB" { CMDlval.i = MakeToken< int >( '\t', yylineno ); return '@'; }
"SPC" { CMDlval.i = MakeToken< int >( ' ', yylineno ); return '@'; }
"@" { CMDlval.i = MakeToken< int >( 0, yylineno ); return '@'; }
"/*" { /* this comment stops syntax highlighting from getting messed up when editing the lexer in TextPad */
int c = 0, l;
for ( ; ; )
{
l = c;
c = yyinput();
// Is this an open comment? \"(\\.|[^\\"\n\r])*\" { return Sc_ScanString(STRATOM); }
if ( c == EOF ) \'(\\.|[^\\'\n\r])*\' { return Sc_ScanString(TAGATOM); }
{
CMDerror( "unexpected end of file found in comment" );
break;
}
// Did we find the end of the comment? "==" { CMDlval.i = MakeToken<int>(opEQ, yylineno); return opEQ; }
else if ( l == '*' && c == '/' ) "!=" { CMDlval.i = MakeToken<int>(opNE, yylineno); return opNE; }
break; ">=" { CMDlval.i = MakeToken<int>(opGE, yylineno); return opGE; }
} "<=" { CMDlval.i = MakeToken<int>(opLE, yylineno); return opLE; }
"&&" { CMDlval.i = MakeToken<int>(opAND, yylineno); return opAND; }
"||" { CMDlval.i = MakeToken<int>(opOR, yylineno); return opOR; }
"::" { CMDlval.i = MakeToken<int>(opCOLONCOLON, yylineno); return opCOLONCOLON; }
"--" { CMDlval.i = MakeToken<int>(opMINUSMINUS, yylineno); return opMINUSMINUS; }
"++" { CMDlval.i = MakeToken<int>(opPLUSPLUS, yylineno); return opPLUSPLUS; }
"$=" { CMDlval.i = MakeToken<int>(opSTREQ, yylineno); return opSTREQ; }
"!$=" { CMDlval.i = MakeToken<int>(opSTRNE, yylineno); return opSTRNE; }
"<<" { CMDlval.i = MakeToken<int>(opSHL, yylineno); return opSHL; }
">>" { CMDlval.i = MakeToken<int>(opSHR, yylineno); return opSHR; }
"+=" { CMDlval.i = MakeToken<int>(opPLASN, yylineno); return opPLASN; }
"-=" { CMDlval.i = MakeToken<int>(opMIASN, yylineno); return opMIASN; }
"*=" { CMDlval.i = MakeToken<int>(opMLASN, yylineno); return opMLASN; }
"/=" { CMDlval.i = MakeToken<int>(opDVASN, yylineno); return opDVASN; }
"%=" { CMDlval.i = MakeToken<int>(opMODASN, yylineno); return opMODASN; }
"&=" { CMDlval.i = MakeToken<int>(opANDASN, yylineno); return opANDASN; }
"^=" { CMDlval.i = MakeToken<int>(opXORASN, yylineno); return opXORASN; }
"|=" { CMDlval.i = MakeToken<int>(opORASN, yylineno); return opORASN; }
"<<=" { CMDlval.i = MakeToken<int>(opSLASN, yylineno); return opSLASN; }
">>=" { CMDlval.i = MakeToken<int>(opSRASN, yylineno); return opSRASN; }
"->" { CMDlval.i = MakeToken<int>(opINTNAME, yylineno); return opINTNAME; }
"-->" { CMDlval.i = MakeToken<int>(opINTNAMER, yylineno); return opINTNAMER; }
%{
// String concatenation operators. All four return the '@' token; the
// distinguishing data is the separator character stored in the token value.
// The grammar rule expr '@' expr uses $2.value as the appendChar
// argument to StrcatExprNode — so plain '@' gets 0 (no separator),
// NL/TAB/SPC get their respective ASCII codes.
%}
"NL" { CMDlval.i = MakeToken<int>('\n', yylineno); return '@'; }
"TAB" { CMDlval.i = MakeToken<int>('\t', yylineno); return '@'; }
"SPC" { CMDlval.i = MakeToken<int>(' ', yylineno); return '@'; }
"@" { CMDlval.i = MakeToken<int>(0, yylineno); return '@'; }
"/*" {
// Block comment — consume until '*/'
int c = 0, prev = 0;
for (;;)
{
prev = c;
c = yyinput();
if (c == EOF)
{
CMDerror("unexpected end of file inside block comment");
break;
} }
if (prev == '*' && c == '/')
break;
}
}
%{
// Single-character punctuation tokens.
%}
"?" | "?" |
"[" | "[" |
"]" | "]" |
@ -197,40 +228,55 @@ HEXDIGIT [a-fA-F0-9]
"%" | "%" |
"^" | "^" |
"~" | "~" |
"=" { CMDlval.i = MakeToken< int >( CMDtext[ 0 ], yylineno ); return CMDtext[ 0 ]; } "=" { CMDlval.i = MakeToken<int>(CMDtext[0], yylineno); return CMDtext[0]; }
"in" { CMDlval.i = MakeToken< int >( rwIN, yylineno ); return(rwIN); } %{
"or" { CMDlval.i = MakeToken< int >( rwCASEOR, yylineno ); return(rwCASEOR); } // Reserved words — must be listed before {ID} to take priority.
"break" { CMDlval.i = MakeToken< int >( rwBREAK, yylineno ); return(rwBREAK); } // NOTE: "namespace" and "class" are intentionally NOT listed here.
"return" { CMDlval.i = MakeToken< int >( rwRETURN, yylineno ); return(rwRETURN); } // rwNAMESPACE and rwCLASS were previously declared as grammar tokens but
"else" { CMDlval.i = MakeToken< int >( rwELSE, yylineno ); return(rwELSE); } // had no productions that used them and no lexer rules that produced them.
"assert" { CMDlval.i = MakeToken< int >( rwASSERT, yylineno ); return(rwASSERT); } // They have been removed from the grammar. The words "namespace" and
"while" { CMDlval.i = MakeToken< int >( rwWHILE, yylineno ); return(rwWHILE); } // "class" therefore lex as plain IDENT tokens and can be used as object
"do" { CMDlval.i = MakeToken< int >( rwDO, yylineno ); return(rwDO); } // names or field names in script without causing parse errors. If you add
"if" { CMDlval.i = MakeToken< int >( rwIF, yylineno ); return(rwIF); } // syntax that consumes those keywords, add both the lexer rule and the
"foreach$" { CMDlval.i = MakeToken< int >( rwFOREACHSTR, yylineno ); return(rwFOREACHSTR); } // grammar token declaration at the same time.
"foreach" { CMDlval.i = MakeToken< int >( rwFOREACH, yylineno ); return(rwFOREACH); } %}
"for" { CMDlval.i = MakeToken< int >( rwFOR, yylineno ); return(rwFOR); } "in" { CMDlval.i = MakeToken<int>(rwIN, yylineno); return rwIN; }
"continue" { CMDlval.i = MakeToken< int >( rwCONTINUE, yylineno ); return(rwCONTINUE); } "or" { CMDlval.i = MakeToken<int>(rwCASEOR, yylineno); return rwCASEOR; }
"function" { CMDlval.i = MakeToken< int >( rwDEFINE, yylineno ); return(rwDEFINE); } "break" { CMDlval.i = MakeToken<int>(rwBREAK, yylineno); return rwBREAK; }
"new" { CMDlval.i = MakeToken< int >( rwDECLARE, yylineno ); return(rwDECLARE); } "return" { CMDlval.i = MakeToken<int>(rwRETURN, yylineno); return rwRETURN; }
"singleton" { CMDlval.i = MakeToken< int >( rwDECLARESINGLETON, yylineno ); return(rwDECLARESINGLETON); } "else" { CMDlval.i = MakeToken<int>(rwELSE, yylineno); return rwELSE; }
"datablock" { CMDlval.i = MakeToken< int >( rwDATABLOCK, yylineno ); return(rwDATABLOCK); } "assert" { CMDlval.i = MakeToken<int>(rwASSERT, yylineno); return rwASSERT; }
"case" { CMDlval.i = MakeToken< int >( rwCASE, yylineno ); return(rwCASE); } "while" { CMDlval.i = MakeToken<int>(rwWHILE, yylineno); return rwWHILE; }
"switch$" { CMDlval.i = MakeToken< int >( rwSWITCHSTR, yylineno ); return(rwSWITCHSTR); } "do" { CMDlval.i = MakeToken<int>(rwDO, yylineno); return rwDO; }
"switch" { CMDlval.i = MakeToken< int >( rwSWITCH, yylineno ); return(rwSWITCH); } "if" { CMDlval.i = MakeToken<int>(rwIF, yylineno); return rwIF; }
"default" { CMDlval.i = MakeToken< int >( rwDEFAULT, yylineno ); return(rwDEFAULT); } "foreach$" { CMDlval.i = MakeToken<int>(rwFOREACHSTR, yylineno); return rwFOREACHSTR; }
"package" { CMDlval.i = MakeToken< int >( rwPACKAGE, yylineno ); return(rwPACKAGE); } "foreach" { CMDlval.i = MakeToken<int>(rwFOREACH, yylineno); return rwFOREACH; }
"namespace" { CMDlval.i = MakeToken< int >( rwNAMESPACE, yylineno ); return(rwNAMESPACE); } "for" { CMDlval.i = MakeToken<int>(rwFOR, yylineno); return rwFOR; }
"true" { CMDlval.i = MakeToken< int >( 1, yylineno ); return INTCONST; } "continue" { CMDlval.i = MakeToken<int>(rwCONTINUE, yylineno); return rwCONTINUE; }
"false" { CMDlval.i = MakeToken< int >( 0, yylineno ); return INTCONST; } "function" { CMDlval.i = MakeToken<int>(rwDEFINE, yylineno); return rwDEFINE; }
{VAR} { return(Sc_ScanVar()); } "new" { CMDlval.i = MakeToken<int>(rwDECLARE, yylineno); return rwDECLARE; }
"singleton" { CMDlval.i = MakeToken<int>(rwDECLARESINGLETON, yylineno); return rwDECLARESINGLETON; }
"datablock" { CMDlval.i = MakeToken<int>(rwDATABLOCK, yylineno); return rwDATABLOCK; }
"case" { CMDlval.i = MakeToken<int>(rwCASE, yylineno); return rwCASE; }
"switch$" { CMDlval.i = MakeToken<int>(rwSWITCHSTR, yylineno); return rwSWITCHSTR; }
"switch" { CMDlval.i = MakeToken<int>(rwSWITCH, yylineno); return rwSWITCH; }
"default" { CMDlval.i = MakeToken<int>(rwDEFAULT, yylineno); return rwDEFAULT; }
"package" { CMDlval.i = MakeToken<int>(rwPACKAGE, yylineno); return rwPACKAGE; }
%{
// Boolean literals — return INTCONST so the parser treats them as integers.
%}
"true" { CMDlval.i = MakeToken<int>(1, yylineno); return INTCONST; }
"false" { CMDlval.i = MakeToken<int>(0, yylineno); return INTCONST; }
{ID} { return Sc_ScanIdent(); } {VAR} { return Sc_ScanVar(); }
0[xX]{HEXDIGIT}+ return(Sc_ScanHex()); {ID} { return Sc_ScanIdent(); }
{INTEGER} { CMDtext[CMDleng] = 0; CMDlval.i = MakeToken< int >( dAtoi(CMDtext), yylineno ); return INTCONST; } 0[xX]{HEXDIGIT}+ { return Sc_ScanHex(); }
{FLOAT} return Sc_ScanNum(); {INTEGER} { CMDtext[CMDleng] = 0;
{ILID} return(ILLEGAL_TOKEN); CMDlval.i = MakeToken<int>(dAtoi(CMDtext), yylineno);
. return(ILLEGAL_TOKEN); return INTCONST; }
{FLOAT} { return Sc_ScanNum(); }
{ILID} { return ILLEGAL_TOKEN; }
. { return ILLEGAL_TOKEN; }
%% %%
static const char *scanBuffer; static const char *scanBuffer;
@ -238,48 +284,69 @@ static const char *fileName;
static int scanIndex; static int scanIndex;
extern YYLTYPE CMDlloc; extern YYLTYPE CMDlloc;
const char * CMDGetCurrentFile() const char* CMDGetCurrentFile() { return fileName; }
int CMDGetCurrentLine() { return yylineno; }
void CMDSetScanBuffer(const char* sb, const char* fn)
{ {
return fileName; scanBuffer = sb;
fileName = fn;
scanIndex = 0;
yylineno = 1;
gCachedLineContextCount = -1; // re-read $scriptErrorLineCount for each file
lines.clear();
} }
int CMDGetCurrentLine() int CMDgetc()
{ {
return yylineno; int c = scanBuffer[scanIndex];
if (c)
scanIndex++;
else
c = -1; // EOF sentinel expected by YY_INPUT
return c;
}
int CMDwrap()
{
return 1;
} }
extern bool gConsoleSyntaxError; extern bool gConsoleSyntaxError;
void CMDerror(const char *format, ...) void CMDerror(const char* format, ...)
{ {
Compiler::gSyntaxError = true; Compiler::gSyntaxError = true;
const int BUFMAX = 1024; const int BUFMAX = 1024;
char tempBuf[BUFMAX]; char tempBuf[BUFMAX];
va_list args; va_list args;
va_start( args, format ); va_start(args, format);
#ifdef TORQUE_OS_WIN #ifdef TORQUE_OS_WIN
_vsnprintf( tempBuf, BUFMAX, format, args ); _vsnprintf(tempBuf, BUFMAX, format, args);
#else #else
vsnprintf( tempBuf, BUFMAX, format, args ); vsnprintf(tempBuf, BUFMAX, format, args);
#endif #endif
va_end(args); va_end(args);
if(fileName) if (fileName)
{ {
Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - %s", fileName, yylineno, tempBuf); Con::errorf(ConsoleLogEntry::Script, "%s Line: %d - %s",
// Update the script-visible error buffer. fileName, yylineno, tempBuf);
const char *prevStr = Con::getVariable("$ScriptError");
// Append to the script-visible error string and bump the hash so
// listeners know a new error arrived.
const char* prevStr = Con::getVariable("$ScriptError");
if (prevStr[0]) if (prevStr[0])
dSprintf(tempBuf, sizeof(tempBuf), "%s\n%s Line: %d - Syntax error.", prevStr, fileName, yylineno); dSprintf(tempBuf, sizeof(tempBuf), "%s\n%s Line: %d - Syntax error.",
prevStr, fileName, yylineno);
else else
dSprintf(tempBuf, sizeof(tempBuf), "%s Line: %d - Syntax error.", fileName, yylineno); dSprintf(tempBuf, sizeof(tempBuf), "%s Line: %d - Syntax error.",
fileName, yylineno);
Con::setVariable("$ScriptError", tempBuf); Con::setVariable("$ScriptError", tempBuf);
// We also need to mark that we came up with a new error. static S32 sScriptErrorHash = 1000;
static S32 sScriptErrorHash=1000;
Con::setIntVariable("$ScriptErrorHash", sScriptErrorHash++); Con::setIntVariable("$ScriptErrorHash", sScriptErrorHash++);
} }
else else
{ {
@ -287,30 +354,6 @@ void CMDerror(const char *format, ...)
} }
} }
void CMDSetScanBuffer(const char *sb, const char *fn)
{
scanBuffer = sb;
fileName = fn;
scanIndex = 0;
yylineno = 1;
lines.clear();
}
int CMDgetc()
{
int ret = scanBuffer[scanIndex];
if(ret)
scanIndex++;
else
ret = -1;
return ret;
}
int CMDwrap()
{
return 1;
}
static int Sc_ScanVar() static int Sc_ScanVar()
{ {
// Truncate the temp buffer... // Truncate the temp buffer...
@ -323,63 +366,60 @@ static int Sc_ScanVar()
static int charConv(int in) static int charConv(int in)
{ {
switch(in) switch (in)
{ {
case 'r': case 'r': return '\r';
return '\r'; case 'n': return '\n';
case 'n': case 't': return '\t';
return '\n'; default: return in;
case 't':
return '\t';
default:
return in;
} }
} }
static int getHexDigit(char c) static int getHexDigit(char c)
{ {
if(c >= '0' && c <= '9') if (c >= '0' && c <= '9') return c - '0';
return c - '0'; if (c >= 'A' && c <= 'F') return c - 'A' + 10;
if(c >= 'A' && c <= 'F') if (c >= 'a' && c <= 'f') return c - 'a' + 10;
return c - 'A' + 10;
if(c >= 'a' && c <= 'f')
return c - 'a' + 10;
return -1; return -1;
} }
static int Sc_ScanDocBlock() static int Sc_ScanDocBlock()
{ {
S32 len = dStrlen(CMDtext); S32 len = dStrlen(CMDtext);
char* text = (char *) consoleAlloc(len + 1); char* text = (char*)consoleAlloc(len + 1);
S32 line = yylineno; S32 line = yylineno;
for( S32 i = 0, j = 0; j <= len; j++ ) for (S32 i = 0, j = 0; j <= len; j++)
{ {
if( ( j <= (len - 2) ) && ( CMDtext[j] == '/' ) && ( CMDtext[j + 1] == '/' ) && ( CMDtext[j + 2] == '/' ) ) // Strip leading '///' on each doc line.
if ((j <= (len - 2)) &&
CMDtext[j] == '/' &&
CMDtext[j + 1] == '/' &&
CMDtext[j + 2] == '/')
{ {
j += 2; j += 2;
continue; continue;
} }
if (CMDtext[j] == '\r') continue;
if( CMDtext[j] == '\r' )
continue;
text[i++] = CMDtext[j]; text[i++] = CMDtext[j];
} }
CMDlval.str = MakeToken< char* >( text, line ); CMDlval.str = MakeToken<char*>(text, line);
return(DOCBLOCK); return DOCBLOCK;
} }
static int Sc_ScanString(int ret) static int Sc_ScanString(int ret)
{ {
CMDtext[CMDleng - 1] = 0; // CMDtext arrives as "content" or 'content' (with quotes).
if(!collapseEscape(CMDtext+1)) // Replace the closing quote with a null terminator so collapseEscape
// can work on the interior without seeing the delimiter.
CMDtext[CMDleng - 1] = '\0';
if (!collapseEscape(CMDtext + 1))
return -1; return -1;
dsize_t bufferLen = dStrlen( CMDtext ); dsize_t allocSize = dStrlen(CMDtext); // = 1 + content_len (see above)
char* buffer = ( char* ) consoleAlloc( bufferLen ); char* buffer = (char*)consoleAlloc(allocSize);
dStrcpy( buffer, CMDtext + 1, bufferLen ); dStrcpy(buffer, CMDtext + 1, allocSize); // skip the opening quote
CMDlval.str = MakeToken< char* >( buffer, yylineno ); CMDlval.str = MakeToken< char* >( buffer, yylineno );
return ret; return ret;
@ -387,22 +427,35 @@ static int Sc_ScanString(int ret)
static int Sc_ScanIdent() static int Sc_ScanIdent()
{ {
ConsoleBaseType *type;
CMDtext[CMDleng] = 0; CMDtext[CMDleng] = 0;
if((type = ConsoleBaseType::getTypeByName(CMDtext)) != NULL) // Check if the identifier is a registered engine type name (e.g. "Point3F").
ConsoleBaseType* type = ConsoleBaseType::getTypeByName(CMDtext);
if (type)
{ {
/* It's a type */ CMDlval.i = MakeToken<int>(type->getTypeID(), yylineno);
CMDlval.i = MakeToken< int >( type->getTypeID(), yylineno );
return TYPEIDENT; return TYPEIDENT;
} }
/* It's an identifier */ CMDlval.s = MakeToken<StringTableEntry>(StringTable->insert(CMDtext), yylineno);
CMDlval.s = MakeToken< StringTableEntry >( StringTable->insert(CMDtext), yylineno );
return IDENT; return IDENT;
} }
static int Sc_ScanNum()
{
CMDtext[CMDleng] = 0;
CMDlval.f = MakeToken<double>(dAtof(CMDtext), yylineno);
return FLTCONST;
}
static int Sc_ScanHex()
{
S32 val = 0;
dSscanf(CMDtext, "%x", &val);
CMDlval.i = MakeToken<int>(val, yylineno);
return INTCONST;
}
void expandEscape(char *dest, const char *src) void expandEscape(char *dest, const char *src)
{ {
U8 c; U8 c;
@ -570,21 +623,6 @@ bool collapseEscape(char *buf)
return true; return true;
} }
static int Sc_ScanNum()
{
CMDtext[CMDleng] = 0;
CMDlval.f = MakeToken< double >( dAtof(CMDtext), yylineno );
return(FLTCONST);
}
static int Sc_ScanHex()
{
S32 val = 0;
dSscanf(CMDtext, "%x", &val);
CMDlval.i = MakeToken< int >( val, yylineno );
return INTCONST;
}
void CMD_reset() void CMD_reset()
{ {
CMDrestart(NULL); CMDrestart(NULL);

View file

@ -178,15 +178,23 @@ U32 ReturnStmtNode::compileStmt(CodeStream& codeStream, U32 ip)
ExprNode* IfStmtNode::getSwitchOR(ExprNode* left, ExprNode* list, bool string) ExprNode* IfStmtNode::getSwitchOR(ExprNode* left, ExprNode* list, bool string)
{ {
ExprNode* nextExpr = (ExprNode*)list->getNext(); ExprNode* result;
ExprNode* test;
if (string) if (string)
test = StreqExprNode::alloc(left->dbgLineNumber, left, list, true); result = StreqExprNode::alloc(left->dbgLineNumber, left, list, true);
else else
test = IntBinaryExprNode::alloc(left->dbgLineNumber, opEQ, left, list); result = IntBinaryExprNode::alloc(left->dbgLineNumber, opEQ, left, list);
if (!nextExpr)
return test; for (ExprNode* walk = (ExprNode*)list->getNext(); walk; walk = (ExprNode*)walk->getNext())
return IntBinaryExprNode::alloc(test->dbgLineNumber, opOR, test, getSwitchOR(left, nextExpr, string)); {
ExprNode* nextExpr;
if (string)
nextExpr = StreqExprNode::alloc(left->dbgLineNumber, left, list, true);
else
nextExpr = IntBinaryExprNode::alloc(left->dbgLineNumber, opEQ, left, list);
result = IntBinaryExprNode::alloc(result->dbgLineNumber, opOR, result, nextExpr);
}
return result;
} }
void IfStmtNode::propagateSwitchExpr(ExprNode* left, bool string) void IfStmtNode::propagateSwitchExpr(ExprNode* left, bool string)
@ -405,11 +413,14 @@ U32 ConditionalExprNode::compile(CodeStream& codeStream, U32 ip, TypeReq type)
TypeReq ConditionalExprNode::getPreferredType() TypeReq ConditionalExprNode::getPreferredType()
{ {
// We can't make it calculate a type based on subsequent expressions as the expression TypeReq trueType = trueExpr->getPreferredType();
// could be a string, or just numbers. To play it safe, stringify anything that deals with TypeReq falseType = falseExpr->getPreferredType();
// a conditional, and let the interpreter cast as needed to other types safely.
// // Both numeric and the same → keep numeric
// See: Regression Test 7 in ScriptTest. It has a string result in the else portion of the ?: ternary. if (trueType == falseType && trueType != TypeReqNone)
return trueType;
// One is numeric, other is string/none → string (can't avoid conversion)
return TypeReqString; return TypeReqString;
} }
@ -874,7 +885,7 @@ U32 ConstantNode::compile(CodeStream& codeStream, U32 ip, TypeReq type)
case TypeReqNone: case TypeReqNone:
break; break;
} }
return ip; return codeStream.tell();
} }
TypeReq ConstantNode::getPreferredType() TypeReq ConstantNode::getPreferredType()
@ -1498,16 +1509,6 @@ TypeReq ObjectDeclNode::getPreferredType()
U32 FunctionDeclStmtNode::compileStmt(CodeStream& codeStream, U32 ip) U32 FunctionDeclStmtNode::compileStmt(CodeStream& codeStream, U32 ip)
{ {
// OP_FUNC_DECL
// func name
// namespace
// package
// hasBody?
// func end ip
// argc
// ident array[argc]
// code
// OP_RETURN_VOID
setCurrentStringTable(&getFunctionStringTable()); setCurrentStringTable(&getFunctionStringTable());
setCurrentFloatTable(&getFunctionFloatTable()); setCurrentFloatTable(&getFunctionFloatTable());
@ -1523,53 +1524,52 @@ U32 FunctionDeclStmtNode::compileStmt(CodeStream& codeStream, U32 ip)
} }
CodeBlock::smInFunction = true; CodeBlock::smInFunction = true;
precompileIdent(fnName); precompileIdent(fnName);
precompileIdent(nameSpace); precompileIdent(nameSpace);
precompileIdent(package); precompileIdent(package);
CodeBlock::smInFunction = false; CodeBlock::smInFunction = false;
setCurrentStringTable(&getGlobalStringTable()); // -------------------------------------------------------------------------
// check for argument setup // Layout (all relative to the first word after OP_FUNC_DECL):
for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext()) // +0,+1 fnName STE
{ // +2,+3 nameSpace STE
if (walk->defaultValue) // +4,+5 package STE
{ // +6 hasBody | (lineNumber << 1)
TypeReq walkType = walk->defaultValue->getPreferredType(); // +7 endIp (patched after codelets)
if (walkType == TypeReqNone) // +8 argc
walkType = TypeReqString; // +9 local variable count (patched after body)
// +10 .. +10+argc-1 register mappings
ip = walk->defaultValue->compile(codeStream, ip, walkType); // +10+argc .. +10+2*argc-1 arg flags
} // +10+2*argc .. +10+3*argc-1 default codelet IPs (patched below)
} // -------------------------------------------------------------------------
setCurrentStringTable(&getFunctionStringTable());
codeStream.emit(OP_FUNC_DECL); codeStream.emit(OP_FUNC_DECL);
codeStream.emitSTE(fnName); codeStream.emitSTE(fnName);
codeStream.emitSTE(nameSpace); codeStream.emitSTE(nameSpace);
codeStream.emitSTE(package); codeStream.emitSTE(package);
codeStream.emit(U32(bool(stmts != NULL) ? 1 : 0) + U32(dbgLineNumber << 1)); codeStream.emit(U32(bool(stmts != NULL) ? 1 : 0) + U32(dbgLineNumber << 1));
const U32 endIp = codeStream.emit(0); const U32 endIpSlot = codeStream.emit(0); // patched after codelets
codeStream.emit(argc); codeStream.emit(argc);
const U32 localNumVarsIP = codeStream.emit(0); const U32 localVarCountSlot = codeStream.emit(0); // patched after body
// Register mappings (one per arg, in declaration order).
for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext()) for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext())
{ {
StringTableEntry name = walk->varName; codeStream.emit(getFuncVars(dbgLineNumber)->lookup(walk->varName, dbgLineNumber));
codeStream.emit(getFuncVars(dbgLineNumber)->lookup(name, dbgLineNumber));
} }
// check for argument setup // Arg flags (bit 0x1 = this argument has a default expression).
for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext()) for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext())
{ {
U32 flags = 0; codeStream.emit(walk->defaultValue ? 1 : 0);
if (walk->defaultValue) flags |= 0x1;
codeStream.emit(flags);
} }
// Default codelet IP slots — emit 0 placeholders, patched after the body
Vector<U32> defaultOffsetSlots;
defaultOffsetSlots.setSize(argc);
for (S32 i = 0; i < argc; i++)
defaultOffsetSlots[i] = codeStream.emit(0);
CodeBlock::smInFunction = true; CodeBlock::smInFunction = true;
ip = compileBlock(stmts, codeStream, ip); ip = compileBlock(stmts, codeStream, ip);
@ -1579,9 +1579,32 @@ U32 FunctionDeclStmtNode::compileStmt(CodeStream& codeStream, U32 ip)
CodeBlock::smInFunction = false; CodeBlock::smInFunction = false;
codeStream.emit(OP_RETURN_VOID); codeStream.emit(OP_RETURN_VOID);
codeStream.patch(localVarCountSlot, getFuncVars(dbgLineNumber)->count());
codeStream.patch(localNumVarsIP, getFuncVars(dbgLineNumber)->count()); S32 argIdx = 0;
codeStream.patch(endIp, codeStream.tell()); for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext(), ++argIdx)
{
if (walk->defaultValue)
{
// Record where this codelet begins and patch the header slot.
const U32 codeletStart = codeStream.tell();
codeStream.patch(defaultOffsetSlots[argIdx], codeletStart);
// Compile the default expression, preferring its natural type.
// Fall back to string if the type is indeterminate.
TypeReq walkType = walk->defaultValue->getPreferredType();
if (walkType == TypeReqNone)
walkType = TypeReqString;
ip = walk->defaultValue->compile(codeStream, ip, walkType);
// Terminate the codelet. exec() handles this by taking the
// stack top as its return value and exiting the interpreter loop.
codeStream.emit(OP_DEFAULT_END);
}
}
codeStream.patch(endIpSlot, codeStream.tell());
setCurrentStringTable(&getGlobalStringTable()); setCurrentStringTable(&getGlobalStringTable());
setCurrentFloatTable(&getGlobalFloatTable()); setCurrentFloatTable(&getGlobalFloatTable());

File diff suppressed because it is too large Load diff

View file

@ -562,6 +562,7 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi
U32 iterDepth = 0; U32 iterDepth = 0;
ConsoleValue returnValue; ConsoleValue returnValue;
const bool isCodelet = (!argv && setFrame == -2);
incRefCount(); incRefCount();
F64* curFloatTable; F64* curFloatTable;
@ -615,24 +616,102 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi
Script::gEvalState.moveConsoleValue(reg, (value)); Script::gEvalState.moveConsoleValue(reg, (value));
} }
if (wantedArgc < fnArgc) // -----------------------------------------------------------------------
// Handle missing arguments.
//
// For each absent arg that carries a default (argFlags bit 0x1), we
// execute its codelet — a small bytecode expression compiled after the
// function body that ends with OP_DEFAULT_END.
//
// The codelet is run in its own minimal frame via a nested exec() call.
//
// If the default offset is 0, the argument had no default expression and
// the register keeps its zero-initialised value.
// -----------------------------------------------------------------------
if (wantedArgc < S32(fnArgc))
{ {
Namespace::Entry* temp = thisNamespace->lookup(thisFunctionName); Namespace::Entry* temp = thisNamespace->lookup(thisFunctionName);
for (; i < fnArgc; i++)
// Offset into the header where arg flags begin.
const U32 flagBase = ip + 10 + fnArgc;
// Offset into the header where default codelet IPs begin.
const U32 offsetBase = ip + 10 + 2 * fnArgc;
for (; i < S32(fnArgc); i++)
{ {
S32 reg = code[ip + (2 + 6 + 1 + 1) + i]; const S32 reg = code[ip + 10 + i];
if (temp->mArgFlags[i] & 0x1) const U32 argFlags = code[flagBase + i];
if (argFlags & 0x1) // argument has a default expression
{ {
ConsoleValue& value = temp->mDefaultValues[i]; const U32 codeletIp = (temp != NULL)
Script::gEvalState.moveConsoleValue(reg, (value)); ? temp->mDefaultOffsets[i]
: code[offsetBase + i];
if (codeletIp != 0)
{
// Execute the default codelet.
// argv=NULL → uses globalStrings / globalFloats (correct,
// since codelets are compiled into those tables).
// argc=0 → pushes a frame with 0 locals.
// setFrame=-2 → reference to the codelet frame.
Con::EvalResult result = exec(
codeletIp,
NULL, // functionName
NULL, // thisNamespace
0, // argc
NULL, // argv ← signals non-function (codelet) call
false, // noCalls
NULL, // packageName
-2 // setFrame
);
Script::gEvalState.moveConsoleValue(reg, result.value);
}
// codeletIp == 0: no default; register stays at its zero value.
} }
} }
} }
ip = ip + fnArgc + (2 + 6 + 1 + 1) + fnArgc; // -----------------------------------------------------------------------
// Advance ip to the start of the function BODY.
//
// The header now contains 3*fnArgc words after the fixed 10-word prefix:
// fnArgc words for register mappings
// fnArgc words for arg flags
// fnArgc words for default codelet IPs ← new
//
// Old: ip + 10 + 2*fnArgc
// New: ip + 10 + 3*fnArgc
// -----------------------------------------------------------------------
ip = ip + 10 + 3 * fnArgc;
curFloatTable = functionFloats; curFloatTable = functionFloats;
curStringTable = functionStrings; curStringTable = functionStrings;
curStringTableLen = functionStringsMaxLen; curStringTableLen = functionStringsMaxLen;
}
else if (isCodelet)
{
// ---- Codelet path ----------------------------------------------------
//
// The codelet was compiled into functionStrings/functionFloats (see
// compileStmt).
//
// functionStrings lives for the lifetime of the CodeBlock, which is
// always at least as long as any call to a function it contains.
curStringTable = functionStrings;
curFloatTable = functionFloats;
curStringTableLen = functionStringsMaxLen;
// Push a minimal empty frame. The codelet contains only an expression;
// it has no local variables of its own.
Script::gEvalState.pushFrame(NULL, NULL, 0);
popFrame = true;
// setFrame has served its purpose as a mode signal. Reset it so the
// telnet debugger guard `if (telDebuggerOn && setFrame < 0)` fires
// correctly (codelets should not push a telnet stack frame).
setFrame = -1;
} }
else else
{ {
@ -727,6 +806,7 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi
switch (instruction) switch (instruction)
{ {
case OP_FUNC_DECL: case OP_FUNC_DECL:
{
if (!noCalls) if (!noCalls)
{ {
fnName = CodeToSTE(code, ip); fnName = CodeToSTE(code, ip);
@ -739,7 +819,9 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi
ns = Namespace::global(); ns = Namespace::global();
else else
ns = Namespace::find(fnNamespace, fnPackage); ns = Namespace::find(fnNamespace, fnPackage);
ns->addFunction(fnName, this, hasBody ? ip : 0);// if no body, set the IP to 0
ns->addFunction(fnName, this, hasBody ? ip : 0);
if (curNSDocBlock) if (curNSDocBlock)
{ {
if (fnNamespace == StringTable->lookup(nsDocBlockClass)) if (fnNamespace == StringTable->lookup(nsDocBlockClass))
@ -752,45 +834,48 @@ Con::EvalResult CodeBlock::exec(U32 ip, const char* functionName, Namespace* thi
} }
} }
const U32 fnArgc = code[ip + 8];
U32 fnArgc = code[ip + 2 + 6];
// Compute pointer to the register mapping like exec() does.
U32 readPtr = ip + 2 + 6 + 1; // points to the slot after argc (localNumVarsIP)
readPtr += 1; // skip localNumVarsIP
readPtr += fnArgc; // skip register mapping
Namespace::Entry* temp = ns->lookup(fnName); Namespace::Entry* temp = ns->lookup(fnName);
temp->mArgFlags.setSize(fnArgc); temp->mArgFlags.setSize(fnArgc);
temp->mDefaultValues.setSize(fnArgc); temp->mDefaultOffsets.setSize(fnArgc);
// Arg flags: ip + 10 + fnArgc
// Codelet IPs: ip + 10 + 2*fnArgc
const U32 flagBase = ip + 10 + fnArgc;
const U32 offsetBase = ip + 10 + 2 * fnArgc;
// Read flags sequentially
for (U32 fa = 0; fa < fnArgc; ++fa) for (U32 fa = 0; fa < fnArgc; ++fa)
{ {
temp->mArgFlags[fa] = code[readPtr++]; temp->mArgFlags[fa] = code[flagBase + fa];
temp->mDefaultOffsets[fa] = code[offsetBase + fa];
} }
// this might seem weird but because of the order // No stack pops: mDefaultValues is gone.
// the stack accumulates consoleValues we cant be sure
// all args have a console value, and we need to pop
// the stack, do this in reverse order.
for (S32 fa = S32(fnArgc - 1); fa >= 0; fa--)
{
if (temp->mArgFlags[fa] & 0x1)
{
temp->mDefaultValues[fa] = stack[_STK--];
}
}
Namespace::relinkPackages(); Namespace::relinkPackages();
// If we had a docblock, it's definitely not valid anymore, so clear it out.
curFNDocBlock = NULL; curFNDocBlock = NULL;
//Con::printf("Adding function %s::%s (%d)", fnNamespace, fnName, ip);
} }
// Jump past header + body + codelets. endIp is at code[ip + 7].
ip = code[ip + 7]; ip = code[ip + 7];
break; break;
}
case OP_DEFAULT_END:
{
returnValue = stack[_STK];
_STK--;
while (iterDepth > 0)
{
iterStack[--_ITER].mIsStringIter = false;
--iterDepth;
_STK--;
}
goto execFinished;
}
case OP_CREATE_OBJECT: case OP_CREATE_OBJECT:
{ {
@ -2331,10 +2416,13 @@ execFinished:
} }
else else
{ {
delete[] const_cast<char*>(globalStrings); if (!isCodelet)
delete[] globalFloats; {
globalStrings = NULL; delete[] const_cast<char*>(globalStrings);
globalFloats = NULL; delete[] globalFloats;
globalStrings = NULL;
globalFloats = NULL;
}
} }
if (Con::getCurrentScriptModuleName()) if (Con::getCurrentScriptModuleName())

View file

@ -54,6 +54,7 @@ namespace Compiler
enum CompiledInstructions enum CompiledInstructions
{ {
OP_FUNC_DECL, OP_FUNC_DECL,
OP_DEFAULT_END,
OP_CREATE_OBJECT, OP_CREATE_OBJECT,
OP_ADD_OBJECT, OP_ADD_OBJECT,
OP_END_OBJECT, OP_END_OBJECT,