diff --git a/.github/workflows/auto-test.yml b/.github/workflows/auto-test.yml index 2c0646df..adf35faf 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_GCC: diff --git a/sdk/angelscript/include/angelscript.h b/sdk/angelscript/include/angelscript.h index c76fd185..bc329814 100644 --- a/sdk/angelscript/include/angelscript.h +++ b/sdk/angelscript/include/angelscript.h @@ -293,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_builder.cpp b/sdk/angelscript/source/as_builder.cpp index b9a43030..8f82a465 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; @@ -1503,7 +1505,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 ) @@ -1514,6 +1516,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 4dff404d..ba939089 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.cpp b/sdk/angelscript/source/as_compiler.cpp index b5aee1ac..56fab0bf 100644 --- a/sdk/angelscript/source/as_compiler.cpp +++ b/sdk/angelscript/source/as_compiler.cpp @@ -10826,6 +10826,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); @@ -12050,7 +12143,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 ) @@ -12167,6 +12260,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 9169f508..73039efd 100644 --- a/sdk/angelscript/source/as_compiler.h +++ b/sdk/angelscript/source/as_compiler.h @@ -319,6 +319,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(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); 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 32fefecf..5a29a86b 100644 --- a/sdk/angelscript/source/as_context.cpp +++ b/sdk/angelscript/source/as_context.cpp @@ -2325,7 +2325,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_objecttype.h b/sdk/angelscript/source/as_objecttype.h index dcb3107f..cab24c8d 100644 --- a/sdk/angelscript/source/as_objecttype.h +++ b/sdk/angelscript/source/as_objecttype.h @@ -68,10 +68,12 @@ struct asSTypeBehaviour gcReleaseAllReferences = 0; templateCallback = 0; getWeakRefFlag = 0; + literalFactory = 0; } 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 6d29712d..307f7ff6 100644 --- a/sdk/angelscript/source/as_parser.cpp +++ b/sdk/angelscript/source/as_parser.cpp @@ -106,7 +106,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(); @@ -119,6 +119,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 ) @@ -1297,6 +1299,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 ) @@ -1691,7 +1752,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); @@ -1754,6 +1817,45 @@ asCScriptNode *asCParser::ParseConstant() RewindTo(&t); } + // 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 ) + { + sToken next; + GetToken(&next); + if( next.type == ttIdentifier && next.pos == t.pos + t.length ) + { + 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); + } + return node; } @@ -1904,7 +2006,7 @@ asCScriptNode *asCParser::ParseLambda() return node; } -asCScriptNode *asCParser::ParseStringConstant() +asCScriptNode *asCParser::ParseStringConstant(bool allowUserLiteral) { asCScriptNode *node = CreateNode(snConstant); if( node == 0 ) return 0; @@ -1921,6 +2023,31 @@ asCScriptNode *asCParser::ParseStringConstant() node->SetToken(&t); node->UpdateSourcePos(t.pos, t.length); + if (allowUserLiteral) + { + GetToken(&t); + RewindTo(&t); + + if (t.type == ttIdentifier) + { + 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; } @@ -4153,6 +4280,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 0e1571e9..5ca97ccc 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 f9973bdf..6735cd88 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,94 @@ 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); + } + + // Verify the parameters: exactly 1 parameter + if (func.parameterTypes.GetLength() != 1) + { + if (literalPattern) + literalPattern->Destroy(this); + + WriteMessage("", 0, 0, asMSGTYPE_ERROR, TXT_LITERAL_CONSTRUCT_MUST_HAVE_EXACTLY_1_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) + literalPattern->Destroy(this); + + 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 ) @@ -6072,7 +6164,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 5cb21aa0..61b2f275 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 4b87f108..121d6670 100644 --- a/sdk/angelscript/source/as_scriptfunction.cpp +++ b/sdk/angelscript/source/as_scriptfunction.cpp @@ -342,6 +342,99 @@ int asCScriptFunction::ParseListPattern(asSListPatternNode *&target, const char return 0; } +int asCScriptFunction::RegisterLiteralPattern(const char* decl, asCScriptNode* literalPatternNode, asCString* outLiteral, bool* outIsPrefix, bool isCallback) +{ + if (!literalPatternNode) + return asINVALID_ARG; + + // 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) + *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) { @@ -374,6 +467,7 @@ asCScriptFunction::asCScriptFunction(asCScriptEngine *engine, asCModule *mod, as objForDelegate = 0; funcForDelegate = 0; listPattern = 0; + literalPattern = 0; funcdefType = 0; if( funcType == asFUNC_SCRIPT ) @@ -494,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 01045a35..7a178984 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 @@ -91,6 +94,29 @@ 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) + : +#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 +}; + enum asEObjVarInfoOption { asOBJ_UNINIT, // object is uninitialized/destroyed @@ -281,6 +307,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, bool isCallback = false); + int ParseLiteralPattern(); + bool DoesReturnOnStack() const; void JITCompile(); @@ -350,6 +379,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/angelscript/source/as_scriptnode.h b/sdk/angelscript/source/as_scriptnode.h index 1e4810dd..ce50d5d8 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/angelscript/source/as_texts.h b/sdk/angelscript/source/as_texts.h index cce5ef5a..3379048a 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)" diff --git a/sdk/tests/test_feature/source/main.cpp b/sdk/tests/test_feature/source/main.cpp index 32b9f960..d7b049bc 100644 --- a/sdk/tests/test_feature/source/main.cpp +++ b/sdk/tests/test_feature/source/main.cpp @@ -174,7 +174,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(); } #include "utils.h" diff --git a/sdk/tests/test_feature/source/test_compiler.cpp b/sdk/tests/test_feature/source/test_compiler.cpp index 9d95e4ee..fcabaa9f 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"; } @@ -3610,7 +3611,7 @@ bool Test() // Problem reported by Ricky C // http://www.gamedev.net/topic/625484-c99-hexfloats/#entry4943881 { - engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); + engine = asCreateScriptEngine(ANGELSCRIPT_VERSION); engine->SetMessageCallback(asMETHOD(CBufferedOutStream,Callback), &bout, asCALL_THISCALL); bout.buffer = ""; @@ -3633,7 +3634,6 @@ bool Test() PRINTF("%s", bout.buffer.c_str()); TEST_FAILED; } - engine->Release(); } @@ -5728,6 +5728,7 @@ bool Test() fail = Test8() || fail; fail = Test9() || fail; fail = TestRetRef() || fail; + fail = TestUserLiteral() || fail; // Success return fail; @@ -6296,5 +6297,85 @@ 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 - integer) * 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); + + + engine->RegisterObjectType("Fixed32", sizeof(int), asGetTypeTraits() | asOBJ_POD | asOBJ_VALUE | asOBJ_APP_CLASS_ALLINTS); + 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()); + 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; + } + + 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; +} + } // namespace