mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-09 20:12:21 +02:00
Better handling of autocomplete with tokens
This commit is contained in:
parent
74f3ef4e43
commit
e16e4613b6
14 changed files with 108 additions and 65 deletions
|
@ -51,6 +51,11 @@ void BfStructuralVisitor::Visit(BfLabeledBlock* labeledBlock)
|
|||
Visit(labeledBlock->ToBase());
|
||||
}
|
||||
|
||||
void BfStructuralVisitor::Visit(BfErrorNode* bfErrorNode)
|
||||
{
|
||||
Visit(bfErrorNode->ToBase());
|
||||
}
|
||||
|
||||
void BfStructuralVisitor::Visit(BfScopeNode* scopeNode)
|
||||
{
|
||||
Visit(scopeNode->ToBase());
|
||||
|
|
|
@ -399,7 +399,7 @@ public:
|
|||
BfStructuralVisitor();
|
||||
|
||||
virtual void Visit(BfAstNode* bfAstNode) {}
|
||||
virtual void Visit(BfErrorNode* bfErrorNode) {}
|
||||
virtual void Visit(BfErrorNode* bfErrorNode);
|
||||
virtual void Visit(BfScopeNode* scopeNode);
|
||||
virtual void Visit(BfNewNode* newNode);
|
||||
virtual void Visit(BfLabeledBlock* labeledBlock);
|
||||
|
@ -1461,15 +1461,6 @@ T* BfNodeDynCastExact(BfAstNode* node)
|
|||
BfIdentifierNode* BfIdentifierCast(BfAstNode* node);
|
||||
BfAstNode* BfNodeToNonTemporary(BfAstNode* node);
|
||||
|
||||
class BfErrorNode : public BfAstNode
|
||||
{
|
||||
public:
|
||||
BF_AST_TYPE(BfErrorNode, BfAstNode);
|
||||
|
||||
BfAstNode* mRefNode;
|
||||
}; BF_AST_DECL(BfErrorNode, BfAstNode);
|
||||
|
||||
|
||||
class BfStatement : public BfAstNode
|
||||
{
|
||||
public:
|
||||
|
@ -1496,6 +1487,14 @@ public:
|
|||
bool VerifyIsStatement(BfPassInstance* passInstance, bool ignoreError = false);
|
||||
}; BF_AST_DECL(BfExpression, BfAstNode);
|
||||
|
||||
class BfErrorNode : public BfExpression
|
||||
{
|
||||
public:
|
||||
BF_AST_TYPE(BfErrorNode, BfExpression);
|
||||
|
||||
BfAstNode* mRefNode;
|
||||
}; BF_AST_DECL(BfErrorNode, BfExpression);
|
||||
|
||||
class BfExpressionStatement : public BfStatement
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -921,7 +921,7 @@ void BfAutoComplete::AddEnumTypeMembers(BfTypeInstance* typeInst, const StringIm
|
|||
// return IsInExpression(node->mParent);
|
||||
// }
|
||||
|
||||
void BfAutoComplete::AddTopLevelNamespaces(BfIdentifierNode* identifierNode)
|
||||
void BfAutoComplete::AddTopLevelNamespaces(BfAstNode* identifierNode)
|
||||
{
|
||||
String filter;
|
||||
if (identifierNode != NULL)
|
||||
|
@ -964,7 +964,7 @@ void BfAutoComplete::AddTopLevelNamespaces(BfIdentifierNode* identifierNode)
|
|||
}
|
||||
}
|
||||
|
||||
void BfAutoComplete::AddTopLevelTypes(BfIdentifierNode* identifierNode, bool onlyAttribute)
|
||||
void BfAutoComplete::AddTopLevelTypes(BfAstNode* identifierNode, bool onlyAttribute)
|
||||
{
|
||||
String filter;
|
||||
|
||||
|
@ -1144,7 +1144,7 @@ void BfAutoComplete::AddTopLevelTypes(BfIdentifierNode* identifierNode, bool onl
|
|||
}
|
||||
}
|
||||
|
||||
void BfAutoComplete::CheckIdentifier(BfIdentifierNode* identifierNode, bool isInExpression, bool isUsingDirective)
|
||||
void BfAutoComplete::CheckIdentifier(BfAstNode* identifierNode, bool isInExpression, bool isUsingDirective)
|
||||
{
|
||||
if ((identifierNode != NULL) && (!IsAutocompleteNode(identifierNode)))
|
||||
return;
|
||||
|
@ -2335,12 +2335,12 @@ void BfAutoComplete::CheckResult(BfAstNode* node, const BfTypedValue& typedValue
|
|||
}
|
||||
}
|
||||
|
||||
void BfAutoComplete::CheckLocalDef(BfIdentifierNode* identifierNode, BfLocalVariable* varDecl)
|
||||
void BfAutoComplete::CheckLocalDef(BfAstNode* identifierNode, BfLocalVariable* varDecl)
|
||||
{
|
||||
CheckLocalRef(identifierNode, varDecl);
|
||||
}
|
||||
|
||||
void BfAutoComplete::CheckLocalRef(BfIdentifierNode* identifierNode, BfLocalVariable* varDecl)
|
||||
void BfAutoComplete::CheckLocalRef(BfAstNode* identifierNode, BfLocalVariable* varDecl)
|
||||
{
|
||||
if (mReplaceLocalId != -1)
|
||||
return;
|
||||
|
@ -2406,7 +2406,7 @@ void BfAutoComplete::CheckLocalRef(BfIdentifierNode* identifierNode, BfLocalVari
|
|||
}
|
||||
}
|
||||
|
||||
void BfAutoComplete::CheckFieldRef(BfIdentifierNode* identifierNode, BfFieldInstance* fieldInst)
|
||||
void BfAutoComplete::CheckFieldRef(BfAstNode* identifierNode, BfFieldInstance* fieldInst)
|
||||
{
|
||||
if (mResolveType == BfResolveType_GetSymbolInfo)
|
||||
{
|
||||
|
@ -2438,7 +2438,7 @@ void BfAutoComplete::CheckFieldRef(BfIdentifierNode* identifierNode, BfFieldInst
|
|||
}
|
||||
}
|
||||
|
||||
void BfAutoComplete::CheckLabel(BfIdentifierNode* identifierNode, BfAstNode* precedingNode)
|
||||
void BfAutoComplete::CheckLabel(BfAstNode* identifierNode, BfAstNode* precedingNode)
|
||||
{
|
||||
String filter;
|
||||
if (identifierNode != NULL)
|
||||
|
|
|
@ -175,7 +175,7 @@ public:
|
|||
BfAstNode* mGetDefinitionNode;
|
||||
BfResolveType mResolveType;
|
||||
BfTypeInstance* mShowAttributeProperties;
|
||||
BfIdentifierNode* mIdentifierUsed;
|
||||
BfAstNode* mIdentifierUsed;
|
||||
bool mIgnoreFixits;
|
||||
bool mHasFriendSet;
|
||||
bool mUncertain; // May be an unknown identifier, do not aggressively autocomplete
|
||||
|
@ -212,8 +212,8 @@ public:
|
|||
void AddSelfResultTypeMembers(BfTypeInstance* typeInst, BfTypeInstance* selfType, const StringImpl& filter, bool allowPrivate);
|
||||
bool InitAutocomplete(BfAstNode* dotNode, BfAstNode* nameNode, String& filter);
|
||||
void AddEnumTypeMembers(BfTypeInstance* typeInst, const StringImpl& filter, bool allowProtected, bool allowPrivate);
|
||||
void AddTopLevelNamespaces(BfIdentifierNode* identifierNode);
|
||||
void AddTopLevelTypes(BfIdentifierNode* identifierNode, bool onlyAttribute = false);
|
||||
void AddTopLevelNamespaces(BfAstNode* identifierNode);
|
||||
void AddTopLevelTypes(BfAstNode* identifierNode, bool onlyAttribute = false);
|
||||
void AddOverrides(const StringImpl& filter);
|
||||
void UpdateReplaceData();
|
||||
void AddTypeInstanceEntry(BfTypeInstance* typeInst);
|
||||
|
@ -233,7 +233,7 @@ public:
|
|||
void RemoveMethodMatchInfo();
|
||||
void ClearMethodMatchEntries();
|
||||
|
||||
void CheckIdentifier(BfIdentifierNode* identifierNode, bool isInExpression = false, bool isUsingDirective = false);
|
||||
void CheckIdentifier(BfAstNode* identifierNode, bool isInExpression = false, bool isUsingDirective = false);
|
||||
bool CheckMemberReference(BfAstNode* target, BfAstNode* dotToken, BfAstNode* memberName, bool onlyShowTypes = false, BfType* expectingType = NULL, bool isUsingDirective = false, bool onlyAttribute = false);
|
||||
bool CheckExplicitInterface(BfTypeInstance* interfaceType, BfAstNode* dotToken, BfAstNode* memberName);
|
||||
void CheckTypeRef(BfTypeReference* typeRef, bool mayBeIdentifier, bool isInExpression = false, bool onlyAttribute = false);
|
||||
|
@ -244,10 +244,10 @@ public:
|
|||
void CheckProperty(BfPropertyDeclaration* propertyDeclaration);
|
||||
void CheckVarResolution(BfAstNode* varTypeRef, BfType* resolvedTypeRef);
|
||||
void CheckResult(BfAstNode* node, const BfTypedValue& typedValue);
|
||||
void CheckLocalDef(BfIdentifierNode* identifierNode, BfLocalVariable* varDecl);
|
||||
void CheckLocalRef(BfIdentifierNode* identifierNode, BfLocalVariable* varDecl);
|
||||
void CheckFieldRef(BfIdentifierNode* identifierNode, BfFieldInstance* fieldInst);
|
||||
void CheckLabel(BfIdentifierNode* identifierNode, BfAstNode* precedingNode = NULL);
|
||||
void CheckLocalDef(BfAstNode* identifierNode, BfLocalVariable* varDecl);
|
||||
void CheckLocalRef(BfAstNode* identifierNode, BfLocalVariable* varDecl);
|
||||
void CheckFieldRef(BfAstNode* identifierNode, BfFieldInstance* fieldInst);
|
||||
void CheckLabel(BfAstNode* identifierNode, BfAstNode* precedingNode = NULL);
|
||||
void CheckEmptyStart(BfAstNode* prevNode, BfType* type);
|
||||
bool CheckFixit(BfAstNode* node);
|
||||
void CheckInterfaceFixit(BfTypeInstance* typeInstance, BfAstNode* node);
|
||||
|
|
|
@ -125,6 +125,11 @@ void BfElementVisitor::Visit(BfLabelableStatement* labelableStmt)
|
|||
}
|
||||
}
|
||||
|
||||
void BfElementVisitor::Visit(BfErrorNode* bfErrorNode)
|
||||
{
|
||||
Visit(bfErrorNode->mRefNode);
|
||||
}
|
||||
|
||||
void BfElementVisitor::Visit(BfScopeNode* scopeNode)
|
||||
{
|
||||
Visit(scopeNode->ToBase());
|
||||
|
|
|
@ -10,7 +10,7 @@ public:
|
|||
BfElementVisitor();
|
||||
|
||||
virtual void Visit(BfAstNode* bfAstNode) {}
|
||||
virtual void Visit(BfErrorNode* bfErrorNode) {}
|
||||
virtual void Visit(BfErrorNode* bfErrorNode);
|
||||
virtual void Visit(BfScopeNode* scopeNode);
|
||||
virtual void Visit(BfNewNode* newNode);
|
||||
virtual void Visit(BfLabeledBlock* labeledBlock);
|
||||
|
|
|
@ -2290,6 +2290,15 @@ void BfExprEvaluator::Evaluate(BfAstNode* astNode, bool propogateNullConditional
|
|||
}
|
||||
}
|
||||
|
||||
void BfExprEvaluator::Visit(BfErrorNode* errorNode)
|
||||
{
|
||||
mModule->Fail("Invalid token", errorNode);
|
||||
|
||||
auto autoComplete = GetAutoComplete();
|
||||
if (autoComplete != NULL)
|
||||
autoComplete->CheckIdentifier(errorNode->mRefNode, true);
|
||||
}
|
||||
|
||||
void BfExprEvaluator::Visit(BfTypeReference* typeRef)
|
||||
{
|
||||
mResult.mType = ResolveTypeRef(typeRef, BfPopulateType_Declaration);
|
||||
|
@ -3583,7 +3592,10 @@ BfTypedValue BfExprEvaluator::LookupField(BfAstNode* targetSrc, BfTypedValue tar
|
|||
bool isStaticCtor = (mModule->mCurMethodInstance != NULL) && (mModule->mCurMethodInstance->mMethodDef->mMethodType == BfMethodType_Ctor) &&
|
||||
(mModule->mCurMethodInstance->mMethodDef->mIsStatic);
|
||||
if ((field->mIsReadOnly) && (!isStaticCtor))
|
||||
retVal = mModule->LoadValue(retVal, NULL, mIsVolatileReference);
|
||||
{
|
||||
if (retVal.IsAddr())
|
||||
retVal.mKind = BfTypedValueKind_ReadOnlyAddr;
|
||||
}
|
||||
else
|
||||
mIsHeapReference = true;
|
||||
return retVal;
|
||||
|
|
|
@ -405,6 +405,7 @@ public:
|
|||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
virtual void Visit(BfErrorNode* errorNode) override;
|
||||
virtual void Visit(BfTypeReference* typeRef) override;
|
||||
virtual void Visit(BfAttributedExpression* attribExpr) override;
|
||||
virtual void Visit(BfBlock* blockExpr) override;
|
||||
|
|
|
@ -2787,9 +2787,15 @@ void BfPrinter::Visit(BfRootNode* rootNode)
|
|||
child->Accept(this);
|
||||
}
|
||||
|
||||
// Flush whitespace at the end of the document
|
||||
BfParserData* bfParser = rootNode->GetSourceData()->ToParserData();
|
||||
if (bfParser != NULL)
|
||||
Write(rootNode, rootNode->GetSrcEnd(), bfParser->mSrcLength - rootNode->GetSrcEnd());
|
||||
{
|
||||
BfAstNode endNode;
|
||||
BfAstNode::Zero<BfAstNode>(&endNode);
|
||||
endNode.Init(rootNode->GetSrcEnd(), rootNode->GetSrcEnd(), bfParser->mSrcLength - rootNode->GetSrcEnd());
|
||||
Visit(&endNode);
|
||||
}
|
||||
|
||||
if (mCharMapping != NULL)
|
||||
{
|
||||
|
|
|
@ -1402,16 +1402,8 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat
|
|||
|
||||
AssertCurrentNode(node);
|
||||
|
||||
/*if (auto block = BfNodeDynCast<BfBlock>(node))
|
||||
{
|
||||
HandleBlock(block, true);
|
||||
return block;
|
||||
}*/
|
||||
|
||||
auto rhsCreateExprFlags = (CreateExprFlags)(createExprFlags & CreateExprFlags_BreakOnRChevron);
|
||||
|
||||
node = ReplaceTokenStarter(node);
|
||||
|
||||
auto exprLeft = BfNodeDynCast<BfExpression>(node);
|
||||
|
||||
AssertCurrentNode(node);
|
||||
|
@ -2431,6 +2423,8 @@ BfExpression* BfReducer::CreateExpression(BfAstNode* node, CreateExprFlags creat
|
|||
{
|
||||
if (nextTokenNode->GetToken() == BfToken_Dot)
|
||||
{
|
||||
TryIdentifierConvert(checkIdx + 2);
|
||||
|
||||
auto nextNextCheckNode = mVisitorPos.Get(checkIdx + 2);
|
||||
|
||||
if (auto identifierNode = BfNodeDynCast<BfIdentifierNode>(nextNextCheckNode))
|
||||
|
@ -4265,8 +4259,6 @@ BfAstNode* BfReducer::CreateStatement(BfAstNode* node, CreateStmtFlags createStm
|
|||
{
|
||||
AssertCurrentNode(node);
|
||||
|
||||
node = ReplaceTokenStarter(node);
|
||||
|
||||
if ((createStmtFlags & CreateStmtFlags_AllowUnterminatedExpression) != 0)
|
||||
{
|
||||
if (IsTerminatingExpression(node))
|
||||
|
@ -5033,11 +5025,6 @@ BfTypeReference* BfReducer::CreateTypeRef(BfAstNode* firstNode, CreateTypeRefFla
|
|||
}
|
||||
return CreateRefTypeRef(typeRef, tokenNode);
|
||||
}
|
||||
else if ((token == BfToken_In) || (token == BfToken_As))
|
||||
{
|
||||
// This is mostly to allow a partially typed 'int' to be parsed as 'in' to make autocomplete nicer
|
||||
return CreateTypeRef(ReplaceTokenStarter(firstNode), createTypeRefFlags);
|
||||
}
|
||||
}
|
||||
return DoCreateTypeRef(firstNode, createTypeRefFlags);
|
||||
}
|
||||
|
@ -5107,8 +5094,20 @@ BfIdentifierNode* BfReducer::CompactQualifiedName(BfAstNode* leftNode)
|
|||
|
||||
auto nextNextToken = mVisitorPos.Get(mVisitorPos.mReadPos + 2);
|
||||
auto rightIdentifier = BfNodeDynCast<BfIdentifierNode>(nextNextToken);
|
||||
if (rightIdentifier == NULL)
|
||||
{
|
||||
if (auto rightToken = BfNodeDynCast<BfTokenNode>(nextNextToken))
|
||||
{
|
||||
if (BfTokenIsKeyword(rightToken->mToken))
|
||||
{
|
||||
rightIdentifier = mAlloc->Alloc<BfIdentifierNode>();
|
||||
ReplaceNode(rightToken, rightIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
if (rightIdentifier == NULL)
|
||||
return leftIdentifier;
|
||||
}
|
||||
|
||||
// If the previous dotted span failed (IE: had chevrons) then don't insert qualified names in the middle of it
|
||||
auto prevNodeToken = BfNodeDynCast<BfTokenNode>(prevNode);
|
||||
|
@ -5131,6 +5130,20 @@ BfIdentifierNode* BfReducer::CompactQualifiedName(BfAstNode* leftNode)
|
|||
return leftIdentifier;
|
||||
}
|
||||
|
||||
void BfReducer::TryIdentifierConvert(int readPos)
|
||||
{
|
||||
auto node = mVisitorPos.Get(readPos);
|
||||
if (auto tokenNode = BfNodeDynCast<BfTokenNode>(node))
|
||||
{
|
||||
if (BfTokenIsKeyword(tokenNode->mToken))
|
||||
{
|
||||
auto identifierNode = mAlloc->Alloc<BfIdentifierNode>();
|
||||
ReplaceNode(tokenNode, identifierNode);
|
||||
mVisitorPos.Set(readPos, identifierNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BfReducer::CreateQualifiedNames(BfAstNode* node)
|
||||
{
|
||||
auto block = BfNodeDynCast<BfBlock>(node);
|
||||
|
@ -6266,8 +6279,6 @@ BfAstNode* BfReducer::ReadTypeMember(BfAstNode* node, int depth)
|
|||
|
||||
AssertCurrentNode(node);
|
||||
|
||||
node = ReplaceTokenStarter(node);
|
||||
|
||||
BfTokenNode* refToken = NULL;
|
||||
|
||||
if (auto tokenNode = BfNodeDynCast<BfTokenNode>(node))
|
||||
|
@ -9296,21 +9307,7 @@ void BfReducer::HandleBlock(BfBlock* block, bool allowEndingExpression)
|
|||
|
||||
auto statement = CreateStatement(node, flags);
|
||||
if (statement == NULL)
|
||||
{
|
||||
auto nextNode = mVisitorPos.GetNext();
|
||||
isDone = !mVisitorPos.MoveNext();
|
||||
#ifdef BF_AST_HAS_PARENT_MEMBER
|
||||
BF_ASSERT(node->mParent != NULL);
|
||||
#endif
|
||||
|
||||
node->RemoveSelf();
|
||||
BF_ASSERT(node->GetSourceData() == mSource->mSourceData);
|
||||
mSource->AddErrorNode(node);
|
||||
|
||||
if (nextNode == NULL)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
statement = mSource->CreateErrorNode(node);
|
||||
|
||||
isDone = !mVisitorPos.MoveNext();
|
||||
mVisitorPos.Write(statement);
|
||||
|
|
|
@ -185,6 +185,7 @@ public:
|
|||
BfAstNode* ReadTypeMember(BfTokenNode* node, int depth = 0);
|
||||
BfAstNode* ReadTypeMember(BfAstNode* node, int depth = 0);
|
||||
BfIdentifierNode* CompactQualifiedName(BfAstNode* leftNode);
|
||||
void TryIdentifierConvert(int readPos);
|
||||
void CreateQualifiedNames(BfAstNode* node);
|
||||
BfFieldDtorDeclaration* CreateFieldDtorDeclaration(BfAstNode* srcNode);
|
||||
BfFieldDeclaration* CreateFieldDeclaration(BfTokenNode* tokenNode, BfTypeReference* typeRef, BfIdentifierNode* nameIdentifier, BfFieldDeclaration* prevFieldDeclaration);
|
||||
|
|
|
@ -58,7 +58,7 @@ bool BfSource::WantsStats()
|
|||
return ((int)parser->mFileName.IndexOf("main2.cs") != -1);
|
||||
}
|
||||
|
||||
void BfSource::AddErrorNode(BfAstNode* astNode)
|
||||
BfErrorNode* BfSource::CreateErrorNode(BfAstNode* astNode)
|
||||
{
|
||||
BfErrorNode* errorNode = BfNodeDynCast<BfErrorNode>(astNode);
|
||||
if (errorNode == NULL)
|
||||
|
@ -67,8 +67,12 @@ void BfSource::AddErrorNode(BfAstNode* astNode)
|
|||
errorNode->Init(astNode->GetSrcStart(), astNode->GetSrcStart(), astNode->GetSrcEnd());
|
||||
errorNode->mRefNode = astNode;
|
||||
}
|
||||
return errorNode;
|
||||
}
|
||||
|
||||
mPendingErrorNodes.push_back(errorNode);
|
||||
void BfSource::AddErrorNode(BfAstNode* astNode)
|
||||
{
|
||||
mPendingErrorNodes.push_back(CreateErrorNode(astNode));
|
||||
}
|
||||
|
||||
int BfSource::AllocChars(int charCount)
|
||||
|
|
|
@ -102,6 +102,7 @@ public:
|
|||
|
||||
virtual BfParser* ToParser() { return NULL; }
|
||||
|
||||
BfErrorNode* BfSource::CreateErrorNode(BfAstNode* astNode);
|
||||
void AddErrorNode(BfAstNode* astNode);
|
||||
int AllocChars(int charCount);
|
||||
void FinishSideNodes();
|
||||
|
|
|
@ -3517,6 +3517,10 @@ void BfModule::Visit(BfUncheckedStatement* uncheckedStmt)
|
|||
|
||||
void BfModule::DoIfStatement(BfIfStatement* ifStmt, bool includeTrueStmt, bool includeFalseStmt)
|
||||
{
|
||||
auto autoComplete = mCompiler->GetAutoComplete();
|
||||
if (autoComplete != NULL)
|
||||
autoComplete->CheckIdentifier(ifStmt->mIfToken, true);
|
||||
|
||||
if (ifStmt->mCondition == NULL)
|
||||
{
|
||||
AssertErrorState();
|
||||
|
@ -3764,6 +3768,10 @@ void BfModule::Visit(BfDeleteStatement* deleteStmt)
|
|||
{
|
||||
UpdateSrcPos(deleteStmt);
|
||||
|
||||
auto autoComplete = mCompiler->GetAutoComplete();
|
||||
if (autoComplete != NULL)
|
||||
autoComplete->CheckIdentifier(deleteStmt->mDeleteToken, true);
|
||||
|
||||
bool isAppendDelete = false;
|
||||
BfTypedValue customAllocator;
|
||||
if (deleteStmt->mAllocExpr != NULL)
|
||||
|
@ -5463,6 +5471,10 @@ void BfModule::Visit(BfWhileStatement* whileStmt)
|
|||
|
||||
void BfModule::Visit(BfForStatement* forStmt)
|
||||
{
|
||||
auto autoComplete = mCompiler->GetAutoComplete();
|
||||
if (autoComplete != NULL)
|
||||
autoComplete->CheckIdentifier(forStmt->mForToken, true);
|
||||
|
||||
UpdateSrcPos(forStmt);
|
||||
|
||||
auto startBB = mBfIRBuilder->CreateBlock("for.start", true);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue