diff --git a/IDEHelper/Compiler/BfAst.cpp b/IDEHelper/Compiler/BfAst.cpp index 17fa587b..3b8d9315 100644 --- a/IDEHelper/Compiler/BfAst.cpp +++ b/IDEHelper/Compiler/BfAst.cpp @@ -166,6 +166,11 @@ void BfStructuralVisitor::Visit(BfLiteralExpression* literalExpr) Visit(literalExpr->ToBase()); } +void BfStructuralVisitor::Visit(BfStringInterpolationExpression* stringInterpolationExpression) +{ + Visit(stringInterpolationExpression->ToBase()); +} + void BfStructuralVisitor::Visit(BfIdentifierNode* identifierNode) { Visit(identifierNode->ToBase()); diff --git a/IDEHelper/Compiler/BfAst.h b/IDEHelper/Compiler/BfAst.h index c62ca408..06c84012 100644 --- a/IDEHelper/Compiler/BfAst.h +++ b/IDEHelper/Compiler/BfAst.h @@ -261,6 +261,7 @@ class BfExpressionStatement; class BfAttributedExpression; class BfAttributedStatement; class BfLiteralExpression; +class BfStringInterpolationExpression; class BfBlock; class BfBlockExtension; class BfRootNode; @@ -441,8 +442,9 @@ public: virtual void Visit(BfEmptyStatement* emptyStmt); virtual void Visit(BfTokenNode* tokenNode); - virtual void Visit(BfTokenPairNode* tokenPairNode); + virtual void Visit(BfTokenPairNode* tokenPairNode); virtual void Visit(BfLiteralExpression* literalExpr); + virtual void Visit(BfStringInterpolationExpression* stringInterpolationExpression); virtual void Visit(BfIdentifierNode* identifierNode); virtual void Visit(BfAttributedIdentifierNode* attrIdentifierNode); virtual void Visit(BfQualifiedNameNode* nameNode); @@ -2099,6 +2101,16 @@ public: BfVariant mValue; }; BF_AST_DECL(BfLiteralExpression, BfExpression); +class BfStringInterpolationExpression : public BfExpression +{ +public: + BF_AST_TYPE(BfStringInterpolationExpression, BfExpression); + + BfAstNode* mAllocNode; + String* mString; + BfSizedArray mExpressions; +}; BF_AST_DECL(BfStringInterpolationExpression, BfExpression); + class BfInitializerExpression : public BfExpression { public: diff --git a/IDEHelper/Compiler/BfElementVisitor.cpp b/IDEHelper/Compiler/BfElementVisitor.cpp index 92c9b4b8..8655defc 100644 --- a/IDEHelper/Compiler/BfElementVisitor.cpp +++ b/IDEHelper/Compiler/BfElementVisitor.cpp @@ -208,6 +208,16 @@ void BfElementVisitor::Visit(BfLiteralExpression* literalExpr) Visit(literalExpr->ToBase()); } +void BfElementVisitor::Visit(BfStringInterpolationExpression* stringInterpolationExpression) +{ + Visit(stringInterpolationExpression->ToBase()); + VisitChild(stringInterpolationExpression->mAllocNode); + for (auto block : stringInterpolationExpression->mExpressions) + { + VisitChild(block); + } +} + void BfElementVisitor::Visit(BfIdentifierNode* identifierNode) { Visit(identifierNode->ToBase()); diff --git a/IDEHelper/Compiler/BfElementVisitor.h b/IDEHelper/Compiler/BfElementVisitor.h index f72e79e5..4d3d91a7 100644 --- a/IDEHelper/Compiler/BfElementVisitor.h +++ b/IDEHelper/Compiler/BfElementVisitor.h @@ -37,6 +37,7 @@ public: virtual void Visit(BfTokenNode* tokenNode); virtual void Visit(BfTokenPairNode* tokenPairNode); virtual void Visit(BfLiteralExpression* literalExpr); + virtual void Visit(BfStringInterpolationExpression* stringInterpolationExpression); virtual void Visit(BfIdentifierNode* identifierNode); virtual void Visit(BfAttributedIdentifierNode* attrIdentifierNode); virtual void Visit(BfQualifiedNameNode* nameNode); diff --git a/IDEHelper/Compiler/BfExprEvaluator.cpp b/IDEHelper/Compiler/BfExprEvaluator.cpp index 75c8ac93..f0597264 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.cpp +++ b/IDEHelper/Compiler/BfExprEvaluator.cpp @@ -3200,6 +3200,47 @@ void BfExprEvaluator::Visit(BfLiteralExpression* literalExpr) GetLiteral(literalExpr, literalExpr->mValue); } +void BfExprEvaluator::Visit(BfStringInterpolationExpression* stringInterpolationExpression) +{ + if ((mBfEvalExprFlags & BfEvalExprFlags_StringInterpolateFormat) != 0) + { + BfVariant variant; + variant.mTypeCode = BfTypeCode_CharPtr; + variant.mString = stringInterpolationExpression->mString; + GetLiteral(stringInterpolationExpression, variant); + return; + } + + if (stringInterpolationExpression->mAllocNode != NULL) + { + auto stringType = mModule->ResolveTypeDef(mModule->mCompiler->mStringTypeDef)->ToTypeInstance(); + + BfTokenNode* newToken = NULL; + BfAllocTarget allocTarget = ResolveAllocTarget(stringInterpolationExpression->mAllocNode, newToken); + + CreateObject(NULL, stringInterpolationExpression->mAllocNode, stringType); + BfTypedValue newString = mResult; + BF_ASSERT(newString); + + SizedArray argExprs; + argExprs.Add(stringInterpolationExpression); + BfSizedArray sizedArgExprs(argExprs); + BfResolvedArgs argValues(&sizedArgExprs); + ResolveArgValues(argValues, BfResolveArgFlag_InsideStringInterpolationAlloc); + MatchMethod(stringInterpolationExpression, NULL, newString, false, false, "AppendF", argValues, NULL); + mResult = newString; + + return; + } + + mModule->Fail("Invalid use of string interpolation expression. Consider adding an allocation specifier such as 'scope'.", stringInterpolationExpression); + + for (auto block : stringInterpolationExpression->mExpressions) + { + VisitChild(block); + } +} + BfTypedValue BfExprEvaluator::LoadLocal(BfLocalVariable* varDecl, bool allowRef) { if (!mModule->mIsInsideAutoComplete) @@ -4413,23 +4454,44 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr autoComplete->mIgnoreFixits = true; } - for (int argIdx = 0; argIdx < argCount ; argIdx++) + int deferredArgIdx = 0; + SizedArray deferredArgs; + + int argIdx = 0; + //for (int argIdx = 0; argIdx < argCount ; argIdx++) + + while (true) { //printf("Args: %p %p %d\n", resolvedArgs.mArguments, resolvedArgs.mArguments->mVals, resolvedArgs.mArguments->mSize); BfExpression* argExpr = NULL; - if (argIdx < resolvedArgs.mArguments->size()) - argExpr = (*resolvedArgs.mArguments)[argIdx]; + + int curArgIdx = -1; + + if (deferredArgIdx < deferredArgs.size()) + { + argExpr = deferredArgs[deferredArgIdx++]; + } + else if (argIdx >= argCount) + { + break; + } + else + { + curArgIdx = argIdx++; + if (curArgIdx < resolvedArgs.mArguments->size()) + argExpr = (*resolvedArgs.mArguments)[curArgIdx]; + } if (argExpr == NULL) { - if (argIdx == 0) + if (curArgIdx == 0) { if (resolvedArgs.mOpenToken != NULL) mModule->FailAfter("Expression expected", resolvedArgs.mOpenToken); } else if (resolvedArgs.mCommas != NULL) - mModule->FailAfter("Expression expected", (*resolvedArgs.mCommas)[argIdx - 1]); + mModule->FailAfter("Expression expected", (*resolvedArgs.mCommas)[curArgIdx - 1]); } if (auto typedValueExpr = BfNodeDynCast(argExpr)) @@ -4440,7 +4502,7 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr resolvedArgs.mResolvedArgs.push_back(resolvedArg); continue; } - + BfResolvedArg resolvedArg; BfExprEvaluator exprEvaluator(mModule); exprEvaluator.mResolveGenericParam = (flags & BfResolveArgFlag_AllowUnresolvedTypes) == 0; @@ -4448,6 +4510,16 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr bool handled = false; bool evaluated = false; + if (auto interpolateExpr = BfNodeDynCastExact(argExpr)) + { + if ((interpolateExpr->mAllocNode == NULL) || ((flags & BfResolveArgFlag_InsideStringInterpolationAlloc) != 0)) + { + resolvedArg.mArgFlags = (BfArgFlags)(resolvedArg.mArgFlags | BfArgFlag_StringInterpolateFormat); + for (auto innerExpr : interpolateExpr->mExpressions) + deferredArgs.Add(innerExpr); + } + } + bool deferParamEval = false; if ((flags & BfResolveArgFlag_DeferParamEval) != 0) { @@ -4542,6 +4614,10 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr if (!evaluated) { exprEvaluator.mBfEvalExprFlags = (BfEvalExprFlags)(exprEvaluator.mBfEvalExprFlags | BfEvalExprFlags_AllowParamsExpr); + + if ((resolvedArg.mArgFlags & BfArgFlag_StringInterpolateFormat) != 0) + exprEvaluator.mBfEvalExprFlags = (BfEvalExprFlags)(exprEvaluator.mBfEvalExprFlags | BfEvalExprFlags_StringInterpolateFormat); + exprEvaluator.Evaluate(argExpr, false, false, true); } @@ -4595,7 +4671,7 @@ void BfExprEvaluator::ResolveArgValues(BfResolvedArgs& resolvedArgs, BfResolveAr } resolvedArg.mExpression = argExpr; resolvedArgs.mResolvedArgs.push_back(resolvedArg); - } + } if (autoComplete != NULL) autoComplete->mIgnoreFixits = hadIgnoredFixits; @@ -12035,14 +12111,19 @@ void BfExprEvaluator::CheckObjectCreateTypeRef(BfType* expectingType, BfAstNode* } void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) +{ + CreateObject(objCreateExpr, objCreateExpr->mNewNode, NULL); +} + +void BfExprEvaluator::CreateObject(BfObjectCreateExpression* objCreateExpr, BfAstNode* allocNode, BfType* wantAllocType) { auto autoComplete = GetAutoComplete(); - if ((autoComplete != NULL) && (objCreateExpr->mTypeRef != NULL)) + if ((autoComplete != NULL) && (objCreateExpr != NULL) && (objCreateExpr->mTypeRef != NULL)) { autoComplete->CheckTypeRef(objCreateExpr->mTypeRef, false, true); } - if ((autoComplete != NULL) && (objCreateExpr->mOpenToken != NULL) && (objCreateExpr->mCloseToken != NULL) && + if ((autoComplete != NULL) && (objCreateExpr != NULL) && (objCreateExpr->mOpenToken != NULL) && (objCreateExpr->mCloseToken != NULL) && (objCreateExpr->mOpenToken->mToken == BfToken_LBrace) && (autoComplete->CheckFixit(objCreateExpr->mOpenToken))) { auto refNode = objCreateExpr->mOpenToken; @@ -12055,28 +12136,28 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) } } - CheckObjectCreateTypeRef(mExpectingType, objCreateExpr->mNewNode); + CheckObjectCreateTypeRef(mExpectingType, allocNode); BfAttributeState attributeState; attributeState.mTarget = BfAttributeTargets_Alloc; SetAndRestoreValue prevAttributeState(mModule->mAttributeState, &attributeState); BfTokenNode* newToken = NULL; - BfAllocTarget allocTarget = ResolveAllocTarget(objCreateExpr->mNewNode, newToken, &attributeState.mCustomAttributes); + BfAllocTarget allocTarget = ResolveAllocTarget(allocNode, newToken, &attributeState.mCustomAttributes); bool isScopeAlloc = newToken->GetToken() == BfToken_Scope; bool isAppendAlloc = newToken->GetToken() == BfToken_Append; bool isStackAlloc = (newToken->GetToken() == BfToken_Stack) || (isScopeAlloc); bool isArrayAlloc = false;// (objCreateExpr->mArraySizeSpecifier != NULL); - bool isRawArrayAlloc = (objCreateExpr->mStarToken != NULL); + bool isRawArrayAlloc = (objCreateExpr != NULL) && (objCreateExpr->mStarToken != NULL); if (isScopeAlloc) { if ((mBfEvalExprFlags & BfEvalExprFlags_FieldInitializer) != 0) { - mModule->Warn(0, "This allocation will only be in scope during the constructor. Consider using a longer-term allocation such as 'new'", objCreateExpr->mNewNode); + mModule->Warn(0, "This allocation will only be in scope during the constructor. Consider using a longer-term allocation such as 'new'", allocNode); } - if (objCreateExpr->mNewNode == newToken) // Scope, no target specified + if (allocNode == newToken) // Scope, no target specified { if (mModule->mParentNodeEntry != NULL) { @@ -12086,7 +12167,7 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) { // If we are assigning this to a property then it's possible the property setter can actually deal with a temporary allocation so no warning in that case if ((mBfEvalExprFlags & BfEvalExprFlags_PendingPropSet) == 0) - mModule->Warn(0, "This allocation will immediately go out of scope. Consider specifying a wider scope target such as 'scope::'", objCreateExpr->mNewNode); + mModule->Warn(0, "This allocation will immediately go out of scope. Consider specifying a wider scope target such as 'scope::'", allocNode); } } } @@ -12102,7 +12183,12 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) BfType* unresolvedTypeRef = NULL; BfType* resolvedTypeRef = NULL; - if (objCreateExpr->mTypeRef == NULL) + if (wantAllocType != NULL) + { + unresolvedTypeRef = wantAllocType; + resolvedTypeRef = wantAllocType; + } + else if (objCreateExpr->mTypeRef == NULL) { if ((!mExpectingType) || (!mExpectingType->IsArray())) { @@ -12118,7 +12204,7 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) } } else - { + { if ((objCreateExpr->mTypeRef->IsExact()) && (mExpectingType != NULL)) { //mModule->SetElementType(objCreateExpr->mTypeRef, BfSourceElementType_TypeRef); @@ -12287,12 +12373,12 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) { if (!mModule->mCurTypeInstance->IsObject()) { - mModule->Fail("Append allocations are only allowed in classes", objCreateExpr->mNewNode); + mModule->Fail("Append allocations are only allowed in classes", allocNode); isAppendAlloc = false; } else if ((mBfEvalExprFlags & BfEvalExprFlags_VariableDeclaration) == 0) { - mModule->Fail("Append allocations are only allowed as local variable initializers in constructor body", objCreateExpr->mNewNode); + mModule->Fail("Append allocations are only allowed as local variable initializers in constructor body", allocNode); isAppendAlloc = false; } else @@ -12300,22 +12386,22 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) auto methodDef = mModule->mCurMethodInstance->mMethodDef; if (methodDef->mMethodType == BfMethodType_CtorCalcAppend) { - mModule->Fail("Append allocations are only allowed as local variable declarations in the main method body", objCreateExpr->mNewNode); + mModule->Fail("Append allocations are only allowed as local variable declarations in the main method body", allocNode); isAppendAlloc = false; } else if (!methodDef->mHasAppend) { - mModule->Fail("Append allocations can only be used on constructors with [AllowAppend] specified", objCreateExpr->mNewNode); + mModule->Fail("Append allocations can only be used on constructors with [AllowAppend] specified", allocNode); isAppendAlloc = false; } else if (methodDef->mMethodType != BfMethodType_Ctor) { - mModule->Fail("Append allocations are only allowed in constructors", objCreateExpr->mNewNode); + mModule->Fail("Append allocations are only allowed in constructors", allocNode); isAppendAlloc = false; } else if (methodDef->mIsStatic) { - mModule->Fail("Append allocations are only allowed in non-static constructors", objCreateExpr->mNewNode); + mModule->Fail("Append allocations are only allowed in non-static constructors", allocNode); isAppendAlloc = false; } } @@ -12731,7 +12817,7 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) if ((isStackAlloc) && (mModule->mCurMethodState == NULL)) { - mModule->Fail("Cannot use 'stack' here", objCreateExpr->mNewNode); + mModule->Fail("Cannot use 'stack' here", allocNode); isStackAlloc = false; isScopeAlloc = false; } @@ -12810,8 +12896,12 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) BfIRValue appendSizeValue; BfTypedValue emtpyThis(mModule->mBfIRBuilder->GetFakeVal(), resolvedTypeRef, resolvedTypeRef->IsStruct()); - BfResolvedArgs argValues(objCreateExpr->mOpenToken, &objCreateExpr->mArguments, &objCreateExpr->mCommas, objCreateExpr->mCloseToken); - ResolveArgValues(argValues, BfResolveArgFlag_DeferParamEval); //// + BfResolvedArgs argValues; + if (objCreateExpr != NULL) + { + argValues.Init(objCreateExpr->mOpenToken, &objCreateExpr->mArguments, &objCreateExpr->mCommas, objCreateExpr->mCloseToken); + ResolveArgValues(argValues, BfResolveArgFlag_DeferParamEval); //// + } if (typeInstance == NULL) { @@ -12821,7 +12911,7 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) mModule->Fail(StrFormat("Only default parameterless constructors can be called on primitive type '%s'", mModule->TypeToString(resolvedTypeRef).c_str()), objCreateExpr->mTypeRef); } } - else if ((autoComplete != NULL) && (objCreateExpr->mOpenToken != NULL)) + else if ((autoComplete != NULL) && (objCreateExpr != NULL) && (objCreateExpr->mOpenToken != NULL)) { auto wasCapturingMethodInfo = autoComplete->mIsCapturingMethodMatchInfo; autoComplete->CheckInvocation(objCreateExpr, objCreateExpr->mOpenToken, objCreateExpr->mCloseToken, objCreateExpr->mCommas); @@ -12836,9 +12926,13 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) } else if (!resolvedTypeRef->IsFunction()) { - MatchConstructor(objCreateExpr->mTypeRef, objCreateExpr, emtpyThis, typeInstance, argValues, false, true); + auto refNode = allocNode; + if (objCreateExpr != NULL) + refNode = objCreateExpr->mTypeRef; + MatchConstructor(refNode, objCreateExpr, emtpyThis, typeInstance, argValues, false, true); } - mModule->ValidateAllocation(typeInstance, objCreateExpr->mTypeRef); + if (objCreateExpr != NULL) + mModule->ValidateAllocation(typeInstance, objCreateExpr->mTypeRef); prevBindResult.Restore(); @@ -12998,7 +13092,7 @@ void BfExprEvaluator::Visit(BfObjectCreateExpression* objCreateExpr) BF_ASSERT(removeStackObjMethod); if (removeStackObjMethod) { - mModule->AddDeferredCall(removeStackObjMethod, irArgs, allocTarget.mScopeData, objCreateExpr); + mModule->AddDeferredCall(removeStackObjMethod, irArgs, allocTarget.mScopeData, allocNode); } } } @@ -15079,7 +15173,7 @@ void BfExprEvaluator::DoInvocation(BfAstNode* target, BfMethodBoundExpression* m copiedArgs.push_back(arg); BfSizedArray sizedCopiedArgs(copiedArgs); BfResolvedArgs argValues(&sizedCopiedArgs); - + if (mModule->mParentNodeEntry != NULL) { if (auto invocationExpr = BfNodeDynCast(mModule->mParentNodeEntry->mNode)) @@ -15088,7 +15182,7 @@ void BfExprEvaluator::DoInvocation(BfAstNode* target, BfMethodBoundExpression* m argValues.mCommas = &invocationExpr->mCommas; argValues.mCloseToken = invocationExpr->mCloseParen; } - } + } BfResolveArgFlags resolveArgsFlags = (BfResolveArgFlags)(BfResolveArgFlag_DeferFixits | BfResolveArgFlag_AllowUnresolvedTypes); resolveArgsFlags = (BfResolveArgFlags)(resolveArgsFlags | BfResolveArgFlag_DeferParamEval); diff --git a/IDEHelper/Compiler/BfExprEvaluator.h b/IDEHelper/Compiler/BfExprEvaluator.h index b08f1827..ee427c17 100644 --- a/IDEHelper/Compiler/BfExprEvaluator.h +++ b/IDEHelper/Compiler/BfExprEvaluator.h @@ -18,7 +18,8 @@ enum BfArgFlags BfArgFlag_ExpectedTypeCast = 0x80, BfArgFlag_VariableDeclaration = 0x100, BfArgFlag_ParamsExpr = 0x200, - BfArgFlag_UninitializedExpr = 0x400 + BfArgFlag_UninitializedExpr = 0x400, + BfArgFlag_StringInterpolateFormat = 0x800 }; enum BfResolveArgFlags @@ -28,6 +29,7 @@ enum BfResolveArgFlags BfResolveArgFlag_DeferParamValues = 2, // We still evaluate but don't generate code until the method is selected (for SkipCall support) BfResolveArgFlag_DeferParamEval = 4, BfResolveArgFlag_AllowUnresolvedTypes = 8, + BfResolveArgFlag_InsideStringInterpolationAlloc = 0x10 }; class BfResolvedArg @@ -91,6 +93,14 @@ public: mCloseToken = closeToken; } + void Init(BfTokenNode* openToken, BfSizedArray* args, BfSizedArray* commas, BfTokenNode* closeToken) + { + mOpenToken = openToken; + mArguments = args; + mCommas = commas; + mCloseToken = closeToken; + } + void Init(const BfSizedArray* args) { mOpenToken = NULL; @@ -99,7 +109,8 @@ public: mCloseToken = NULL; } - void HandleFixits(BfModule* module); + void HandleFixits(BfModule* module); + }; class BfGenericInferContext @@ -441,6 +452,7 @@ public: void InitializedSizedArray(BfSizedArrayType* sizedArrayType, BfTokenNode* openToken, const BfSizedArray& values, const BfSizedArray& commas, BfTokenNode* closeToken, BfTypedValue* receivingValue = NULL); void CheckDotToken(BfTokenNode* tokenNode); void DoMemberReference(BfMemberReferenceExpression* memberRefExpr, BfTypedValue* outCascadeValue); + void CreateObject(BfObjectCreateExpression* objCreateExpr, BfAstNode* allocNode, BfType* allocType); ////////////////////////////////////////////////////////////////////////// @@ -452,6 +464,7 @@ public: virtual void Visit(BfCaseExpression* caseExpr) override; virtual void Visit(BfTypedValueExpression* typedValueExpr) override; virtual void Visit(BfLiteralExpression* literalExpr) override; + virtual void Visit(BfStringInterpolationExpression* stringInterpolationExpression) override; virtual void Visit(BfIdentifierNode* identifierNode) override; virtual void Visit(BfAttributedIdentifierNode* attrIdentifierNode) override; virtual void Visit(BfQualifiedNameNode* nameNode) override; diff --git a/IDEHelper/Compiler/BfModule.h b/IDEHelper/Compiler/BfModule.h index 33e519be..85b84005 100644 --- a/IDEHelper/Compiler/BfModule.h +++ b/IDEHelper/Compiler/BfModule.h @@ -43,7 +43,7 @@ enum BfPopulateType BfPopulateType_Interfaces, BfPopulateType_Data, BfPopulateType_DataAndMethods, - BfPopulateType_Full = BfPopulateType_DataAndMethods , + BfPopulateType_Full = BfPopulateType_DataAndMethods, BfPopulateType_Full_Force }; @@ -66,7 +66,8 @@ enum BfEvalExprFlags BfEvalExprFlags_FieldInitializer = 0x2000, BfEvalExprFlags_VariableDeclaration = 0x4000, BfEvalExprFlags_NoAutoComplete = 0x8000, - BfEvalExprFlags_AllowNonConst = 0x10000 + BfEvalExprFlags_AllowNonConst = 0x10000, + BfEvalExprFlags_StringInterpolateFormat = 0x20000 }; enum BfCastFlags diff --git a/IDEHelper/Compiler/BfParser.cpp b/IDEHelper/Compiler/BfParser.cpp index 6c5907ac..eb012ba2 100644 --- a/IDEHelper/Compiler/BfParser.cpp +++ b/IDEHelper/Compiler/BfParser.cpp @@ -357,7 +357,8 @@ BfParser::BfParser(BfSystem* bfSystem, BfProject* bfProject) : BfSource(bfSystem mLineStart = 0; //mCurToken = (BfSyntaxToken)0; mToken = BfToken_None; - mSyntaxToken = BfSyntaxToken_None; + mSyntaxToken = BfSyntaxToken_None; + mTokenStart = 0; mTokenEnd = 0; mLineNum = 0; @@ -1360,7 +1361,7 @@ double BfParser::ParseLiteralDouble() return strtod(buf, NULL); } -void BfParser::NextToken(int endIdx) +void BfParser::NextToken(int endIdx, bool outerIsInterpolate) { auto prevToken = mToken; @@ -1372,11 +1373,13 @@ void BfParser::NextToken(int endIdx) bool isLineStart = true; bool isVerbatim = false; - int verbatimStart = -1; + bool isInterpolate = false; + int stringStart = -1; while (true) { bool setVerbatim = false; + bool setInterpolate = false; uint32 checkTokenHash = 0; if ((endIdx != -1) && (mSrcIdx >= endIdx)) @@ -1389,6 +1392,15 @@ void BfParser::NextToken(int endIdx) mTokenEnd = mSrcIdx + 1; char c = mSrc[mSrcIdx++]; + if (outerIsInterpolate) + { + if (c == '"') + { + mSyntaxToken = BfSyntaxToken_StringQuote; + return; + } + } + if ((mPreprocessorIgnoreDepth > 0) && (endIdx == -1)) { if (c == 0) @@ -1668,7 +1680,7 @@ void BfParser::NextToken(int endIdx) case '@': setVerbatim = true; c = mSrc[mSrcIdx]; - if (c == '\"') + if ((c == '\"') || (c == '$')) { setVerbatim = true; } @@ -1679,19 +1691,29 @@ void BfParser::NextToken(int endIdx) else { mSyntaxToken = BfSyntaxToken_Identifier; - //mToken = BfToken_At; - //mSyntaxToken = BfSyntaxToken_Token; - //Fail("Keyword, identifier, or string expected after verbatim specifier: @"); // CS1646 return; } break; + case '$': + setInterpolate = true; + c = mSrc[mSrcIdx]; + if ((c == '\"') || (c == '@')) + { + setInterpolate = true; + } + else + Fail("Expected to precede string"); + break; case '"': case '\'': { + SizedArray interpolateExpressions; + String lineHeader; String strLiteral; char startChar = c; bool isMultiline = false; + int triviaStart = mTriviaStart; if ((mSrc[mSrcIdx] == '"') && (mSrc[mSrcIdx + 1] == '"')) { @@ -1982,16 +2004,64 @@ void BfParser::NextToken(int endIdx) Fail("Unrecognized escape sequence"); strLiteral += c; } - } + } else + { strLiteral += c; + + if (isInterpolate) + { + if (c == '{') + { + BfBlock* newBlock = mAlloc->Alloc(); + mTokenStart = mSrcIdx - 1; + mTriviaStart = mTokenStart; + mTokenEnd = mTokenStart + 1; + mToken = BfToken_LBrace; + newBlock->mOpenBrace = (BfTokenNode*)CreateNode(); + newBlock->Init(this); + ParseBlock(newBlock, 1, true); + if (mToken == BfToken_RBrace) + { + newBlock->mCloseBrace = (BfTokenNode*)CreateNode(); + newBlock->SetSrcEnd(mSrcIdx); + strLiteral += "}"; + } + else if ((mSyntaxToken == BfSyntaxToken_EOF) || (mSyntaxToken == BfSyntaxToken_StringQuote)) + { + mSrcIdx--; + mPassInstance->FailAfterAt("Expected '}'", mSourceData, newBlock->GetSrcEnd() - 1); + } + mInAsmBlock = false; + interpolateExpressions.Add(newBlock); + } + else if (c == '}') + { + if (!interpolateExpressions.IsEmpty()) + { + auto block = interpolateExpressions.back(); + if (block->mCloseBrace == NULL) + { + mTokenStart = mSrcIdx - 1; + mTriviaStart = mTokenStart; + mTokenEnd = mTokenStart + 1; + mToken = BfToken_RBrace; + block->mCloseBrace = (BfTokenNode*)CreateNode(); + block->SetSrcEnd(mSrcIdx); + } + } + } + } + } } - if (isVerbatim) + if (stringStart != -1) { - mTokenStart--; - } + mTokenStart = stringStart; + stringStart = -1; + } + mTriviaStart = triviaStart; mTokenEnd = mSrcIdx; mSyntaxToken = BfSyntaxToken_Literal; if (startChar == '\'') @@ -2046,6 +2116,20 @@ void BfParser::NextToken(int endIdx) mLiteral.mTypeCode = BfTypeCode_CharPtr; mLiteral.mString = strLiteralPtr; } + + if (isInterpolate) + { + auto interpolateExpr = mAlloc->Alloc(); + interpolateExpr->mString = mLiteral.mString; + interpolateExpr->mTriviaStart = mTriviaStart; + interpolateExpr->mSrcStart = mTokenStart; + interpolateExpr->mSrcEnd = mSrcIdx; + BfSizedArrayInitIndirect(interpolateExpr->mExpressions, interpolateExpressions, mAlloc); + mGeneratedNode = interpolateExpr; + mSyntaxToken = BfSyntaxToken_GeneratedNode; + mToken = BfToken_None; + } + return; } break; @@ -3098,8 +3182,11 @@ void BfParser::NextToken(int endIdx) ((c >= 'a') && (c <= 'z')) || (c == '_')) { - if (isVerbatim) - mTokenStart = verbatimStart; + if (stringStart != -1) + { + mTokenStart = stringStart; + stringStart = -1; + } while (true) { @@ -3163,7 +3250,12 @@ void BfParser::NextToken(int endIdx) if ((setVerbatim) && (!isVerbatim)) { isVerbatim = true; - verbatimStart = mTokenStart; + stringStart = mTokenStart; + } + if ((setInterpolate) && (!isInterpolate)) + { + isInterpolate = true; + stringStart = mTokenStart; } } } @@ -3171,7 +3263,7 @@ void BfParser::NextToken(int endIdx) static int gParseBlockIdx = 0; static int gParseMemberIdx = 0; -void BfParser::ParseBlock(BfBlock* astNode, int depth) +void BfParser::ParseBlock(BfBlock* astNode, int depth, bool isInterpolate) { gParseBlockIdx++; int startParseBlockIdx = gParseBlockIdx; @@ -3180,6 +3272,8 @@ void BfParser::ParseBlock(BfBlock* astNode, int depth) SizedArray childArr; + int parenDepth = 0; + while (true) { if ((mSyntaxToken == BfSyntaxToken_Token) && (mToken == BfToken_Asm)) @@ -3190,7 +3284,8 @@ void BfParser::ParseBlock(BfBlock* astNode, int depth) isAsmBlock = true; } - NextToken(); + NextToken(-1, isInterpolate); + if (mPreprocessorIgnoredSectionNode != NULL) { if (mSyntaxToken != BfSyntaxToken_EOF) @@ -3256,7 +3351,7 @@ void BfParser::ParseBlock(BfBlock* astNode, int depth) mInAsmBlock = false; astNode->Add(newBlock); childArr.push_back(newBlock); - } + } else if (mToken == BfToken_RBrace) { if (depth == 0) @@ -3265,6 +3360,20 @@ void BfParser::ParseBlock(BfBlock* astNode, int depth) } else { + if (mToken == BfToken_LParen) + parenDepth++; + else if (mToken == BfToken_RParen) + parenDepth--; + + if ((isInterpolate) && (parenDepth == 0)) + { + if ((mToken == BfToken_Colon) || (mToken == BfToken_Comma)) + { + mSrcIdx = mTokenStart; + break; + } + } + astNode->Add(childNode); childArr.push_back(childNode); @@ -3310,6 +3419,8 @@ BfAstNode* BfParser::CreateNode() mLiteral.mWarnType = 0; return bfLiteralExpression; } + case BfSyntaxToken_GeneratedNode: + return mGeneratedNode; default: break; } diff --git a/IDEHelper/Compiler/BfParser.h b/IDEHelper/Compiler/BfParser.h index 1c843853..2d5b0797 100644 --- a/IDEHelper/Compiler/BfParser.h +++ b/IDEHelper/Compiler/BfParser.h @@ -26,6 +26,7 @@ enum BfSyntaxToken BfSyntaxToken_Literal, BfSyntaxToken_CommentLine, BfSyntaxToken_CommentBlock, + BfSyntaxToken_GeneratedNode, BfSyntaxToken_FAILED, BfSyntaxToken_HIT_END_IDX, BfSyntaxToken_EOF @@ -105,7 +106,7 @@ class BfParserCache public: struct LookupEntry { - uint64 mHash; + uint64 mHash; String mFileName; const char* mSrc; int mSrcLength; @@ -173,7 +174,8 @@ public: BfSyntaxToken mSyntaxToken; int mTriviaStart; // mTriviaStart < mTokenStart when there's leading whitespace int mTokenStart; - int mTokenEnd; + int mTokenEnd; + BfAstNode* mGeneratedNode; BfVariant mLiteral; BfToken mToken; BfPreprocesorIgnoredSectionNode* mPreprocessorIgnoredSectionNode; @@ -203,7 +205,7 @@ public: bool IsUnwarnedAt(BfAstNode* node); bool SrcPtrHasToken(const char* name); uint32 GetTokenHash(); - void ParseBlock(BfBlock* astNode, int depth); + void ParseBlock(BfBlock* astNode, int depth, bool isInterpolate = false); double ParseLiteralDouble(); void AddErrorNode(int startIdx, int endIdx); BfCommentKind GetCommentKind(int startIdx); @@ -223,7 +225,7 @@ public: void SetSource(const char* data, int length); void MoveSource(const char* data, int length); // Takes ownership of data ptr void RefSource(const char* data, int length); - void NextToken(int endIdx = -1); + void NextToken(int endIdx = -1, bool outerIsInterpolate = false); BfAstNode* CreateNode(); void Parse(BfPassInstance* passInstance); diff --git a/IDEHelper/Compiler/BfPrinter.cpp b/IDEHelper/Compiler/BfPrinter.cpp index efb06a8d..bf4195ac 100644 --- a/IDEHelper/Compiler/BfPrinter.cpp +++ b/IDEHelper/Compiler/BfPrinter.cpp @@ -1163,6 +1163,14 @@ void BfPrinter::Visit(BfLiteralExpression* literalExpr) WriteSourceString(literalExpr); } +void BfPrinter::Visit(BfStringInterpolationExpression* stringInterpolationExpression) +{ + Visit(stringInterpolationExpression->ToBase()); + String str; + stringInterpolationExpression->ToString(str); + Write(str); +} + void BfPrinter::Visit(BfIdentifierNode* identifierNode) { Visit(identifierNode->ToBase()); diff --git a/IDEHelper/Compiler/BfPrinter.h b/IDEHelper/Compiler/BfPrinter.h index 0bf6c6d9..8469cb77 100644 --- a/IDEHelper/Compiler/BfPrinter.h +++ b/IDEHelper/Compiler/BfPrinter.h @@ -138,6 +138,7 @@ public: virtual void Visit(BfEmptyStatement* emptyStmt) override; virtual void Visit(BfTokenNode* tokenNode) override; virtual void Visit(BfLiteralExpression* literalExpr) override; + virtual void Visit(BfStringInterpolationExpression* stringInterpolationExpression) override; virtual void Visit(BfIdentifierNode* identifierNode) override; virtual void Visit(BfQualifiedNameNode* nameNode) override; virtual void Visit(BfThisExpression* thisExpr) override; diff --git a/IDEHelper/Compiler/BfReducer.cpp b/IDEHelper/Compiler/BfReducer.cpp index 39679838..10cfbece 100644 --- a/IDEHelper/Compiler/BfReducer.cpp +++ b/IDEHelper/Compiler/BfReducer.cpp @@ -1427,6 +1427,15 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat AssertCurrentNode(node); + if (auto interpolateExpr = BfNodeDynCastExact(node)) + { + for (auto block : interpolateExpr->mExpressions) + { + HandleBlock(block, true); + } + return interpolateExpr; + } + if ((createExprFlags & (CreateExprFlags_AllowVariableDecl | CreateExprFlags_PermissiveVariableDecl)) != 0) { bool isLocalVariable = false; @@ -1712,6 +1721,8 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat bool isDelegateBind = false; bool isLambdaBind = false; bool isBoxing = false; + + auto nextNode = mVisitorPos.GetNext(); if (auto nextTokenNode = BfNodeDynCast(nextNode)) { @@ -1735,8 +1746,20 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat mVisitorPos.mReadPos--; } } + + if (auto interpExpr = BfNodeDynCastExact(nextNode)) + { + mVisitorPos.MoveNext(); + auto nextInterpExpr = CreateExpression(nextNode); + BF_ASSERT(nextInterpExpr == interpExpr); - if (isBoxing) + interpExpr->mAllocNode = allocNode; + interpExpr->mTriviaStart = allocNode->mTriviaStart; + interpExpr->mSrcStart = allocNode->mSrcStart; + + exprLeft = interpExpr; + } + else if (isBoxing) { auto boxExpr = mAlloc->Alloc(); ReplaceNode(allocNode, boxExpr); diff --git a/IDEHelper/Compiler/BfSourceClassifier.cpp b/IDEHelper/Compiler/BfSourceClassifier.cpp index c9bbecda..387671ca 100644 --- a/IDEHelper/Compiler/BfSourceClassifier.cpp +++ b/IDEHelper/Compiler/BfSourceClassifier.cpp @@ -393,6 +393,20 @@ void BfSourceClassifier::Visit(BfLiteralExpression* literalExpr) SetElementType(literalExpr, BfSourceElementType_Literal); } +void BfSourceClassifier::Visit(BfStringInterpolationExpression* stringInterpolationExpression) +{ + HandleLeafNode(stringInterpolationExpression); + + Visit(stringInterpolationExpression->ToBase()); + SetElementType(stringInterpolationExpression, BfSourceElementType_Literal); + + VisitChild(stringInterpolationExpression->mAllocNode); + for (auto& expr : stringInterpolationExpression->mExpressions) + { + VisitChild(expr); + } +} + void BfSourceClassifier::Visit(BfTokenNode* tokenNode) { HandleLeafNode(tokenNode); diff --git a/IDEHelper/Compiler/BfSourceClassifier.h b/IDEHelper/Compiler/BfSourceClassifier.h index aeea9b5f..022a54c7 100644 --- a/IDEHelper/Compiler/BfSourceClassifier.h +++ b/IDEHelper/Compiler/BfSourceClassifier.h @@ -109,6 +109,7 @@ public: virtual void Visit(BfGenericInstanceTypeRef* typeRef) override; virtual void Visit(BfLocalMethodDeclaration * methodDecl) override; virtual void Visit(BfLiteralExpression* literalExpr) override; + virtual void Visit(BfStringInterpolationExpression* stringInterpolationExpression) override; virtual void Visit(BfTokenNode* tokenNode) override; virtual void Visit(BfInvocationExpression* invocationExpr) override; virtual void Visit(BfIndexerExpression* indexerExpr) override; diff --git a/IDEHelper/Tests/src/Strings.bf b/IDEHelper/Tests/src/Strings.bf new file mode 100644 index 00000000..42f6c2e9 --- /dev/null +++ b/IDEHelper/Tests/src/Strings.bf @@ -0,0 +1,24 @@ +using System; + +namespace Tests +{ + class Strings + { + static void FormatString(String outString, String format, params Object[] args) + { + outString.AppendF(format, params args); + } + + [Test] + public static void TestBasics() + { + var str0 = scope $@"AB\C"; + Test.Assert(str0 == "AB\\C"); + var str1 = scope @$"\A{100+200}B{100+200:X}"; + Test.Assert(str1 == "\\A300B12C"); + var str2 = scope String(); + FormatString(str2, $"\a{200+300}B{200+300:X}"); + Test.Assert(str2 == "\a500B1F4"); + } + } +}