mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Ambiguity between template arguments and relational expression, bug 104706.
This commit is contained in:
parent
2b53efe517
commit
08cce46ebc
5 changed files with 122 additions and 83 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2004, 2007 IBM Corporation and others.
|
* Copyright (c) 2004, 2008 IBM Corporation and others.
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License v1.0
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
|
@ -9,10 +9,6 @@
|
||||||
* IBM Corporation - initial API and implementation
|
* IBM Corporation - initial API and implementation
|
||||||
* Markus Schorn (Wind River Systems)
|
* Markus Schorn (Wind River Systems)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
|
|
||||||
/*
|
|
||||||
* Created on Nov 22, 2004
|
|
||||||
*/
|
|
||||||
package org.eclipse.cdt.core.parser.tests.ast2;
|
package org.eclipse.cdt.core.parser.tests.ast2;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -266,7 +262,8 @@ public class AST2BaseTest extends BaseTestCase {
|
||||||
shouldVisitNames = true;
|
shouldVisitNames = true;
|
||||||
}
|
}
|
||||||
public List nameList = new ArrayList();
|
public List nameList = new ArrayList();
|
||||||
public int visit( IASTName name ){
|
@Override
|
||||||
|
public int visit( IASTName name ){
|
||||||
nameList.add( name );
|
nameList.add( name );
|
||||||
return PROCESS_CONTINUE;
|
return PROCESS_CONTINUE;
|
||||||
}
|
}
|
||||||
|
@ -295,7 +292,8 @@ public class AST2BaseTest extends BaseTestCase {
|
||||||
shouldVisitNames = true;
|
shouldVisitNames = true;
|
||||||
}
|
}
|
||||||
public List<IASTName> nameList = new ArrayList<IASTName>();
|
public List<IASTName> nameList = new ArrayList<IASTName>();
|
||||||
public int visit(IASTName name) {
|
@Override
|
||||||
|
public int visit(IASTName name) {
|
||||||
nameList.add(name);
|
nameList.add(name);
|
||||||
return PROCESS_CONTINUE;
|
return PROCESS_CONTINUE;
|
||||||
}
|
}
|
||||||
|
@ -361,6 +359,7 @@ public class AST2BaseTest extends BaseTestCase {
|
||||||
public int numProblemBindings=0;
|
public int numProblemBindings=0;
|
||||||
public int numNullBindings=0;
|
public int numNullBindings=0;
|
||||||
public List nameList = new ArrayList();
|
public List nameList = new ArrayList();
|
||||||
|
@Override
|
||||||
public int visit( IASTName name ){
|
public int visit( IASTName name ){
|
||||||
nameList.add( name );
|
nameList.add( name );
|
||||||
IBinding binding = name.resolveBinding();
|
IBinding binding = name.resolveBinding();
|
||||||
|
@ -385,6 +384,7 @@ public class AST2BaseTest extends BaseTestCase {
|
||||||
public int numProblemBindings=0;
|
public int numProblemBindings=0;
|
||||||
public int numNullBindings=0;
|
public int numNullBindings=0;
|
||||||
public List nameList = new ArrayList();
|
public List nameList = new ArrayList();
|
||||||
|
@Override
|
||||||
public int visit( IASTName name ){
|
public int visit( IASTName name ){
|
||||||
nameList.add( name );
|
nameList.add( name );
|
||||||
IBinding binding = name.resolveBinding();
|
IBinding binding = name.resolveBinding();
|
||||||
|
@ -453,7 +453,7 @@ public class AST2BaseTest extends BaseTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
names= (IASTName[]) lnames.toArray(new IASTName[lnames.size()]);
|
names= (IASTName[]) lnames.toArray(new IASTName[lnames.size()]);
|
||||||
assertEquals("<>1 name found for \""+section+"\"", 1, names.length);
|
assertEquals("found " + names.length + " names for \""+section.substring(0, len)+"\"", 1, names.length);
|
||||||
|
|
||||||
IBinding binding = names[0].resolveBinding();
|
IBinding binding = names[0].resolveBinding();
|
||||||
assertNotNull("No binding for "+names[0].getRawSignature(), binding);
|
assertNotNull("No binding for "+names[0].getRawSignature(), binding);
|
||||||
|
|
|
@ -5708,4 +5708,17 @@ public class AST2CPPTests extends AST2BaseTest {
|
||||||
bh.assertNonProblem("foo3", 4);
|
bh.assertNonProblem("foo3", 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// int foo2() {
|
||||||
|
// int relayIndex = -1;
|
||||||
|
// int numRelays = 0;
|
||||||
|
// if( relayIndex < 0 || relayIndex > numRelays )
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
public void testTemplateIDAmbiguity_Bug104706() throws Exception {
|
||||||
|
BindingAssertionHelper bh= new BindingAssertionHelper(getContents(1)[0].toString(), true);
|
||||||
|
|
||||||
|
bh.assertNonProblem("relayIndex <", 10);
|
||||||
|
bh.assertNonProblem("relayIndex >", 10);
|
||||||
|
bh.assertNonProblem("numRelays )", 9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,21 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2002, 2007 IBM Corporation and others.
|
* Copyright (c) 2002, 2008 IBM Corporation and others.
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License v1.0
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
* http://www.eclipse.org/legal/epl-v10.html
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* IBM Rational Software - Initial API and implementation
|
* IBM Rational Software - Initial API and implementation
|
||||||
|
* Markus Schorn (Wind River Systems)
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.eclipse.cdt.core.parser;
|
package org.eclipse.cdt.core.parser;
|
||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author jcamelon
|
* @author jcamelon
|
||||||
|
@ -27,13 +30,13 @@ public interface ITokenDuple {
|
||||||
*/
|
*/
|
||||||
public abstract IToken getLastToken();
|
public abstract IToken getLastToken();
|
||||||
|
|
||||||
public List [] getTemplateIdArgLists();
|
public List<IASTNode>[] getTemplateIdArgLists();
|
||||||
|
|
||||||
public ITokenDuple getLastSegment();
|
public ITokenDuple getLastSegment();
|
||||||
public ITokenDuple getLeadingSegments();
|
public ITokenDuple getLeadingSegments();
|
||||||
public int getSegmentCount();
|
public int getSegmentCount();
|
||||||
|
|
||||||
public abstract Iterator iterator();
|
public abstract Iterator<IToken> iterator();
|
||||||
public abstract String toString();
|
public abstract String toString();
|
||||||
public char [] toCharArray();
|
public char [] toCharArray();
|
||||||
|
|
||||||
|
|
|
@ -276,13 +276,13 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List templateArgumentList() throws EndOfFileException,
|
protected List<IASTNode> templateArgumentList() throws EndOfFileException,
|
||||||
BacktrackException {
|
BacktrackException {
|
||||||
IToken start = LA(1);
|
IToken start = LA(1);
|
||||||
int startingOffset = start.getOffset();
|
int startingOffset = start.getOffset();
|
||||||
int endOffset = 0;
|
int endOffset = 0;
|
||||||
start = null;
|
start = null;
|
||||||
List list = new ArrayList();
|
List<IASTNode> list = new ArrayList<IASTNode>();
|
||||||
|
|
||||||
boolean completedArg = false;
|
boolean completedArg = false;
|
||||||
boolean failed = false;
|
boolean failed = false;
|
||||||
|
@ -363,10 +363,12 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
case IToken.tCOMPLETION:
|
case IToken.tCOMPLETION:
|
||||||
case IToken.tEOC:
|
case IToken.tEOC:
|
||||||
last = consume();
|
last = consume();
|
||||||
IToken templateLast = consumeTemplateArguments(last, argumentList);
|
if (!fNoTemplateArguments) {
|
||||||
if (last != templateLast) {
|
IToken templateLast = consumeTemplateArguments(last, argumentList);
|
||||||
last = templateLast;
|
if (last != templateLast) {
|
||||||
hasTemplateId = true;
|
last = templateLast;
|
||||||
|
hasTemplateId = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -378,10 +380,13 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (LT(1) == IToken.tCOLONCOLON) {
|
while (LT(1) == IToken.tCOLONCOLON) {
|
||||||
|
boolean checkTemplateArgs= !fNoTemplateArguments;
|
||||||
last = consume();
|
last = consume();
|
||||||
|
|
||||||
if (LT(1) == IToken.t_template)
|
if (LT(1) == IToken.t_template) {
|
||||||
|
checkTemplateArgs= true;
|
||||||
consume();
|
consume();
|
||||||
|
}
|
||||||
|
|
||||||
if (LT(1) == IToken.tBITCOMPLEMENT)
|
if (LT(1) == IToken.tBITCOMPLEMENT)
|
||||||
consume();
|
consume();
|
||||||
|
@ -396,7 +401,9 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
case IToken.tCOMPLETION:
|
case IToken.tCOMPLETION:
|
||||||
case IToken.tEOC:
|
case IToken.tEOC:
|
||||||
last = consume();
|
last = consume();
|
||||||
last = consumeTemplateArguments(last, argumentList);
|
if (checkTemplateArgs) {
|
||||||
|
last = consumeTemplateArguments(last, argumentList);
|
||||||
|
}
|
||||||
if (last.getType() == IToken.tGT || last.getType() == IToken.tEOC)
|
if (last.getType() == IToken.tGT || last.getType() == IToken.tEOC)
|
||||||
hasTemplateId = true;
|
hasTemplateId = true;
|
||||||
}
|
}
|
||||||
|
@ -425,16 +432,16 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
IToken secondMark = mark();
|
IToken secondMark = mark();
|
||||||
consume();
|
consume();
|
||||||
try {
|
try {
|
||||||
List list = templateArgumentList();
|
List<IASTNode> list = templateArgumentList();
|
||||||
argumentList.addSegment(list);
|
argumentList.addSegment(list);
|
||||||
switch (LT(1)) {
|
switch (LT(1)) {
|
||||||
case IToken.tGT:
|
case IToken.tGT:
|
||||||
case IToken.tEOC:
|
case IToken.tEOC:
|
||||||
last = consume();
|
if( LT(2) == IToken.tINTEGER || LT(2) == IToken.tFLOATINGPT) {
|
||||||
if( LT(1) == IToken.tINTEGER || LT(1) == IToken.tFLOATINGPT ) {
|
|
||||||
backup( secondMark );
|
backup( secondMark );
|
||||||
return last;
|
return last;
|
||||||
}
|
}
|
||||||
|
last = consume();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw backtrack;
|
throw backtrack;
|
||||||
|
@ -530,7 +537,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
* @throws BacktrackException
|
* @throws BacktrackException
|
||||||
* request a backtrack
|
* request a backtrack
|
||||||
*/
|
*/
|
||||||
protected void consumePointerOperators(List collection)
|
protected void consumePointerOperators(List<IASTPointerOperator> collection)
|
||||||
throws EndOfFileException, BacktrackException {
|
throws EndOfFileException, BacktrackException {
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
@ -1862,6 +1869,8 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
|
|
||||||
private final IIndex index;
|
private final IIndex index;
|
||||||
|
|
||||||
|
private boolean fNoTemplateArguments;
|
||||||
|
|
||||||
private static final int DEFAULT_PARM_LIST_SIZE = 4;
|
private static final int DEFAULT_PARM_LIST_SIZE = 4;
|
||||||
|
|
||||||
private static final int DEFAULT_POINTEROPS_LIST_SIZE = 4;
|
private static final int DEFAULT_POINTEROPS_LIST_SIZE = 4;
|
||||||
|
@ -2139,7 +2148,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List parms = templateParameterList();
|
List<ICPPASTTemplateParameter> parms = templateParameterList();
|
||||||
consume(IToken.tGT);
|
consume(IToken.tGT);
|
||||||
ICPPASTTemplateDeclaration templateDecl = createTemplateDeclaration();
|
ICPPASTTemplateDeclaration templateDecl = createTemplateDeclaration();
|
||||||
try
|
try
|
||||||
|
@ -2150,7 +2159,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
templateDecl.setExported(exported);
|
templateDecl.setExported(exported);
|
||||||
templateDecl.setDeclaration(d);
|
templateDecl.setDeclaration(d);
|
||||||
for (int i = 0; i < parms.size(); ++i) {
|
for (int i = 0; i < parms.size(); ++i) {
|
||||||
ICPPASTTemplateParameter parm = (ICPPASTTemplateParameter) parms.get(i);
|
ICPPASTTemplateParameter parm = parms.get(i);
|
||||||
templateDecl.addTemplateParamter(parm);
|
templateDecl.addTemplateParamter(parm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2204,11 +2213,11 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
* @throws BacktrackException
|
* @throws BacktrackException
|
||||||
* request for a backtrack
|
* request for a backtrack
|
||||||
*/
|
*/
|
||||||
protected List templateParameterList() throws BacktrackException,
|
protected List<ICPPASTTemplateParameter> templateParameterList() throws BacktrackException,
|
||||||
EndOfFileException {
|
EndOfFileException {
|
||||||
// if we have gotten this far then we have a true template-declaration
|
// if we have gotten this far then we have a true template-declaration
|
||||||
// iterate through the template parameter list
|
// iterate through the template parameter list
|
||||||
List returnValue = new ArrayList(DEFAULT_PARM_LIST_SIZE);
|
List<ICPPASTTemplateParameter> returnValue = new ArrayList<ICPPASTTemplateParameter>(DEFAULT_PARM_LIST_SIZE);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (LT(1) == IToken.tGT)
|
if (LT(1) == IToken.tGT)
|
||||||
|
@ -2250,7 +2259,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
IToken firstToken = consume();
|
IToken firstToken = consume();
|
||||||
consume(IToken.tLT);
|
consume(IToken.tLT);
|
||||||
|
|
||||||
List subResult = templateParameterList();
|
List<ICPPASTTemplateParameter> subResult = templateParameterList();
|
||||||
consume(IToken.tGT);
|
consume(IToken.tGT);
|
||||||
int last = consume(IToken.t_class).getEndOffset();
|
int last = consume(IToken.t_class).getEndOffset();
|
||||||
IASTName identifierName = null;
|
IASTName identifierName = null;
|
||||||
|
@ -2275,7 +2284,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < subResult.size(); ++i) {
|
for (int i = 0; i < subResult.size(); ++i) {
|
||||||
ICPPASTTemplateParameter p = (ICPPASTTemplateParameter) subResult.get(i);
|
ICPPASTTemplateParameter p = subResult.get(i);
|
||||||
parm.addTemplateParamter(p);
|
parm.addTemplateParamter(p);
|
||||||
}
|
}
|
||||||
returnValue.add(parm);
|
returnValue.add(parm);
|
||||||
|
@ -2587,10 +2596,10 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
}
|
}
|
||||||
result.setTemplateName(templateIdName);
|
result.setTemplateName(templateIdName);
|
||||||
if (duple.getTemplateIdArgLists() != null) {
|
if (duple.getTemplateIdArgLists() != null) {
|
||||||
List args = duple.getTemplateIdArgLists()[0];
|
List<IASTNode> args= duple.getTemplateIdArgLists()[0];
|
||||||
if (args != null)
|
if (args != null)
|
||||||
for (int i = 0; i < args.size(); ++i) {
|
for (int i = 0; i < args.size(); ++i) {
|
||||||
IASTNode n = (IASTNode) args.get(i);
|
IASTNode n = args.get(i);
|
||||||
if (n instanceof IASTTypeId)
|
if (n instanceof IASTTypeId)
|
||||||
result.addTemplateArgument((IASTTypeId) n);
|
result.addTemplateArgument((IASTTypeId) n);
|
||||||
else if(n instanceof IASTExpression)
|
else if(n instanceof IASTExpression)
|
||||||
|
@ -2712,7 +2721,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
boolean hasFunctionTryBlock = false;
|
boolean hasFunctionTryBlock = false;
|
||||||
boolean consumedSemi = false;
|
boolean consumedSemi = false;
|
||||||
int semiOffset = 0;
|
int semiOffset = 0;
|
||||||
List constructorChain = Collections.EMPTY_LIST;
|
List<IASTNode> constructorChain = Collections.emptyList();
|
||||||
|
|
||||||
switch (LT(1)) {
|
switch (LT(1)) {
|
||||||
case IToken.tSEMI:
|
case IToken.tSEMI:
|
||||||
|
@ -2724,13 +2733,13 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
case IToken.t_try:
|
case IToken.t_try:
|
||||||
consume();
|
consume();
|
||||||
if (LT(1) == IToken.tCOLON) {
|
if (LT(1) == IToken.tCOLON) {
|
||||||
constructorChain = new ArrayList(DEFAULT_CONSTRUCTOR_CHAIN_LIST_SIZE);
|
constructorChain = new ArrayList<IASTNode>(DEFAULT_CONSTRUCTOR_CHAIN_LIST_SIZE);
|
||||||
ctorInitializer(constructorChain);
|
ctorInitializer(constructorChain);
|
||||||
}
|
}
|
||||||
hasFunctionTryBlock = true;
|
hasFunctionTryBlock = true;
|
||||||
break;
|
break;
|
||||||
case IToken.tCOLON:
|
case IToken.tCOLON:
|
||||||
constructorChain = new ArrayList(DEFAULT_CONSTRUCTOR_CHAIN_LIST_SIZE);
|
constructorChain = new ArrayList<IASTNode>(DEFAULT_CONSTRUCTOR_CHAIN_LIST_SIZE);
|
||||||
ctorInitializer(constructorChain);
|
ctorInitializer(constructorChain);
|
||||||
hasFunctionBody = true;
|
hasFunctionBody = true;
|
||||||
break;
|
break;
|
||||||
|
@ -2794,10 +2803,10 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
((ASTNode) funcDefinition).setLength(calculateEndOffset(s) - firstOffset);
|
((ASTNode) funcDefinition).setLength(calculateEndOffset(s) - firstOffset);
|
||||||
|
|
||||||
if (hasFunctionTryBlock && declarator instanceof ICPPASTFunctionTryBlockDeclarator) {
|
if (hasFunctionTryBlock && declarator instanceof ICPPASTFunctionTryBlockDeclarator) {
|
||||||
List handlers = new ArrayList(DEFAULT_CATCH_HANDLER_LIST_SIZE);
|
List<ICPPASTCatchHandler> handlers = new ArrayList<ICPPASTCatchHandler>(DEFAULT_CATCH_HANDLER_LIST_SIZE);
|
||||||
catchHandlerSequence(handlers);
|
catchHandlerSequence(handlers);
|
||||||
for (int i = 0; i < handlers.size(); ++i) {
|
for (int i = 0; i < handlers.size(); ++i) {
|
||||||
ICPPASTCatchHandler handler = (ICPPASTCatchHandler) handlers.get(i);
|
ICPPASTCatchHandler handler = handlers.get(i);
|
||||||
((ICPPASTFunctionTryBlockDeclarator) declarator).addCatchHandler(handler);
|
((ICPPASTFunctionTryBlockDeclarator) declarator).addCatchHandler(handler);
|
||||||
((ASTNode) funcDefinition).setLength(calculateEndOffset(handler) - firstOffset);
|
((ASTNode) funcDefinition).setLength(calculateEndOffset(handler) - firstOffset);
|
||||||
}
|
}
|
||||||
|
@ -2842,7 +2851,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
* @throws BacktrackException
|
* @throws BacktrackException
|
||||||
* request a backtrack
|
* request a backtrack
|
||||||
*/
|
*/
|
||||||
protected void ctorInitializer(List collection) throws EndOfFileException,
|
protected void ctorInitializer(List<IASTNode> collection) throws EndOfFileException,
|
||||||
BacktrackException {
|
BacktrackException {
|
||||||
consume();
|
consume();
|
||||||
ctorLoop: for (;;) {
|
ctorLoop: for (;;) {
|
||||||
|
@ -3545,10 +3554,10 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
la = null;
|
la = null;
|
||||||
IASTDeclarator innerDecl = null;
|
IASTDeclarator innerDecl = null;
|
||||||
IASTName declaratorName = null;
|
IASTName declaratorName = null;
|
||||||
List pointerOps = new ArrayList(DEFAULT_POINTEROPS_LIST_SIZE);
|
List<IASTPointerOperator> pointerOps = new ArrayList<IASTPointerOperator>(DEFAULT_POINTEROPS_LIST_SIZE);
|
||||||
List parameters = Collections.EMPTY_LIST;
|
List<IASTNode> parameters = Collections.emptyList();
|
||||||
List arrayMods = Collections.EMPTY_LIST;
|
List<IASTNode> arrayMods = Collections.emptyList();
|
||||||
List exceptionSpecIds = Collections.EMPTY_LIST;
|
List<IASTNode> exceptionSpecIds = Collections.emptyList();
|
||||||
boolean encounteredVarArgs = false;
|
boolean encounteredVarArgs = false;
|
||||||
boolean tryEncountered = false;
|
boolean tryEncountered = false;
|
||||||
IASTExpression bitField = null;
|
IASTExpression bitField = null;
|
||||||
|
@ -3563,7 +3572,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
__attribute_decl_seq(supportAttributeSpecifiers, supportDeclspecSpecifiers);
|
__attribute_decl_seq(supportAttributeSpecifiers, supportDeclspecSpecifiers);
|
||||||
|
|
||||||
if (!pointerOps.isEmpty())
|
if (!pointerOps.isEmpty())
|
||||||
finalOffset = calculateEndOffset((IASTNode) pointerOps.get(pointerOps.size() - 1));
|
finalOffset = calculateEndOffset(pointerOps.get(pointerOps.size() - 1));
|
||||||
|
|
||||||
if (!forNewTypeId && LT(1) == IToken.tLPAREN) {
|
if (!forNewTypeId && LT(1) == IToken.tLPAREN) {
|
||||||
IToken mark = mark();
|
IToken mark = mark();
|
||||||
|
@ -3655,7 +3664,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
IASTParameterDeclaration p = parameterDeclaration();
|
IASTParameterDeclaration p = parameterDeclaration();
|
||||||
finalOffset = calculateEndOffset(p);
|
finalOffset = calculateEndOffset(p);
|
||||||
if (parameters == Collections.EMPTY_LIST)
|
if (parameters == Collections.EMPTY_LIST)
|
||||||
parameters = new ArrayList(DEFAULT_PARM_LIST_SIZE);
|
parameters = new ArrayList<IASTNode>(DEFAULT_PARM_LIST_SIZE);
|
||||||
parameters.add(p);
|
parameters.add(p);
|
||||||
seenParameter = true;
|
seenParameter = true;
|
||||||
}
|
}
|
||||||
|
@ -3693,7 +3702,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
// check for throws clause here
|
// check for throws clause here
|
||||||
|
|
||||||
if (LT(1) == IToken.t_throw) {
|
if (LT(1) == IToken.t_throw) {
|
||||||
exceptionSpecIds = new ArrayList(DEFAULT_SIZE_EXCEPTIONS_LIST);
|
exceptionSpecIds = new ArrayList<IASTNode>(DEFAULT_SIZE_EXCEPTIONS_LIST);
|
||||||
consume(); // throw
|
consume(); // throw
|
||||||
consume(IToken.tLPAREN); // (
|
consume(IToken.tLPAREN); // (
|
||||||
boolean done = false;
|
boolean done = false;
|
||||||
|
@ -3759,10 +3768,10 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
case IToken.tLBRACKET:
|
case IToken.tLBRACKET:
|
||||||
if (forNewTypeId)
|
if (forNewTypeId)
|
||||||
break;
|
break;
|
||||||
arrayMods = new ArrayList(DEFAULT_POINTEROPS_LIST_SIZE);
|
arrayMods = new ArrayList<IASTNode>(DEFAULT_POINTEROPS_LIST_SIZE);
|
||||||
consumeArrayModifiers(arrayMods);
|
consumeArrayModifiers(arrayMods);
|
||||||
if (!arrayMods.isEmpty())
|
if (!arrayMods.isEmpty())
|
||||||
finalOffset = calculateEndOffset((IASTNode) arrayMods.get(arrayMods.size() - 1));
|
finalOffset = calculateEndOffset(arrayMods.get(arrayMods.size() - 1));
|
||||||
continue;
|
continue;
|
||||||
case IToken.tCOLON:
|
case IToken.tCOLON:
|
||||||
consume();
|
consume();
|
||||||
|
@ -3818,7 +3827,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
d = createDeclarator();
|
d = createDeclarator();
|
||||||
}
|
}
|
||||||
for (int i = 0; i < pointerOps.size(); ++i) {
|
for (int i = 0; i < pointerOps.size(); ++i) {
|
||||||
IASTPointerOperator po = (IASTPointerOperator) pointerOps.get(i);
|
IASTPointerOperator po = pointerOps.get(i);
|
||||||
d.addPointerOperator(po);
|
d.addPointerOperator(po);
|
||||||
}
|
}
|
||||||
if (innerDecl != null) {
|
if (innerDecl != null) {
|
||||||
|
@ -4179,7 +4188,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
return new CPPASTBaseSpecifier();
|
return new CPPASTBaseSpecifier();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void catchHandlerSequence(List collection)
|
protected void catchHandlerSequence(List<ICPPASTCatchHandler> collection)
|
||||||
throws EndOfFileException, BacktrackException {
|
throws EndOfFileException, BacktrackException {
|
||||||
if (LT(1) == IToken.tEOC)
|
if (LT(1) == IToken.tEOC)
|
||||||
return;
|
return;
|
||||||
|
@ -4349,7 +4358,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
return new CPPASTTranslationUnit();
|
return new CPPASTTranslationUnit();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void consumeArrayModifiers(List collection)
|
protected void consumeArrayModifiers(List<IASTNode> collection)
|
||||||
throws EndOfFileException, BacktrackException {
|
throws EndOfFileException, BacktrackException {
|
||||||
while (LT(1) == IToken.tLBRACKET) {
|
while (LT(1) == IToken.tLBRACKET) {
|
||||||
int o = consume().getOffset(); // eat the '['
|
int o = consume().getOffset(); // eat the '['
|
||||||
|
@ -4608,14 +4617,14 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
protected IASTStatement parseTryStatement() throws EndOfFileException, BacktrackException {
|
protected IASTStatement parseTryStatement() throws EndOfFileException, BacktrackException {
|
||||||
int startO = consume().getOffset();
|
int startO = consume().getOffset();
|
||||||
IASTStatement tryBlock = compoundStatement();
|
IASTStatement tryBlock = compoundStatement();
|
||||||
List catchHandlers = new ArrayList(DEFAULT_CATCH_HANDLER_LIST_SIZE);
|
List<ICPPASTCatchHandler> catchHandlers = new ArrayList<ICPPASTCatchHandler>(DEFAULT_CATCH_HANDLER_LIST_SIZE);
|
||||||
catchHandlerSequence(catchHandlers);
|
catchHandlerSequence(catchHandlers);
|
||||||
ICPPASTTryBlockStatement tryStatement = createTryBlockStatement();
|
ICPPASTTryBlockStatement tryStatement = createTryBlockStatement();
|
||||||
((ASTNode) tryStatement).setOffset(startO);
|
((ASTNode) tryStatement).setOffset(startO);
|
||||||
tryStatement.setTryBody(tryBlock);
|
tryStatement.setTryBody(tryBlock);
|
||||||
|
|
||||||
for (int i = 0; i < catchHandlers.size(); ++i) {
|
for (int i = 0; i < catchHandlers.size(); ++i) {
|
||||||
ICPPASTCatchHandler handler = (ICPPASTCatchHandler) catchHandlers.get(i);
|
ICPPASTCatchHandler handler = catchHandlers.get(i);
|
||||||
tryStatement.addCatchHandler(handler);
|
tryStatement.addCatchHandler(handler);
|
||||||
((ASTNode) tryStatement).setLength(calculateEndOffset(handler) - startO);
|
((ASTNode) tryStatement).setLength(calculateEndOffset(handler) - startO);
|
||||||
}
|
}
|
||||||
|
@ -4654,7 +4663,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
protected IASTStatement parseWhileStatement() throws EndOfFileException, BacktrackException {
|
protected IASTStatement parseWhileStatement() throws EndOfFileException, BacktrackException {
|
||||||
int startOffset = consume().getOffset();
|
int startOffset = consume().getOffset();
|
||||||
consume(IToken.tLPAREN);
|
consume(IToken.tLPAREN);
|
||||||
IASTNode while_condition = cppStyleCondition(true);
|
IASTNode while_condition = cppStyleCondition(IToken.tRPAREN);
|
||||||
switch (LT(1)) {
|
switch (LT(1)) {
|
||||||
case IToken.tRPAREN:
|
case IToken.tRPAREN:
|
||||||
consume();
|
consume();
|
||||||
|
@ -4689,20 +4698,31 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
* @param expectSemi TODO
|
* @param expectSemi TODO
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
protected IASTNode cppStyleCondition(boolean expectSemi) throws BacktrackException, EndOfFileException {
|
protected IASTNode cppStyleCondition(int expectToken) throws BacktrackException, EndOfFileException {
|
||||||
IToken mark = mark();
|
IToken mark = mark();
|
||||||
try {
|
try {
|
||||||
IASTExpression e = expression();
|
IASTExpression e = expression();
|
||||||
if( ! expectSemi )
|
final int lt1= LT(1);
|
||||||
return e;
|
if (lt1 == expectToken || lt1 == IToken.tEOC) {
|
||||||
switch (LT(1)) {
|
return e;
|
||||||
case IToken.tRPAREN:
|
|
||||||
case IToken.tEOC:
|
|
||||||
return e;
|
|
||||||
default:
|
|
||||||
throwBacktrack(LA(1));
|
|
||||||
}
|
}
|
||||||
return e;
|
if (!fNoTemplateArguments) {
|
||||||
|
// bug 104706, ambiguity between template arguments and conditional expression,
|
||||||
|
// try without template args
|
||||||
|
backup(mark);
|
||||||
|
try {
|
||||||
|
fNoTemplateArguments= true;
|
||||||
|
e= expression();
|
||||||
|
final int lt11= LT(1);
|
||||||
|
if (lt11 == expectToken || lt11 == IToken.tEOC) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
fNoTemplateArguments= false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throwBacktrack(LA(1));
|
||||||
} catch (BacktrackException bt) {
|
} catch (BacktrackException bt) {
|
||||||
backup(mark);
|
backup(mark);
|
||||||
try {
|
try {
|
||||||
|
@ -4710,9 +4730,9 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
} catch (BacktrackException b) {
|
} catch (BacktrackException b) {
|
||||||
failParse();
|
failParse();
|
||||||
throwBacktrack(b);
|
throwBacktrack(b);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class EmptyVisitor extends CPPASTVisitor {
|
private static class EmptyVisitor extends CPPASTVisitor {
|
||||||
|
@ -4749,7 +4769,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
consume(IToken.tLPAREN);
|
consume(IToken.tLPAREN);
|
||||||
IASTNode condition = null;
|
IASTNode condition = null;
|
||||||
try {
|
try {
|
||||||
condition = cppStyleCondition(true);
|
condition = cppStyleCondition(IToken.tRPAREN);
|
||||||
// condition
|
// condition
|
||||||
if (LT(1) == IToken.tEOC) {
|
if (LT(1) == IToken.tEOC) {
|
||||||
// Completing in the condition
|
// Completing in the condition
|
||||||
|
@ -4898,7 +4918,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
int startOffset;
|
int startOffset;
|
||||||
startOffset = consume().getOffset();
|
startOffset = consume().getOffset();
|
||||||
consume(IToken.tLPAREN);
|
consume(IToken.tLPAREN);
|
||||||
IASTNode switch_condition = cppStyleCondition(true);
|
IASTNode switch_condition = cppStyleCondition(IToken.tRPAREN);
|
||||||
switch (LT(1)) {
|
switch (LT(1)) {
|
||||||
case IToken.tRPAREN:
|
case IToken.tRPAREN:
|
||||||
consume();
|
consume();
|
||||||
|
@ -4945,7 +4965,7 @@ public class GNUCPPSourceParser extends AbstractGNUSourceCodeParser {
|
||||||
case IToken.tEOC:
|
case IToken.tEOC:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
for_condition = cppStyleCondition(false);
|
for_condition = cppStyleCondition(IToken.tSEMI);
|
||||||
}
|
}
|
||||||
switch (LT(1)) {
|
switch (LT(1)) {
|
||||||
case IToken.tSEMI:
|
case IToken.tSEMI:
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Copyright (c) 2002, 2007 IBM Corporation and others.
|
* Copyright (c) 2002, 2008 IBM Corporation and others.
|
||||||
* All rights reserved. This program and the accompanying materials
|
* All rights reserved. This program and the accompanying materials
|
||||||
* are made available under the terms of the Eclipse Public License v1.0
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
* which accompanies this distribution, and is available at
|
* which accompanies this distribution, and is available at
|
||||||
* http://www.eclipse.org/legal/epl-v10.html
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
*
|
*
|
||||||
* Contributors:
|
* Contributors:
|
||||||
* IBM Rational Software - Initial API and implementation
|
* IBM Rational Software - Initial API and implementation
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
package org.eclipse.cdt.internal.core.parser.token;
|
package org.eclipse.cdt.internal.core.parser.token;
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
import org.eclipse.cdt.core.dom.ast.IASTNode;
|
||||||
import org.eclipse.cdt.core.parser.IToken;
|
import org.eclipse.cdt.core.parser.IToken;
|
||||||
import org.eclipse.cdt.core.parser.ITokenDuple;
|
import org.eclipse.cdt.core.parser.ITokenDuple;
|
||||||
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
import org.eclipse.cdt.core.parser.util.CharArrayUtils;
|
||||||
|
@ -56,7 +57,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
return lastToken;
|
return lastToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator iterator()
|
public Iterator<IToken> iterator()
|
||||||
{
|
{
|
||||||
return new TokenIterator();
|
return new TokenIterator();
|
||||||
}
|
}
|
||||||
|
@ -80,9 +81,9 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
last = token;
|
last = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
List [] args = getTemplateIdArgLists();
|
List<IASTNode> [] args = getTemplateIdArgLists();
|
||||||
if( args != null && args[ args.length - 1 ] != null ){
|
if( args != null && args[ args.length - 1 ] != null ){
|
||||||
List newArgs = new ArrayList( 1 );
|
List<List<IASTNode>> newArgs = new ArrayList<List<IASTNode>>( 1 );
|
||||||
newArgs.add( args[ args.length - 1 ] );
|
newArgs.add( args[ args.length - 1 ] );
|
||||||
return TokenFactory.createTokenDuple( first, last, newArgs );
|
return TokenFactory.createTokenDuple( first, last, newArgs );
|
||||||
}
|
}
|
||||||
|
@ -92,7 +93,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
public ITokenDuple[] getSegments()
|
public ITokenDuple[] getSegments()
|
||||||
{
|
{
|
||||||
|
|
||||||
List r = new ArrayList();
|
List<ITokenDuple> r = new ArrayList<ITokenDuple>();
|
||||||
IToken token = null;
|
IToken token = null;
|
||||||
IToken prev = null;
|
IToken prev = null;
|
||||||
IToken last = getLastToken();
|
IToken last = getLastToken();
|
||||||
|
@ -125,7 +126,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
ITokenDuple d = TokenFactory.createTokenDuple( startOfSegment, last );
|
ITokenDuple d = TokenFactory.createTokenDuple( startOfSegment, last );
|
||||||
r.add( d );
|
r.add( d );
|
||||||
}
|
}
|
||||||
return (ITokenDuple[]) r.toArray( new ITokenDuple[ r.size() ]);
|
return r.toArray( new ITokenDuple[ r.size() ]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,8 +163,8 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
}
|
}
|
||||||
|
|
||||||
if( getTemplateIdArgLists() != null ){
|
if( getTemplateIdArgLists() != null ){
|
||||||
List[] args = getTemplateIdArgLists();
|
List<IASTNode>[] args = getTemplateIdArgLists();
|
||||||
List newArgs = new ArrayList( args.length - 1 );
|
List<List<IASTNode>> newArgs = new ArrayList<List<IASTNode>>( args.length - 1 );
|
||||||
boolean foundArgs = false;
|
boolean foundArgs = false;
|
||||||
for( int i = 0; i < args.length - 1; i++ ){
|
for( int i = 0; i < args.length - 1; i++ ){
|
||||||
newArgs.add( args[i] );
|
newArgs.add( args[i] );
|
||||||
|
@ -187,7 +188,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
private static final char[] EMPTY_STRING = "".toCharArray(); //$NON-NLS-1$
|
private static final char[] EMPTY_STRING = "".toCharArray(); //$NON-NLS-1$
|
||||||
private char[] stringRepresentation = null;
|
private char[] stringRepresentation = null;
|
||||||
|
|
||||||
private class TokenIterator implements Iterator
|
private class TokenIterator implements Iterator<IToken>
|
||||||
{
|
{
|
||||||
private IToken iter = firstToken;
|
private IToken iter = firstToken;
|
||||||
|
|
||||||
|
@ -201,7 +202,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see java.util.Iterator#next()
|
* @see java.util.Iterator#next()
|
||||||
*/
|
*/
|
||||||
public Object next() {
|
public IToken next() {
|
||||||
if( ! hasNext() )
|
if( ! hasNext() )
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
IToken temp = iter;
|
IToken temp = iter;
|
||||||
|
@ -285,6 +286,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
if( stringRepresentation == null )
|
if( stringRepresentation == null )
|
||||||
|
@ -369,7 +371,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see org.eclipse.cdt.core.parser.ITokenDuple#getTemplateIdArgLists()
|
* @see org.eclipse.cdt.core.parser.ITokenDuple#getTemplateIdArgLists()
|
||||||
*/
|
*/
|
||||||
public List[] getTemplateIdArgLists() {
|
public List<IASTNode>[] getTemplateIdArgLists() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,6 +416,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
/* (non-Javadoc)
|
/* (non-Javadoc)
|
||||||
* @see java.lang.Object#equals(java.lang.Object)
|
* @see java.lang.Object#equals(java.lang.Object)
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
if( !(other instanceof ITokenDuple ) ) return false;
|
if( !(other instanceof ITokenDuple ) ) return false;
|
||||||
if( ((ITokenDuple) other).getFirstToken().equals( getFirstToken() ) &&
|
if( ((ITokenDuple) other).getFirstToken().equals( getFirstToken() ) &&
|
||||||
|
@ -425,7 +428,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
public ITokenDuple getTemplateIdNameTokenDuple() {
|
public ITokenDuple getTemplateIdNameTokenDuple() {
|
||||||
ITokenDuple nameDuple = getLastSegment();
|
ITokenDuple nameDuple = getLastSegment();
|
||||||
|
|
||||||
List [] argLists = getTemplateIdArgLists();
|
List<IASTNode>[] argLists = getTemplateIdArgLists();
|
||||||
if( argLists == null || argLists[ argLists.length - 1 ] == null )
|
if( argLists == null || argLists[ argLists.length - 1 ] == null )
|
||||||
return nameDuple;
|
return nameDuple;
|
||||||
|
|
||||||
|
@ -463,7 +466,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
public char[] extractNameFromTemplateId(){
|
public char[] extractNameFromTemplateId(){
|
||||||
ITokenDuple nameDuple = getLastSegment();
|
ITokenDuple nameDuple = getLastSegment();
|
||||||
|
|
||||||
List [] argLists = getTemplateIdArgLists();
|
List<IASTNode>[] argLists = getTemplateIdArgLists();
|
||||||
if( argLists == null || argLists[ argLists.length - 1 ] == null )
|
if( argLists == null || argLists[ argLists.length - 1 ] == null )
|
||||||
return nameDuple.toCharArray();
|
return nameDuple.toCharArray();
|
||||||
|
|
||||||
|
@ -545,7 +548,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private String [] generateQualifiedName() {
|
private String [] generateQualifiedName() {
|
||||||
List qn = new ArrayList();
|
List<String> qn = new ArrayList<String>();
|
||||||
IToken i = firstToken;
|
IToken i = firstToken;
|
||||||
while( i != lastToken )
|
while( i != lastToken )
|
||||||
{
|
{
|
||||||
|
@ -577,7 +580,7 @@ public class BasicTokenDuple implements ITokenDuple {
|
||||||
qn.add( i.getImage() );
|
qn.add( i.getImage() );
|
||||||
}
|
}
|
||||||
String [] qualifiedName = new String[ qn.size() ];
|
String [] qualifiedName = new String[ qn.size() ];
|
||||||
return (String[]) qn.toArray( qualifiedName );
|
return qn.toArray( qualifiedName );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Add table
Reference in a new issue