浏览代码

String interpolation

Brian Fiete 4 年之前
父节点
当前提交
281f19e04c

+ 5 - 0
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());

+ 13 - 1
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<ASTREF(BfBlock*)> mExpressions;
+};  BF_AST_DECL(BfStringInterpolationExpression, BfExpression);
+
 class BfInitializerExpression : public BfExpression
 {
 public:

+ 10 - 0
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());

+ 1 - 0
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);

+ 126 - 32
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<BfExpression*, 2> argExprs;
+		argExprs.Add(stringInterpolationExpression);
+		BfSizedArray<BfExpression*> 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<BfExpression*, 8> 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<BfTypedValueExpression>(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<BfStringInterpolationExpression>(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<BfAttributeState*> 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<BfDotTypeReference>()) && (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<BfExpression*> sizedCopiedArgs(copiedArgs);
 	BfResolvedArgs argValues(&sizedCopiedArgs);	
-
+	
 	if (mModule->mParentNodeEntry != NULL)
 	{
 		if (auto invocationExpr = BfNodeDynCast<BfInvocationExpression>(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);

+ 15 - 2
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<BfExpression*>* args, BfSizedArray<BfTokenNode*>* commas, BfTokenNode* closeToken)
+	{
+		mOpenToken = openToken;
+		mArguments = args;
+		mCommas = commas;
+		mCloseToken = closeToken;
+	}
+
 	void Init(const BfSizedArray<BfExpression*>* 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<BfExpression*>& values, const BfSizedArray<BfTokenNode*>& 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;

+ 3 - 2
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

+ 128 - 17
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<BfBlock*, 4> 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<BfBlock>();
+							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<BfStringInterpolationExpression>();
+				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<BfAstNode*, 32> 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;
 	}
 

+ 6 - 4
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);		

+ 8 - 0
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());	

+ 1 - 0
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;

+ 24 - 1
IDEHelper/Compiler/BfReducer.cpp

@@ -1427,6 +1427,15 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat
 
 	AssertCurrentNode(node);
 
+	if (auto interpolateExpr = BfNodeDynCastExact<BfStringInterpolationExpression>(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<BfTokenNode>(nextNode))
 				{
@@ -1735,8 +1746,20 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat
 						mVisitorPos.mReadPos--;
 					}
 				}
+				
+				if (auto interpExpr = BfNodeDynCastExact<BfStringInterpolationExpression>(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<BfBoxExpression>();
 					ReplaceNode(allocNode, boxExpr);

+ 14 - 0
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);

+ 1 - 0
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;

+ 24 - 0
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");
+		}
+	}
+}