From 96f34902d095ad665963aa6396bdff8fce46c23a Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Sat, 16 Aug 2025 23:17:11 +0800 Subject: [PATCH 01/10] Migrate the incomplete user-defined literal feature from SVN --- sdk/angelscript/include/angelscript.h | 2 + sdk/angelscript/source/as_builder.cpp | 18 ++- sdk/angelscript/source/as_builder.h | 2 +- sdk/angelscript/source/as_compiler.h | 2 + sdk/angelscript/source/as_context.cpp | 1 - sdk/angelscript/source/as_parser.cpp | 86 ++++++++++++- sdk/angelscript/source/as_parser.h | 6 +- sdk/angelscript/source/as_scriptengine.cpp | 41 ++++++- sdk/angelscript/source/as_scriptengine.h | 12 ++ sdk/angelscript/source/as_scriptfunction.cpp | 14 +++ sdk/angelscript/source/as_scriptfunction.h | 20 +++ sdk/angelscript/source/as_scriptnode.h | 2 + sdk/tests/test_feature/source/main.cpp | 8 +- .../test_feature/source/test_compiler.cpp | 116 +++++++++++------- 14 files changed, 272 insertions(+), 58 deletions(-) diff --git a/sdk/angelscript/include/angelscript.h b/sdk/angelscript/include/angelscript.h index dbfe0d218..e14aba854 100644 --- a/sdk/angelscript/include/angelscript.h +++ b/sdk/angelscript/include/angelscript.h @@ -270,6 +270,7 @@ enum asEBehaviours // Value object memory management asBEHAVE_CONSTRUCT, asBEHAVE_LIST_CONSTRUCT, + asBEHAVE_LITERAL_CONSTRUCT, asBEHAVE_DESTRUCT, // Reference object memory management @@ -281,6 +282,7 @@ enum asEBehaviours // Object operators asBEHAVE_TEMPLATE_CALLBACK, + asBEHAVE_LITERAL_CALLBACK, // Garbage collection behaviours asBEHAVE_FIRST_GC, diff --git a/sdk/angelscript/source/as_builder.cpp b/sdk/angelscript/source/as_builder.cpp index a7eb9762c..ffa0737b4 100644 --- a/sdk/angelscript/source/as_builder.cpp +++ b/sdk/angelscript/source/as_builder.cpp @@ -1310,12 +1310,14 @@ asCGlobalProperty *asCBuilder::GetGlobalProperty(const char *prop, asSNameSpace return 0; } -int asCBuilder::ParseFunctionDeclaration(asCObjectType *objType, const char *decl, asCScriptFunction *func, bool isSystemFunction, asCArray *paramAutoHandles, bool *returnAutoHandle, asSNameSpace *ns, asCScriptNode **listPattern, asCObjectType **outParentClass) +int asCBuilder::ParseFunctionDeclaration(asCObjectType *objType, const char *decl, asCScriptFunction *func, bool isSystemFunction, asCArray *paramAutoHandles, bool *returnAutoHandle, asSNameSpace *ns, asCScriptNode **listPattern, asCScriptNode **literalPattern, asCObjectType **outParentClass) { asASSERT( objType || ns ); if (listPattern) *listPattern = 0; + if (literalPattern) + *literalPattern = 0; if (outParentClass) *outParentClass = 0; @@ -1327,7 +1329,7 @@ int asCBuilder::ParseFunctionDeclaration(asCObjectType *objType, const char *dec source.SetCode(TXT_SYSTEM_FUNCTION, decl, true); asCParser parser(this); - int r = parser.ParseFunctionDefinition(&source, listPattern != 0); + int r = parser.ParseFunctionDefinition(&source, listPattern != 0, literalPattern != 0); if( r < 0 ) return asINVALID_DECLARATION; @@ -1499,7 +1501,7 @@ int asCBuilder::ParseFunctionDeclaration(asCObjectType *objType, const char *dec n = n->next; } - // If the caller expects a list pattern, check for the existence, else report an error if not + // If the caller expects a list or literal pattern, check for the existence, else report an error if not if( listPattern ) { if( n == 0 || n->nodeType != snListPattern ) @@ -1510,6 +1512,16 @@ int asCBuilder::ParseFunctionDeclaration(asCObjectType *objType, const char *dec n->DisconnectParent(); } } + else if( literalPattern ) + { + if( n == 0 || n->nodeType != snLiteralPattern ) + return asINVALID_DECLARATION; + else + { + *literalPattern = n; + n->DisconnectParent(); + } + } else { if( n ) diff --git a/sdk/angelscript/source/as_builder.h b/sdk/angelscript/source/as_builder.h index 2c3127696..3ebd28ec8 100644 --- a/sdk/angelscript/source/as_builder.h +++ b/sdk/angelscript/source/as_builder.h @@ -146,7 +146,7 @@ class asCBuilder int VerifyProperty(asCDataType *dt, const char *decl, asCString &outName, asCDataType &outType, asSNameSpace *ns); int ParseDataType(const char *datatype, asCDataType *result, asSNameSpace *implicitNamespace, bool isReturnType = false); int ParseTemplateDecl(const char *decl, asCString *name, asCArray &subtypeNames); - int ParseFunctionDeclaration(asCObjectType *type, const char *decl, asCScriptFunction *func, bool isSystemFunction, asCArray *paramAutoHandles = 0, bool *returnAutoHandle = 0, asSNameSpace *ns = 0, asCScriptNode **outListPattern = 0, asCObjectType **outParentClass = 0); + int ParseFunctionDeclaration(asCObjectType *type, const char *decl, asCScriptFunction *func, bool isSystemFunction, asCArray *paramAutoHandles = 0, bool *returnAutoHandle = 0, asSNameSpace *ns = 0, asCScriptNode **outListPattern = 0, asCScriptNode **outLiteralPattern = 0, asCObjectType **outParentClass = 0); int ParseVariableDeclaration(const char *decl, asSNameSpace *implicitNamespace, asCString &outName, asSNameSpace *&outNamespace, asCDataType &outDt); int CheckNameConflict(const char *name, asCScriptNode *node, asCScriptCode *code, asSNameSpace *ns, bool isProperty, bool isVirtualProperty, bool isSharedIntf); int CheckNameConflictMember(asCTypeInfo *type, const char *name, asCScriptNode *node, asCScriptCode *code, bool isProperty, bool isVirtualProperty); diff --git a/sdk/angelscript/source/as_compiler.h b/sdk/angelscript/source/as_compiler.h index 22e8cb609..ad7597ea7 100644 --- a/sdk/angelscript/source/as_compiler.h +++ b/sdk/angelscript/source/as_compiler.h @@ -284,6 +284,8 @@ class asCCompiler int CompileInitListElement(asSListPatternNode *&patternNode, asCScriptNode *&valueNode, int bufferTypeId, short bufferVar, asUINT &bufferSize, asCByteCode &byteCode, int &elementsInSubList); int CompileAnonymousInitList(asCScriptNode *listNode, asCExprContext *ctx, const asCDataType &dt); + void CompileUserLiteral(); + int CallDefaultConstructor(const asCDataType &type, int offset, bool isObjectOnHeap, asCByteCode *bc, asCScriptNode *node, EVarGlobOrMem isVarGlobOrMem = asVGM_VARIABLE, bool derefDest = false); int CallCopyConstructor(asCDataType &type, int offset, bool isObjectOnHeap, asCExprContext *ctx, asCExprContext *arg, asCScriptNode *node, EVarGlobOrMem isVarGlobOrMem = asVGM_VARIABLE, bool derefDestination = false); void CallDestructor(asCDataType &type, int offset, bool isObjectOnHeap, asCByteCode *bc); diff --git a/sdk/angelscript/source/as_context.cpp b/sdk/angelscript/source/as_context.cpp index 784434003..3380d0db0 100644 --- a/sdk/angelscript/source/as_context.cpp +++ b/sdk/angelscript/source/as_context.cpp @@ -2303,7 +2303,6 @@ static const void *const dispatch_table[256] = { asDWORD *old = l_bc; #endif - // Remember to keep the cases in order and without // gaps, because that will make the switch faster. // It will be faster since only one lookup will be diff --git a/sdk/angelscript/source/as_parser.cpp b/sdk/angelscript/source/as_parser.cpp index 447af6fea..3cae48496 100644 --- a/sdk/angelscript/source/as_parser.cpp +++ b/sdk/angelscript/source/as_parser.cpp @@ -103,7 +103,7 @@ asCScriptNode *asCParser::GetScriptNode() return scriptNode; } -int asCParser::ParseFunctionDefinition(asCScriptCode *in_script, bool in_expectListPattern) +int asCParser::ParseFunctionDefinition(asCScriptCode *in_script, bool in_expectListPattern, bool in_expectLiteralPattern) { Reset(); @@ -116,6 +116,8 @@ int asCParser::ParseFunctionDefinition(asCScriptCode *in_script, bool in_expectL if( in_expectListPattern ) scriptNode->AddChildLast(ParseListPattern()); + if (in_expectLiteralPattern) + scriptNode->AddChildLast(ParseLiteralPattern()); // The declaration should end after the definition if( !isSyntaxError ) @@ -1294,6 +1296,65 @@ asCScriptNode *asCParser::ParseListPattern() return node; } +asCScriptNode* asCParser::ParseLiteralPattern() +{ + asCScriptNode* node = CreateNode(snLiteralPattern); + if (node == 0) return 0; + + sToken t1; + + GetToken(&t1); + if( t1.type != ttStartStatementBlock ) + { + Error(ExpectedToken("{"), &t1); + Error(InsteadFound(t1), &t1); + return node; + } + + node->UpdateSourcePos(t1.pos, t1.length); + + GetToken(&t1); + if (t1.type != ttStringConstant) + { + Error(TXT_EXPECTED_STRING, &t1); + Error(InsteadFound(t1), &t1); + return node; + } + else + RewindTo(&t1); + + node->AddChildLast(ParseStringConstant(false)); + + GetToken(&t1); + if ( t1.type == ttIdentifier ) + { + if( IdentifierIs(t1, "suffix") || IdentifierIs(t1, "prefix") ) + { + RewindTo(&t1); + node->AddChildLast(ParseIdentifier()); + } + else + { + const char* expected[2] = { "suffix", "prefix" }; + Error(ExpectedOneOf(expected, 2), &t1); + Error(InsteadFound(t1), &t1); + return node; + } + } + + GetToken(&t1); + if( t1.type != ttEndStatementBlock ) + { + Error(ExpectedToken("}"), &t1); + Error(InsteadFound(t1), &t1); + return node; + } + + node->UpdateSourcePos(t1.pos, t1.length); + + return node; +} + bool asCParser::IdentifierIs(const sToken &t, const char *str) { if( t.type != ttIdentifier ) @@ -1688,7 +1749,9 @@ asCScriptNode *asCParser::ParseExprValue() else if( t1.type == ttCast ) node->AddChildLast(ParseCast()); else if( IsConstant(t1.type) ) + { node->AddChildLast(ParseConstant()); + } else if( t1.type == ttOpenParenthesis) { GetToken(&t1); @@ -1898,7 +1961,7 @@ asCScriptNode *asCParser::ParseLambda() return node; } -asCScriptNode *asCParser::ParseStringConstant() +asCScriptNode *asCParser::ParseStringConstant(bool allowUserLiteral) { asCScriptNode *node = CreateNode(snConstant); if( node == 0 ) return 0; @@ -1915,6 +1978,15 @@ asCScriptNode *asCParser::ParseStringConstant() node->SetToken(&t); node->UpdateSourcePos(t.pos, t.length); + if (allowUserLiteral) + { + GetToken(&t); + RewindTo(&t); + + if (t.type == ttIdentifier) + node->AddChildLast(ParseUserLiteral()); + } + return node; } @@ -4121,6 +4193,16 @@ asCScriptNode *asCParser::ParseInitList() UNREACHABLE_RETURN; } +asCScriptNode* asCParser::ParseUserLiteral() +{ + asCScriptNode* node = CreateNode(snUserLiteral); + if (node == 0) return 0; + + node->AddChildLast(ParseIdentifier()); + + return node; +} + // BNF:1: VAR ::= ('private'|'protected')? TYPE IDENTIFIER (( '=' (INITLIST | EXPR)) | ARGLIST)? (',' IDENTIFIER (( '=' (INITLIST | EXPR)) | ARGLIST)?)* ';' asCScriptNode *asCParser::ParseDeclaration(bool isClassProp, bool isGlobalVar) { diff --git a/sdk/angelscript/source/as_parser.h b/sdk/angelscript/source/as_parser.h index 0e1571e90..5ca97ccc4 100644 --- a/sdk/angelscript/source/as_parser.h +++ b/sdk/angelscript/source/as_parser.h @@ -53,7 +53,7 @@ class asCParser asCParser(asCBuilder *builder); ~asCParser(); - int ParseFunctionDefinition(asCScriptCode *script, bool expectListPattern); + int ParseFunctionDefinition(asCScriptCode *script, bool expectListPattern, bool expectLiteralPattern); int ParsePropertyDeclaration(asCScriptCode *script); int ParseDataType(asCScriptCode *script, bool isReturnType); int ParseTemplateDecl(asCScriptCode *script); @@ -95,6 +95,7 @@ class asCParser void ParseMethodAttributes(asCScriptNode *funcNode); asCScriptNode *ParseListPattern(); + asCScriptNode *ParseLiteralPattern(); bool IsRealType(int tokenType); bool IsDataType(const sToken &token); @@ -130,6 +131,7 @@ class asCParser asCScriptNode *ParseClass(); asCScriptNode *ParseMixin(); asCScriptNode *ParseInitList(); + asCScriptNode *ParseUserLiteral(); asCScriptNode *ParseInterface(); asCScriptNode *ParseInterfaceMethod(); asCScriptNode *ParseVirtualPropertyDecl(bool isMethod, bool isInterface); @@ -158,7 +160,7 @@ class asCParser asCScriptNode *ParseConstructCall(); asCScriptNode *ParseCast(); asCScriptNode *ParseConstant(); - asCScriptNode *ParseStringConstant(); + asCScriptNode *ParseStringConstant(bool allowUserLiteral = true); asCScriptNode *ParseLambda(); bool FindTokenAfterType(sToken &nextToken); diff --git a/sdk/angelscript/source/as_scriptengine.cpp b/sdk/angelscript/source/as_scriptengine.cpp index 9169d4051..01347cd67 100644 --- a/sdk/angelscript/source/as_scriptengine.cpp +++ b/sdk/angelscript/source/as_scriptengine.cpp @@ -2174,13 +2174,17 @@ int asCScriptEngine::RegisterBehaviourToObjectType(asCObjectType *objectType, as asCScriptFunction func(this, 0, asFUNC_DUMMY); bool expectListPattern = behaviour == asBEHAVE_LIST_FACTORY || behaviour == asBEHAVE_LIST_CONSTRUCT; + bool expectLiteralPattern = behaviour == asBEHAVE_LITERAL_CONSTRUCT || behaviour == asBEHAVE_LITERAL_CALLBACK; asCScriptNode *listPattern = 0; + asCScriptNode* literalPattern = 0; asCBuilder bld(this, 0); - r = bld.ParseFunctionDeclaration(objectType, decl, &func, true, &internal.paramAutoHandles, &internal.returnAutoHandle, 0, expectListPattern ? &listPattern : 0); + r = bld.ParseFunctionDeclaration(objectType, decl, &func, true, &internal.paramAutoHandles, &internal.returnAutoHandle, 0, expectListPattern ? &listPattern : 0, expectLiteralPattern ? &literalPattern : 0); if( r < 0 ) { if( listPattern ) listPattern->Destroy(this); + if ( literalPattern ) + literalPattern->Destroy(this); return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); } func.name.Format("$beh%d", behaviour); @@ -2339,6 +2343,39 @@ int asCScriptEngine::RegisterBehaviourToObjectType(asCObjectType *objectType, as if( r < 0 ) return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); } + else if( behaviour == asBEHAVE_LITERAL_CONSTRUCT ) + { + func.name = "$literal"; + + // Verify that the return type is void + if (func.returnType != asCDataType::CreatePrimitive(ttVoid, false)) + { + if (literalPattern) + literalPattern->Destroy(this); + + return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } + + // Verify that it is a value type + if (!(func.objectType->flags & asOBJ_VALUE)) + { + if (literalPattern) + literalPattern->Destroy(this); + + WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE); + return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } + + func.id = AddBehaviourFunction(func, internal); + + r = scriptFunctions[func.id]->RegisterLiteralPattern(decl, literalPattern); + + if (literalPattern) + literalPattern->Destroy(this); + + if(r < 0) + return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } else if( behaviour == asBEHAVE_FACTORY || behaviour == asBEHAVE_LIST_FACTORY ) { if( behaviour == asBEHAVE_LIST_FACTORY ) @@ -6033,7 +6070,7 @@ int asCScriptEngine::RegisterFuncdef(const char *decl) asCBuilder bld(this, 0); asCObjectType *parentClass = 0; - int r = bld.ParseFunctionDeclaration(0, decl, func, false, 0, 0, defaultNamespace, 0, &parentClass); + int r = bld.ParseFunctionDeclaration(0, decl, func, false, 0, 0, defaultNamespace, 0, 0, &parentClass); if( r < 0 ) { // Set as dummy function before deleting diff --git a/sdk/angelscript/source/as_scriptengine.h b/sdk/angelscript/source/as_scriptengine.h index 455bf03c8..be41c4672 100644 --- a/sdk/angelscript/source/as_scriptengine.h +++ b/sdk/angelscript/source/as_scriptengine.h @@ -344,6 +344,18 @@ class asCScriptEngine : public asIScriptEngine // TODO: memory savings: Since there can be only one property with the same name a simpler symbol table should be used for global props asCSymbolTable registeredGlobalProps; // increases ref count asCSymbolTable registeredGlobalFuncs; + // User-defined literals and callback for compile-time check + struct + { + struct + { + asCMap uint64Literals; + asCMap doubleLiterals; + asCMap stringLiterals; + asCMap userLiterals; + } prefix, suffix; + } literals, literalsCallback; + // The template global function instances will be stored in this array asCArray generatedTemplateFunctionInstances; // increases ref count // This array contains a list of registered template global functions diff --git a/sdk/angelscript/source/as_scriptfunction.cpp b/sdk/angelscript/source/as_scriptfunction.cpp index 7cc8d94e1..3369eec06 100644 --- a/sdk/angelscript/source/as_scriptfunction.cpp +++ b/sdk/angelscript/source/as_scriptfunction.cpp @@ -342,6 +342,20 @@ int asCScriptFunction::ParseListPattern(asSListPatternNode *&target, const char return 0; } +int asCScriptFunction::RegisterLiteralPattern(const char* decl, asCScriptNode* literalPattern, asCString* outLiteral, bool* outIsPrefix) +{ + if (!literalPattern) + return asINVALID_ARG; + + (void)decl; + if (outLiteral) + (void)outLiteral; + if(outIsPrefix) + outIsPrefix = false; + + return 0; +} + // internal asCScriptFunction::asCScriptFunction(asCScriptEngine *engine, asCModule *mod, asEFuncType _funcType) { diff --git a/sdk/angelscript/source/as_scriptfunction.h b/sdk/angelscript/source/as_scriptfunction.h index 5b87e12ea..59059a029 100644 --- a/sdk/angelscript/source/as_scriptfunction.h +++ b/sdk/angelscript/source/as_scriptfunction.h @@ -90,6 +90,23 @@ struct asSListPatternDataTypeNode : public asSListPatternNode asCDataType dataType; }; +enum asELiteralPatternParamType +{ + asLPPT_UINT64, // f(uint64) + asLPPT_DOUBLE, // f(double) + asLPPT_STRING, // f(const string&in) + asLPPT_USER // f(int&in, uint) +}; + +struct asSLiteralPatternNode +{ + asSLiteralPatternNode(asCString name, asELiteralPatternParamType t, bool isPrefix) + : patternName(std::move(name)), paramType(t), prefix(isPrefix) {} + asCString patternName; + asELiteralPatternParamType paramType; + bool prefix; // otherwise the pattern is a suffix +}; + enum asEObjVarInfoOption { asOBJ_UNINIT, // object is uninitialized/destroyed @@ -275,6 +292,9 @@ class asCScriptFunction : public asIScriptFunction int RegisterListPattern(const char *decl, asCScriptNode *listPattern); int ParseListPattern(asSListPatternNode *&target, const char *decl, asCScriptNode *listPattern); + int RegisterLiteralPattern(const char *decl, asCScriptNode *literalPattern, asCString* outLiteral = 0, bool *outIsPrefix = 0); + int ParseLiteralPattern(); + bool DoesReturnOnStack() const; void JITCompile(); diff --git a/sdk/angelscript/source/as_scriptnode.h b/sdk/angelscript/source/as_scriptnode.h index 1e4810dd6..ce50d5d8a 100644 --- a/sdk/angelscript/source/as_scriptnode.h +++ b/sdk/angelscript/source/as_scriptnode.h @@ -81,6 +81,7 @@ enum eScriptNode snImport, snClass, snInitList, + snUserLiteral, snInterface, snEnum, snTypedef, @@ -92,6 +93,7 @@ enum eScriptNode snUsing, snMixin, snListPattern, + snLiteralPattern, snNamedArgument, snScope, snTryCatch diff --git a/sdk/tests/test_feature/source/main.cpp b/sdk/tests/test_feature/source/main.cpp index 091ace82a..4868df954 100644 --- a/sdk/tests/test_feature/source/main.cpp +++ b/sdk/tests/test_feature/source/main.cpp @@ -118,7 +118,7 @@ namespace TestCastOp { bool Test(); } namespace TestFor { bool Test(); } namespace TestBits { bool Test(); } namespace TestGetArgPtr { bool Test(); } -namespace TestCString { bool Test(); } +namespace TestCString { bool Test() { return false; } } namespace TestBool { bool Test(); } namespace TestInt8 { bool Test(); } namespace TestScriptMath { bool Test(); } @@ -172,7 +172,7 @@ namespace Test_Addon_ContextMgr { bool Test(); } namespace Test_Addon_ScriptFile { bool Test(); } namespace Test_Addon_DateTime { bool Test(); } namespace Test_Addon_StdString { bool Test(); } -namespace Test_Addon_ScriptSocket { bool Test(); } +namespace Test_Addon_ScriptSocket { bool Test() { return false; } } #include "utils.h" @@ -232,7 +232,7 @@ int allTests() InstallMemoryManager(); - if( Test_Addon_ScriptFile::Test() ) goto failed; else PRINTF("-- Test_Addon_ScriptFile passed\n"); + /*if( Test_Addon_ScriptFile::Test() ) goto failed; else PRINTF("-- Test_Addon_ScriptFile passed\n"); if( Test_Addon_ContextMgr::Test() ) goto failed; else PRINTF("-- Test_Addon_ContextMgr passed\n"); if( Test_Addon_ScriptGrid::Test() ) goto failed; else PRINTF("-- Test_Addon_ScriptGrid passed\n"); if( Test_Addon_WeakRef::Test() ) goto failed; else PRINTF("-- Test_Addon_WeakRef passed\n"); @@ -278,7 +278,7 @@ int allTests() if( TestInterface::Test() ) goto failed; else PRINTF("-- TestInterface passed\n"); if( TestCastOp::Test() ) goto failed; else PRINTF("-- TestCastOp passed\n"); if( Test2Modules() ) goto failed; else PRINTF("-- Test2Modules passed\n"); - if( TestArrayObject::Test() ) goto failed; else PRINTF("-- TestArrayObject passed\n"); + if( TestArrayObject::Test() ) goto failed; else PRINTF("-- TestArrayObject passed\n");*/ if( TestCompiler::Test() ) goto failed; else PRINTF("-- TestCompiler passed\n"); if( TestOptimize() ) goto failed; else PRINTF("-- TestOptimize passed\n"); if( TestConversion::Test() ) goto failed; else PRINTF("-- TestConversion passed\n"); diff --git a/sdk/tests/test_feature/source/test_compiler.cpp b/sdk/tests/test_feature/source/test_compiler.cpp index 726c3a53a..0feb08d85 100644 --- a/sdk/tests/test_feature/source/test_compiler.cpp +++ b/sdk/tests/test_feature/source/test_compiler.cpp @@ -24,6 +24,7 @@ bool Test7(); bool Test8(); bool Test9(); bool TestRetRef(); +bool TestUserLiteral(); struct A { A() { text = "hello"; } @@ -206,40 +207,6 @@ bool Test() COutStream out; asIScriptModule *mod; - // Passing const value types by value to constructors as implicit conversion - // https://www.gamedev.net/forums/topic/717880-asbehave_construct-with-custom-pod-string-type-requires-const/5468147/ - { - engine = asCreateScriptEngine(); - engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); - bout.buffer = ""; - - engine->RegisterObjectType("string_view", 1, asOBJ_VALUE | asOBJ_POD); - engine->RegisterStringFactory("const string_view", &testStringFactory); - engine->RegisterObjectType("type", 1, asOBJ_VALUE); - engine->RegisterObjectBehaviour("type", asBEHAVE_CONSTRUCT, "void f(const type &in)", asFUNCTION(0), asCALL_GENERIC); - engine->RegisterObjectBehaviour("type", asBEHAVE_CONSTRUCT, "void f(string_view)", asFUNCTION(0), asCALL_GENERIC); - engine->RegisterObjectBehaviour("type", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(0), asCALL_GENERIC); - engine->RegisterGlobalFunction("void test(type)", asFUNCTION(0), asCALL_GENERIC); - - mod = engine->GetModule("test", asGM_ALWAYS_CREATE); - mod->AddScriptSection("test", - "void main() { \n" - " type t('test'); \n" - " test('test'); \n" - "} \n"); - r = mod->Build(); - if (r < 0) - TEST_FAILED; - - engine->ShutDownAndRelease(); - - if (bout.buffer != "") - { - PRINTF("%s", bout.buffer.c_str()); - TEST_FAILED; - } - } - // Assign with invalid type // https://www.gamedev.net/forums/topic/717831-failed-assertion-on-const-type-w-invalid-assignment/ { @@ -5705,15 +5672,16 @@ bool Test() engine->Release(); } - fail = Test2() || fail; - fail = Test3() || fail; - fail = Test4() || fail; - fail = Test5() || fail; - fail = Test6() || fail; - fail = Test7() || fail; - fail = Test8() || fail; - fail = Test9() || fail; - fail = TestRetRef() || fail; + //fail = Test2() || fail; + //fail = Test3() || fail; + //fail = Test4() || fail; + //fail = Test5() || fail; + //fail = Test6() || fail; + //fail = Test7() || fail; + //fail = Test8() || fail; + //fail = Test9() || fail; + //fail = TestRetRef() || fail; + fail = TestUserLiteral() || fail; // Success return fail; @@ -6282,5 +6250,67 @@ bool TestRetRef() return fail; } +class Fixed32 +{ +public: + int value; // 16bit integer + 16bit fraction + + Fixed32(int v) : value(v) {} + + static void LiteralConstructInt(int intVal, void* mem) + { + new(mem) Fixed32(intVal); + } + + static void LiteralConstructDouble(double dblVal, void* mem) + { + int integer = int(dblVal); + int fraction = int(dblVal * double(1 << 15)); + + new(mem) Fixed32((integer << 15) + fraction); + } + + operator int() const + { + return value >> 15; + } +}; + +bool TestUserLiteral() +{ + bool fail = false; + CBufferedOutStream bout; + int r; + + asIScriptEngine* engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); + engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); + + // TODO: Remove this after user literal no longer depending on a registered string + RegisterScriptString(engine); + + engine->RegisterObjectType("Fixed32", sizeof(int), asGetTypeTraits() | asOBJ_POD | asOBJ_VALUE | asOBJ_APP_CLASS_ALLINTS); + r = engine->RegisterObjectBehaviour("Fixed32", asBEHAVE_LITERAL_CONSTRUCT, "void f(string&in) {'_f32' suffix}", asFUNCTION(Fixed32::LiteralConstructDouble), asCALL_CDECL_OBJLAST); + if (r < 0) + { + PRINTF("%s", bout.buffer.c_str()); + TEST_FAILED; + } + + asIScriptModule* m = engine->GetModule("Fixed32Literal", asGM_ALWAYS_CREATE); + m->AddScriptSection( + "Fixed32Literal", + "Fixed32 test() { return 3.14_f32; }" + ); + if (m->Build() < 0) + { + PRINTF("%s", bout.buffer.c_str()); + TEST_FAILED; + } + + engine->ShutDownAndRelease(); + + return fail; +} + } // namespace From 19ab9906dd6bca274ce6855af40decb31d6ad6b6 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Sun, 14 Sep 2025 18:39:56 +0800 Subject: [PATCH 02/10] Update test --- sdk/tests/test_feature/source/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/tests/test_feature/source/main.cpp b/sdk/tests/test_feature/source/main.cpp index 4868df954..cc8d32ec5 100644 --- a/sdk/tests/test_feature/source/main.cpp +++ b/sdk/tests/test_feature/source/main.cpp @@ -172,7 +172,7 @@ namespace Test_Addon_ContextMgr { bool Test(); } namespace Test_Addon_ScriptFile { bool Test(); } namespace Test_Addon_DateTime { bool Test(); } namespace Test_Addon_StdString { bool Test(); } -namespace Test_Addon_ScriptSocket { bool Test() { return false; } } +namespace Test_Addon_ScriptSocket { bool Test(); } #include "utils.h" From 3a027abc7b40ea886de30643ff242aa7d58b1a21 Mon Sep 17 00:00:00 2001 From: HenryAWE Date: Sun, 26 Oct 2025 11:51:50 +0800 Subject: [PATCH 03/10] Update CI --- .github/workflows/auto-test.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index 223de8cfa..23849a3f7 100644 --- a/.github/workflows/auto-test.yml +++ b/.github/workflows/auto-test.yml @@ -2,9 +2,7 @@ name: Automated test permissions: contents: read -on: - push: - branches: [ "master" ] +on: [push, pull_request] jobs: Linux-x86-64: From 8618daf0c22d9e4f1704cdf51a7563bb9ce2bde6 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Sun, 26 Oct 2025 11:59:02 +0800 Subject: [PATCH 04/10] Fix --- sdk/angelscript/source/as_scriptfunction.h | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/sdk/angelscript/source/as_scriptfunction.h b/sdk/angelscript/source/as_scriptfunction.h index 77bfdaf08..b8b64346a 100644 --- a/sdk/angelscript/source/as_scriptfunction.h +++ b/sdk/angelscript/source/as_scriptfunction.h @@ -45,6 +45,9 @@ #include "as_array.h" #include "as_datatype.h" #include "as_atomic.h" +#ifdef AS_CAN_USE_CPP11 +#include // std::move +#endif BEGIN_AS_NAMESPACE @@ -101,7 +104,13 @@ enum asELiteralPatternParamType struct asSLiteralPatternNode { asSLiteralPatternNode(asCString name, asELiteralPatternParamType t, bool isPrefix) - : patternName(std::move(name)), paramType(t), prefix(isPrefix) {} + : +#ifdef AS_CAN_USE_CPP11 + patternName(std::move(name)) +#else + patternName(name) +#endif + , paramType(t), prefix(isPrefix) {} asCString patternName; asELiteralPatternParamType paramType; bool prefix; // otherwise the pattern is a suffix From 7fb6b9e3f83f703419bcda7f10521e5bb10d09a4 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Sun, 26 Oct 2025 12:01:45 +0800 Subject: [PATCH 05/10] Fix --- sdk/angelscript/source/as_scriptfunction.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/angelscript/source/as_scriptfunction.cpp b/sdk/angelscript/source/as_scriptfunction.cpp index efc444819..4d924ba98 100644 --- a/sdk/angelscript/source/as_scriptfunction.cpp +++ b/sdk/angelscript/source/as_scriptfunction.cpp @@ -351,7 +351,7 @@ int asCScriptFunction::RegisterLiteralPattern(const char* decl, asCScriptNode* l if (outLiteral) (void)outLiteral; if(outIsPrefix) - outIsPrefix = false; + *outIsPrefix = false; return 0; } From f028a6167afd199f2154ed8dbc0b468328b18c27 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Mon, 27 Apr 2026 08:38:51 +0800 Subject: [PATCH 06/10] Commit existing code --- sdk/angelscript/source/as_compiler.cpp | 103 +++++++++++++++++- sdk/angelscript/source/as_compiler.h | 2 +- sdk/angelscript/source/as_objecttype.h | 1 + sdk/angelscript/source/as_parser.cpp | 17 +++ sdk/angelscript/source/as_scriptengine.cpp | 55 ++++++++++ sdk/angelscript/source/as_scriptfunction.cpp | 98 ++++++++++++++++- sdk/angelscript/source/as_scriptfunction.h | 5 +- .../test_feature/source/test_compiler.cpp | 10 +- 8 files changed, 277 insertions(+), 14 deletions(-) diff --git a/sdk/angelscript/source/as_compiler.cpp b/sdk/angelscript/source/as_compiler.cpp index e89a5273b..5a5e18f66 100644 --- a/sdk/angelscript/source/as_compiler.cpp +++ b/sdk/angelscript/source/as_compiler.cpp @@ -10702,6 +10702,99 @@ int asCCompiler::CompilePostFixExpression(asCArray *postfix, as return ret; } + +// Compile a user-defined literal expression (e.g., 3.14_f32) +// constNode is the snConstant node with an snUserLiteral child +// ctx contains the constant value in ctx->type from CompileExpressionValue +void asCCompiler::CompileUserLiteral(asCScriptNode *constNode, asCExprContext *ctx) +{ + // Get the suffix name from the snIdentifier child of snUserLiteral + asCScriptNode *userLiteralNode = constNode->firstChild; + asASSERT(userLiteralNode && userLiteralNode->nodeType == snUserLiteral); + asCScriptNode *identNode = userLiteralNode->firstChild; + asASSERT(identNode && identNode->nodeType == snIdentifier); + asCString suffix(&script->code[identNode->tokenPos], identNode->tokenLength); + + // Look up the function ID from the appropriate suffix map + int funcId = 0; + switch (constNode->tokenType) + { + case ttIntConstant: + case ttBitsConstant: + { + asSMapNode *cursor = 0; + if (engine->literals.suffix.uint64Literals.MoveTo(&cursor, suffix)) + funcId = cursor->value; + } + break; + case ttFloatConstant: + case ttDoubleConstant: + { + asSMapNode *cursor = 0; + if (engine->literals.suffix.doubleLiterals.MoveTo(&cursor, suffix)) + funcId = cursor->value; + } + break; + default: + break; + } + + if (funcId == 0 || funcId >= (int)engine->scriptFunctions.GetLength()) + { + asCString msg; + msg.Format(TXT_NO_MATCHING_SIGNATURES_TO_s, suffix.AddressOf()); + Error(msg, constNode); + ctx->type.SetDummy(); + return; + } + + asCScriptFunction *funcDesc = engine->scriptFunctions[funcId]; + asCObjectType *objType = funcDesc->objectType; + if (!objType || !(objType->flags & asOBJ_VALUE)) + { + asCString msg; + msg.Format(TXT_NO_MATCHING_SIGNATURES_TO_s, suffix.AddressOf()); + Error(msg, constNode); + ctx->type.SetDummy(); + return; + } + + // Allocate a temp variable for the constructed object + asCDataType targetType = asCDataType::CreateType(objType, false); + int offset = AllocateVariable(targetType, true); + + // Create an argument expression context with the constant value + asCExprContext *arg = asNEW(asCExprContext)(engine); + arg->type = ctx->type; + arg->exprNode = constNode; + + asCArray args; + args.PushLast(arg); + + // Prepare the argument and move it to the stack + PrepareFunctionCall(funcId, &ctx->bc, args); + MoveArgsToStack(funcId, &ctx->bc, args, false); + + // Push the object address for OBJLAST calling convention + ctx->bc.InstrSHORT(asBC_PSF, (short)offset); + + // Call the literal construct function + PerformFunctionCall(funcId, ctx, false, &args, objType); + + // Mark the temp variable as initialized + ctx->bc.ObjInfo(offset, asOBJ_INIT); + + // Set the return type to the constructed object + ctx->type.SetVariable(targetType, offset, true); + ctx->type.dataType.MakeReference(false); + + // Push the object address for the expression chain + ctx->bc.InstrSHORT(asBC_PSF, (short)offset); + + // Clean up + asDELETE(arg, asCExprContext); +} + int asCCompiler::CompileAnonymousInitList(asCScriptNode *node, asCExprContext *ctx, const asCDataType &dt) { asASSERT(node->nodeType == snInitList); @@ -11923,7 +12016,7 @@ int asCCompiler::CompileExpressionValue(asCScriptNode *node, asCExprContext *ctx size_t numScanned; double v = asStringScanDouble(value.AddressOf(), &numScanned); ctx->type.SetConstantD(asCDataType::CreatePrimitive(ttDouble, true), v); - asASSERT(numScanned == vnode->tokenLength); + if (numScanned != vnode->tokenLength && !(vnode->firstChild && vnode->firstChild->nodeType == snUserLiteral)) asASSERT(false); } else if( vnode->tokenType == ttTrue || vnode->tokenType == ttFalse ) @@ -12040,6 +12133,14 @@ int asCCompiler::CompileExpressionValue(asCScriptNode *node, asCExprContext *ctx } else asASSERT(false); + + // Check for user literal suffix on numeric constants + if (vnode->firstChild && + vnode->firstChild->nodeType == snUserLiteral) + { + CompileUserLiteral(vnode, ctx); + return 0; + } } else if( vnode->nodeType == snFunctionCall ) { diff --git a/sdk/angelscript/source/as_compiler.h b/sdk/angelscript/source/as_compiler.h index cd114e2ce..d0a613bdc 100644 --- a/sdk/angelscript/source/as_compiler.h +++ b/sdk/angelscript/source/as_compiler.h @@ -318,7 +318,7 @@ class asCCompiler int CompileInitListElement(asSListPatternNode *&patternNode, asCScriptNode *&valueNode, int bufferTypeId, short bufferVar, asUINT &bufferSize, asCByteCode &byteCode, int &elementsInSubList); int CompileAnonymousInitList(asCScriptNode *listNode, asCExprContext *ctx, const asCDataType &dt); - void CompileUserLiteral(); + void CompileUserLiteral(asCScriptNode *constNode, asCExprContext *ctx); int CallDefaultConstructor(const asCDataType &type, int offset, bool isObjectOnHeap, asCByteCode *bc, asCScriptNode *node, EVarGlobOrMem isVarGlobOrMem = asVGM_VARIABLE, bool derefDest = false); int CallCopyConstructor(asCDataType &type, int offset, bool isObjectOnHeap, asCExprContext *ctx, asCExprContext *arg, asCScriptNode *node, EVarGlobOrMem isVarGlobOrMem = asVGM_VARIABLE, bool derefDestination = false); diff --git a/sdk/angelscript/source/as_objecttype.h b/sdk/angelscript/source/as_objecttype.h index dcb3107fd..eb0524410 100644 --- a/sdk/angelscript/source/as_objecttype.h +++ b/sdk/angelscript/source/as_objecttype.h @@ -72,6 +72,7 @@ struct asSTypeBehaviour int factory; int listFactory; // Used for initialization lists only + int literalFactory; // Used for user literal suffixes int copyfactory; int construct; int copyconstruct; diff --git a/sdk/angelscript/source/as_parser.cpp b/sdk/angelscript/source/as_parser.cpp index 9f7cc37b1..cdfe83ea0 100644 --- a/sdk/angelscript/source/as_parser.cpp +++ b/sdk/angelscript/source/as_parser.cpp @@ -1814,6 +1814,23 @@ asCScriptNode *asCParser::ParseConstant() RewindTo(&t); } + // Check for user literal suffix on non-string constants (e.g. 3.14_f32) + if( t.type != ttStringConstant && + t.type != ttMultilineStringConstant && + t.type != ttHeredocStringConstant ) + { + sToken next; + GetToken(&next); + if( next.type == ttIdentifier && next.pos == t.pos + t.length ) + { + // Adjacent identifier without whitespace — user literal suffix + RewindTo(&next); + node->AddChildLast(ParseUserLiteral()); + } + else + RewindTo(&next); + } + return node; } diff --git a/sdk/angelscript/source/as_scriptengine.cpp b/sdk/angelscript/source/as_scriptengine.cpp index fbbb14c7a..f08104336 100644 --- a/sdk/angelscript/source/as_scriptengine.cpp +++ b/sdk/angelscript/source/as_scriptengine.cpp @@ -2366,8 +2366,30 @@ int asCScriptEngine::RegisterBehaviourToObjectType(asCObjectType *objectType, as return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); } + // Verify the parameters: exactly 1 parameter + if (func.parameterTypes.GetLength() != 1) + { + if (literalPattern) + literalPattern->Destroy(this); + + WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_LIST_FACTORY_EXPECTS_1_REF_PARAM); + return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } + + // Don't accept duplicates + if( beh->literalFactory ) + { + if( literalPattern ) + literalPattern->Destroy(this); + + return ConfigError(asALREADY_REGISTERED, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } + func.id = AddBehaviourFunction(func, internal); + // Store the function id in the behaviour + beh->literalFactory = func.id; + r = scriptFunctions[func.id]->RegisterLiteralPattern(decl, literalPattern); if (literalPattern) @@ -2376,6 +2398,39 @@ int asCScriptEngine::RegisterBehaviourToObjectType(asCObjectType *objectType, as if(r < 0) return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); } + else if( behaviour == asBEHAVE_LITERAL_CALLBACK ) + { + func.name = "$literalCallback"; + + // Verify that the return type is bool + if (func.returnType != asCDataType::CreatePrimitive(ttBool, false)) + { + if (literalPattern) + literalPattern->Destroy(this); + + return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } + + // Verify that it is a value type + if (!(func.objectType->flags & asOBJ_VALUE)) + { + if (literalPattern) + literalPattern->Destroy(this); + + WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_ILLEGAL_BEHAVIOUR_FOR_TYPE); + return ConfigError(asILLEGAL_BEHAVIOUR_FOR_TYPE, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } + + func.id = AddBehaviourFunction(func, internal); + + r = scriptFunctions[func.id]->RegisterLiteralPattern(decl, literalPattern, 0, 0, true); + + if (literalPattern) + literalPattern->Destroy(this); + + if(r < 0) + return ConfigError(r, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); + } else if( behaviour == asBEHAVE_FACTORY || behaviour == asBEHAVE_LIST_FACTORY ) { if( behaviour == asBEHAVE_LIST_FACTORY ) diff --git a/sdk/angelscript/source/as_scriptfunction.cpp b/sdk/angelscript/source/as_scriptfunction.cpp index 4d924ba98..b110d04ec 100644 --- a/sdk/angelscript/source/as_scriptfunction.cpp +++ b/sdk/angelscript/source/as_scriptfunction.cpp @@ -342,20 +342,99 @@ int asCScriptFunction::ParseListPattern(asSListPatternNode *&target, const char return 0; } -int asCScriptFunction::RegisterLiteralPattern(const char* decl, asCScriptNode* literalPattern, asCString* outLiteral, bool* outIsPrefix) +int asCScriptFunction::RegisterLiteralPattern(const char* decl, asCScriptNode* literalPatternNode, asCString* outLiteral, bool* outIsPrefix, bool isCallback) { - if (!literalPattern) + if (!literalPatternNode) return asINVALID_ARG; - (void)decl; + // Children: [0] = snConstant (string literal), [1] = optional snIdentifier ("suffix"/"prefix") + asCScriptNode *strNode = literalPatternNode->firstChild; + if (!strNode || strNode->nodeType != snConstant) + return asINVALID_ARG; + + // Extract the pattern string (strip surrounding quotes) + asCString literal(&decl[strNode->tokenPos + 1], strNode->tokenLength - 2); + + // Check for suffix/prefix identifier + bool isPrefix = false; + asCScriptNode *identNode = strNode->next; + if (identNode && identNode->nodeType == snIdentifier) + { + asCString identStr(&decl[identNode->tokenPos], identNode->tokenLength); + isPrefix = (identStr == "prefix"); + } + + // Determine parameter type from the first explicit parameter + asELiteralPatternParamType paramType = asLPPT_USER; + if (parameterTypes.GetLength() > 0) + { + const asCDataType &pt = parameterTypes[0]; + if (pt.IsIntegerType() || pt.IsUnsignedType() || pt.IsEnumType()) + paramType = asLPPT_UINT64; + else if (pt.IsFloatType() || pt.IsDoubleType()) + paramType = asLPPT_DOUBLE; + else if (pt.GetTypeInfo() && engine && + pt.GetTypeInfo() == reinterpret_cast(engine->stringType.GetTypeInfo())) + paramType = asLPPT_STRING; + } + + // Store in engine's maps (suffix only for now) + if (!isPrefix && engine) + { + if (isCallback) + { + switch (paramType) + { + case asLPPT_UINT64: + engine->literalsCallback.suffix.uint64Literals.Insert(literal, id); + break; + case asLPPT_DOUBLE: + engine->literalsCallback.suffix.doubleLiterals.Insert(literal, id); + break; + case asLPPT_STRING: + engine->literalsCallback.suffix.stringLiterals.Insert(literal, id); + break; + case asLPPT_USER: + engine->literalsCallback.suffix.userLiterals.Insert(literal, id); + break; + } + } + else + { + switch (paramType) + { + case asLPPT_UINT64: + engine->literals.suffix.uint64Literals.Insert(literal, id); + break; + case asLPPT_DOUBLE: + engine->literals.suffix.doubleLiterals.Insert(literal, id); + break; + case asLPPT_STRING: + engine->literals.suffix.stringLiterals.Insert(literal, id); + break; + case asLPPT_USER: + engine->literals.suffix.userLiterals.Insert(literal, id); + break; + } + } + } + + this->literalPattern = asNEW(asSLiteralPatternNode)(literal, paramType, isPrefix); + if (outLiteral) - (void)outLiteral; - if(outIsPrefix) - *outIsPrefix = false; + *outLiteral = literal; + if (outIsPrefix) + *outIsPrefix = isPrefix; return 0; } +int asCScriptFunction::ParseLiteralPattern() +{ + // TODO: Implement for bytecode serialization + return asNOT_SUPPORTED; +} + // internal asCScriptFunction::asCScriptFunction(asCScriptEngine *engine, asCModule *mod, asEFuncType _funcType) { @@ -388,6 +467,7 @@ asCScriptFunction::asCScriptFunction(asCScriptEngine *engine, asCModule *mod, as objForDelegate = 0; funcForDelegate = 0; listPattern = 0; + literalPattern = 0; funcdefType = 0; if( funcType == asFUNC_SCRIPT ) @@ -508,6 +588,12 @@ void asCScriptFunction::DestroyInternal() listPattern = n; } + // Deallocate literal pattern data + if( literalPattern ) + { + asDELETE(literalPattern, asSLiteralPatternNode); + literalPattern = 0; + } // Release template sub types for (asUINT n = 0; n < templateSubTypes.GetLength(); n++) if(templateSubTypes[n].GetTypeInfo()) diff --git a/sdk/angelscript/source/as_scriptfunction.h b/sdk/angelscript/source/as_scriptfunction.h index b8b64346a..3bf6ec4eb 100644 --- a/sdk/angelscript/source/as_scriptfunction.h +++ b/sdk/angelscript/source/as_scriptfunction.h @@ -303,7 +303,7 @@ class asCScriptFunction : public asIScriptFunction int RegisterListPattern(const char *decl, asCScriptNode *listPattern); int ParseListPattern(asSListPatternNode *&target, const char *decl, asCScriptNode *listPattern); - int RegisterLiteralPattern(const char *decl, asCScriptNode *literalPattern, asCString* outLiteral = 0, bool *outIsPrefix = 0); + int RegisterLiteralPattern(const char *decl, asCScriptNode *literalPattern, asCString* outLiteral = 0, bool *outIsPrefix = 0, bool isCallback = false); int ParseLiteralPattern(); bool DoesReturnOnStack() const; @@ -374,6 +374,9 @@ class asCScriptFunction : public asIScriptFunction // Used by list factory behaviour asSListPatternNode *listPattern; + // Used by literal construct behaviour + asSLiteralPatternNode *literalPattern; + // Used by asFUNC_SCRIPT struct ScriptFunctionData { diff --git a/sdk/tests/test_feature/source/test_compiler.cpp b/sdk/tests/test_feature/source/test_compiler.cpp index 3deed6e35..fc93d0c1e 100644 --- a/sdk/tests/test_feature/source/test_compiler.cpp +++ b/sdk/tests/test_feature/source/test_compiler.cpp @@ -384,6 +384,7 @@ bool Test() // assign even though it is a temporary obect (non lvalue) // Found in Frictional Games source code { +#if 0 // temporarily disabled engine = asCreateScriptEngine(); engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); bout.buffer = ""; @@ -428,8 +429,8 @@ bool Test() PRINTF("%s", bout.buffer.c_str()); TEST_FAILED; } +#endif // temporarily disabled } - // Test that expression with non lvalue used in assign op gives error // TODO: This test can only be re-enabled after I've added support to indicate if // certain types should allow assignment even though not being lvalue @@ -3577,6 +3578,7 @@ bool Test() // Problem reported by Ricky C // http://www.gamedev.net/topic/625484-c99-hexfloats/#entry4943881 { +#if 0 // temporarily disabled engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); engine->SetMessageCallback(asMETHOD(CBufferedOutStream,Callback), &bout, asCALL_THISCALL); @@ -3600,8 +3602,8 @@ bool Test() PRINTF("%s", bout.buffer.c_str()); TEST_FAILED; } - engine->Release(); +#endif // temporarily disabled } @@ -6299,11 +6301,9 @@ bool TestUserLiteral() asIScriptEngine* engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); - // TODO: Remove this after user literal no longer depending on a registered string - RegisterScriptString(engine); engine->RegisterObjectType("Fixed32", sizeof(int), asGetTypeTraits() | asOBJ_POD | asOBJ_VALUE | asOBJ_APP_CLASS_ALLINTS); - r = engine->RegisterObjectBehaviour("Fixed32", asBEHAVE_LITERAL_CONSTRUCT, "void f(string&in) {'_f32' suffix}", asFUNCTION(Fixed32::LiteralConstructDouble), asCALL_CDECL_OBJLAST); + r = engine->RegisterObjectBehaviour("Fixed32", asBEHAVE_LITERAL_CONSTRUCT, "void f(double) {'_f32' suffix}", asFUNCTION(Fixed32::LiteralConstructDouble), asCALL_CDECL_OBJLAST); if (r < 0) { PRINTF("%s", bout.buffer.c_str()); From 9be5b788bddc69140ae6e5e23449df16e8108e8c Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Mon, 27 Apr 2026 23:05:26 +0800 Subject: [PATCH 07/10] Update test --- .../test_feature/source/test_compiler.cpp | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/sdk/tests/test_feature/source/test_compiler.cpp b/sdk/tests/test_feature/source/test_compiler.cpp index 78a09564b..a3c94ddda 100644 --- a/sdk/tests/test_feature/source/test_compiler.cpp +++ b/sdk/tests/test_feature/source/test_compiler.cpp @@ -6281,7 +6281,7 @@ class Fixed32 static void LiteralConstructDouble(double dblVal, void* mem) { int integer = int(dblVal); - int fraction = int(dblVal * double(1 << 15)); + int fraction = int((dblVal - integer) * double(1 << 15)); new(mem) Fixed32((integer << 15) + fraction); } @@ -6321,6 +6321,26 @@ bool TestUserLiteral() TEST_FAILED; } + asIScriptFunction* f = m->GetFunctionByName("test"); + if (!f) + { + PRINTF("Failed to get test()\n"); + TEST_FAILED; + } + + asIScriptContext* ctx = engine->CreateContext(); + ctx->Prepare(f); + if (ctx->Execute() != asEXECUTION_FINISHED) { + PRINTF("Failed to execute\n"); + TEST_FAILED; + } + Fixed32 result = *(Fixed32*)ctx->GetAddressOfReturnValue(); + if (static_cast(result) != 3) { + PRINTF("Bad result %d (integral part = %d)\n", result.value, static_cast(result)); + TEST_FAILED; + } + ctx->Release(); + engine->ShutDownAndRelease(); return fail; From 1d55ec3ffa185a3600332101dd279d50b741c525 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Tue, 28 Apr 2026 04:14:48 +0800 Subject: [PATCH 08/10] Fix bytecode compatibility by moving literal behaviour enums to the end --- sdk/angelscript/include/angelscript.h | 6 ++-- sdk/angelscript/source/as_objecttype.h | 1 + sdk/angelscript/source/as_parser.cpp | 46 +++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/sdk/angelscript/include/angelscript.h b/sdk/angelscript/include/angelscript.h index 069844b76..bc329814b 100644 --- a/sdk/angelscript/include/angelscript.h +++ b/sdk/angelscript/include/angelscript.h @@ -272,7 +272,6 @@ enum asEBehaviours // Value object memory management asBEHAVE_CONSTRUCT, asBEHAVE_LIST_CONSTRUCT, - asBEHAVE_LITERAL_CONSTRUCT, asBEHAVE_DESTRUCT, // Reference object memory management @@ -284,7 +283,6 @@ enum asEBehaviours // Object operators asBEHAVE_TEMPLATE_CALLBACK, - asBEHAVE_LITERAL_CALLBACK, // Garbage collection behaviours asBEHAVE_FIRST_GC, @@ -295,6 +293,10 @@ enum asEBehaviours asBEHAVE_RELEASEREFS, asBEHAVE_LAST_GC = asBEHAVE_RELEASEREFS, + // User-defined literal support + asBEHAVE_LITERAL_CONSTRUCT, + asBEHAVE_LITERAL_CALLBACK, + asBEHAVE_MAX }; diff --git a/sdk/angelscript/source/as_objecttype.h b/sdk/angelscript/source/as_objecttype.h index eb0524410..cab24c8d4 100644 --- a/sdk/angelscript/source/as_objecttype.h +++ b/sdk/angelscript/source/as_objecttype.h @@ -68,6 +68,7 @@ struct asSTypeBehaviour gcReleaseAllReferences = 0; templateCallback = 0; getWeakRefFlag = 0; + literalFactory = 0; } int factory; diff --git a/sdk/angelscript/source/as_parser.cpp b/sdk/angelscript/source/as_parser.cpp index 15971eb68..307f7ff60 100644 --- a/sdk/angelscript/source/as_parser.cpp +++ b/sdk/angelscript/source/as_parser.cpp @@ -1818,6 +1818,7 @@ asCScriptNode *asCParser::ParseConstant() } // Check for user literal suffix on non-string constants (e.g. 3.14_f32) + // Only treat as user literal if the suffix is registered in the engine if( t.type != ttStringConstant && t.type != ttMultilineStringConstant && t.type != ttHeredocStringConstant ) @@ -1826,9 +1827,30 @@ asCScriptNode *asCParser::ParseConstant() GetToken(&next); if( next.type == ttIdentifier && next.pos == t.pos + t.length ) { - // Adjacent identifier without whitespace — user literal suffix - RewindTo(&next); - node->AddChildLast(ParseUserLiteral()); + asCString suffix(&script->code[next.pos], next.length); + bool isRegistered = false; + if( engine ) + { + asSMapNode *cursor = 0; + if( engine->literals.suffix.uint64Literals.MoveTo(&cursor, suffix) || + engine->literals.suffix.doubleLiterals.MoveTo(&cursor, suffix) || + engine->literals.suffix.stringLiterals.MoveTo(&cursor, suffix) || + engine->literals.suffix.userLiterals.MoveTo(&cursor, suffix) || + engine->literalsCallback.suffix.uint64Literals.MoveTo(&cursor, suffix) || + engine->literalsCallback.suffix.doubleLiterals.MoveTo(&cursor, suffix) || + engine->literalsCallback.suffix.stringLiterals.MoveTo(&cursor, suffix) || + engine->literalsCallback.suffix.userLiterals.MoveTo(&cursor, suffix) ) + { + isRegistered = true; + } + } + if( isRegistered ) + { + RewindTo(&next); + node->AddChildLast(ParseUserLiteral()); + } + else + RewindTo(&next); } else RewindTo(&next); @@ -2007,7 +2029,23 @@ asCScriptNode *asCParser::ParseStringConstant(bool allowUserLiteral) RewindTo(&t); if (t.type == ttIdentifier) - node->AddChildLast(ParseUserLiteral()); + { + asCString suffix(&script->code[t.pos], t.length); + bool isRegistered = false; + if( engine ) + { + asSMapNode *cursor = 0; + if( engine->literals.suffix.stringLiterals.MoveTo(&cursor, suffix) || + engine->literalsCallback.suffix.stringLiterals.MoveTo(&cursor, suffix) || + engine->literals.suffix.userLiterals.MoveTo(&cursor, suffix) || + engine->literalsCallback.suffix.userLiterals.MoveTo(&cursor, suffix) ) + { + isRegistered = true; + } + } + if( isRegistered ) + node->AddChildLast(ParseUserLiteral()); + } } return node; From 6392900087d051376fd4867166f768466f5ef15e Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Tue, 28 Apr 2026 04:45:11 +0800 Subject: [PATCH 09/10] Revert test workarounds, restore original test order --- sdk/tests/test_feature/source/main.cpp | 14 ++--- .../test_feature/source/test_compiler.cpp | 61 ++++++++++++++----- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/sdk/tests/test_feature/source/main.cpp b/sdk/tests/test_feature/source/main.cpp index 18594bfe0..d7b049bce 100644 --- a/sdk/tests/test_feature/source/main.cpp +++ b/sdk/tests/test_feature/source/main.cpp @@ -119,7 +119,7 @@ namespace TestCastOp { bool Test(); } namespace TestFor { bool Test(); } namespace TestBits { bool Test(); } namespace TestGetArgPtr { bool Test(); } -namespace TestCString { bool Test() { return false; } } +namespace TestCString { bool Test(); } namespace TestBool { bool Test(); } namespace TestInt8 { bool Test(); } namespace TestScriptMath { bool Test(); } @@ -234,10 +234,6 @@ int allTests() InstallMemoryManager(); - if( TestCompiler::Test() ) goto failed; else PRINTF("-- TestCompiler passed\n"); - if( TestOptimize() ) goto failed; else PRINTF("-- TestOptimize passed\n"); - if( TestConversion::Test() ) goto failed; else PRINTF("-- TestConversion passed\n"); - if( TestLiteral::Test() ) goto failed; else PRINTF("-- TestLiteral passed\n"); if( Test_Addon_Autowrapper::Test() ) goto failed; else PRINTF("-- Test_Addon_Autowrapper passed\n"); if( Test_Addon_ScriptFile::Test() ) goto failed; else PRINTF("-- Test_Addon_ScriptFile passed\n"); if( Test_Addon_ContextMgr::Test() ) goto failed; else PRINTF("-- Test_Addon_ContextMgr passed\n"); @@ -258,7 +254,7 @@ int allTests() if( Test_Addon_ScriptSocket::Test() ) goto failed; else PRINTF("-- Test_Addon_ScriptSocket passed\n"); #endif - + if( TestLiteral::Test() ) goto failed; else PRINTF("-- TestLiteral passed\n"); if( TestForEach::Test() ) goto failed; else PRINTF("-- TestForEach passed\n"); if( TestContext::Test() ) goto failed; else PRINTF("-- TestContext passed\n"); if( TestComposition::Test() ) goto failed; else PRINTF("-- TestComposition passed\n"); @@ -287,9 +283,9 @@ int allTests() if( TestCastOp::Test() ) goto failed; else PRINTF("-- TestCastOp passed\n"); if( Test2Modules() ) goto failed; else PRINTF("-- Test2Modules passed\n"); if( TestArrayObject::Test() ) goto failed; else PRINTF("-- TestArrayObject passed\n"); -/* if( TestCompiler::Test() ) goto failed; else PRINTF("-- TestCompiler passed\n"); */ -/* if( TestOptimize() ) goto failed; else PRINTF("-- TestOptimize passed\n");*/ -/* if( TestConversion::Test() ) goto failed; else PRINTF("-- TestConversion passed\n");*/ + if( TestCompiler::Test() ) goto failed; else PRINTF("-- TestCompiler passed\n"); + if( TestOptimize() ) goto failed; else PRINTF("-- TestOptimize passed\n"); + if( TestConversion::Test() ) goto failed; else PRINTF("-- TestConversion passed\n"); if( TestRegisterType::Test() ) goto failed; else PRINTF("-- TestRegisterType passed\n"); if( TestRefArgument::Test() ) goto failed; else PRINTF("-- TestRefArgument passed\n"); if( TestStream::Test() ) goto failed; else PRINTF("-- TestStream passed\n"); diff --git a/sdk/tests/test_feature/source/test_compiler.cpp b/sdk/tests/test_feature/source/test_compiler.cpp index a3c94ddda..fcabaa9f2 100644 --- a/sdk/tests/test_feature/source/test_compiler.cpp +++ b/sdk/tests/test_feature/source/test_compiler.cpp @@ -207,6 +207,40 @@ bool Test() COutStream out; asIScriptModule *mod; + // Passing const value types by value to constructors as implicit conversion + // https://www.gamedev.net/forums/topic/717880-asbehave_construct-with-custom-pod-string-type-requires-const/5468147/ + { + engine = asCreateScriptEngine(); + engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); + bout.buffer = ""; + + engine->RegisterObjectType("string_view", 1, asOBJ_VALUE | asOBJ_POD); + engine->RegisterStringFactory("const string_view", &testStringFactory); + engine->RegisterObjectType("type", 1, asOBJ_VALUE); + engine->RegisterObjectBehaviour("type", asBEHAVE_CONSTRUCT, "void f(const type &in)", asFUNCTION(0), asCALL_GENERIC); + engine->RegisterObjectBehaviour("type", asBEHAVE_CONSTRUCT, "void f(string_view)", asFUNCTION(0), asCALL_GENERIC); + engine->RegisterObjectBehaviour("type", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(0), asCALL_GENERIC); + engine->RegisterGlobalFunction("void test(type)", asFUNCTION(0), asCALL_GENERIC); + + mod = engine->GetModule("test", asGM_ALWAYS_CREATE); + mod->AddScriptSection("test", + "void main() { \n" + " type t('test'); \n" + " test('test'); \n" + "} \n"); + r = mod->Build(); + if (r < 0) + TEST_FAILED; + + engine->ShutDownAndRelease(); + + if (bout.buffer != "") + { + PRINTF("%s", bout.buffer.c_str()); + TEST_FAILED; + } + } + // Assign with invalid type // https://www.gamedev.net/forums/topic/717831-failed-assertion-on-const-type-w-invalid-assignment/ { @@ -384,7 +418,6 @@ bool Test() // assign even though it is a temporary obect (non lvalue) // Found in Frictional Games source code { -#if 0 // temporarily disabled engine = asCreateScriptEngine(); engine->SetMessageCallback(asMETHOD(CBufferedOutStream, Callback), &bout, asCALL_THISCALL); bout.buffer = ""; @@ -429,8 +462,8 @@ bool Test() PRINTF("%s", bout.buffer.c_str()); TEST_FAILED; } -#endif // temporarily disabled } + // Test that expression with non lvalue used in assign op gives error // TODO: This test can only be re-enabled after I've added support to indicate if // certain types should allow assignment even though not being lvalue @@ -3578,8 +3611,7 @@ bool Test() // Problem reported by Ricky C // http://www.gamedev.net/topic/625484-c99-hexfloats/#entry4943881 { -#if 0 // temporarily disabled - engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); + engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); engine->SetMessageCallback(asMETHOD(CBufferedOutStream,Callback), &bout, asCALL_THISCALL); bout.buffer = ""; @@ -3603,7 +3635,6 @@ bool Test() TEST_FAILED; } engine->Release(); -#endif // temporarily disabled } @@ -5688,16 +5719,16 @@ bool Test() engine->Release(); } - //fail = Test2() || fail; - //fail = Test3() || fail; - //fail = Test4() || fail; - //fail = Test5() || fail; - //fail = Test6() || fail; - //fail = Test7() || fail; - //fail = Test8() || fail; - //fail = Test9() || fail; - //fail = TestRetRef() || fail; - fail = TestUserLiteral() || fail; + fail = Test2() || fail; + fail = Test3() || fail; + fail = Test4() || fail; + fail = Test5() || fail; + fail = Test6() || fail; + fail = Test7() || fail; + fail = Test8() || fail; + fail = Test9() || fail; + fail = TestRetRef() || fail; + fail = TestUserLiteral() || fail; // Success return fail; From 768addab79301c3f765efc0853ae7b0671dd7878 Mon Sep 17 00:00:00 2001 From: AWE Henry Date: Tue, 28 Apr 2026 04:56:49 +0800 Subject: [PATCH 10/10] Error message --- sdk/angelscript/source/as_scriptengine.cpp | 2 +- sdk/angelscript/source/as_texts.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sdk/angelscript/source/as_scriptengine.cpp b/sdk/angelscript/source/as_scriptengine.cpp index e1d3de806..6735cd887 100644 --- a/sdk/angelscript/source/as_scriptengine.cpp +++ b/sdk/angelscript/source/as_scriptengine.cpp @@ -2372,7 +2372,7 @@ int asCScriptEngine::RegisterBehaviourToObjectType(asCObjectType *objectType, as if (literalPattern) literalPattern->Destroy(this); - WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_LIST_FACTORY_EXPECTS_1_REF_PARAM); + WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_LITERAL_CONSTRUCT_MUST_HAVE_EXACTLY_1_PARAM); return ConfigError(asINVALID_DECLARATION, "RegisterObjectBehaviour", objectType->name.AddressOf(), decl); } diff --git a/sdk/angelscript/source/as_texts.h b/sdk/angelscript/source/as_texts.h index cce5ef5a6..3379048ae 100644 --- a/sdk/angelscript/source/as_texts.h +++ b/sdk/angelscript/source/as_texts.h @@ -350,6 +350,7 @@ #define TXT_TEMPLATE_SUBTYPE_s_DOESNT_EXIST "Template subtype '%s' doesn't exist" #define TXT_TEMPLATE_LIST_FACTORY_EXPECTS_2_REF_PARAMS "Template list factory expects two reference parameters. The last is the pointer to the initialization buffer" #define TXT_LIST_FACTORY_EXPECTS_1_REF_PARAM "List factory expects only one reference parameter. The pointer to the initialization buffer will be passed in this parameter" +#define TXT_LITERAL_CONSTRUCT_MUST_HAVE_EXACTLY_1_PARAM "Literal construct must have exactly one parameter for the literal value" #define TXT_FAILED_READ_SUBTYPE_OF_TEMPLATE_s "Failed to read subtype of template type '%s'" #define TXT_FAILED_IN_FUNC_s_s_d "Failed in call to function '%s' (Code: %s, %d)" #define TXT_FAILED_IN_FUNC_s_WITH_s_s_d "Failed in call to function '%s' with '%s' (Code: %s, %d)"