Package dtool.parser

Source Code of dtool.parser.DeeParserTester

/*******************************************************************************
* Copyright (c) 2013, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Bruno Medeiros - initial API and implementation
*******************************************************************************/
package dtool.parser;

import static dtool.util.NewUtils.replaceRegexFirstOccurrence;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;

import melnorme.utilbox.misc.MiscUtil;
import melnorme.utilbox.misc.StringUtil;
import melnorme.utilbox.tests.CommonTestUtils;
import dtool.ast.ASTVisitor;
import dtool.ast.ASTNode;
import dtool.ast.declarations.DeclarationAttrib;
import dtool.ast.declarations.DeclarationAttrib.AttribBodySyntax;
import dtool.ast.definitions.CommonDefinition;
import dtool.ast.definitions.DefUnit;
import dtool.ast.definitions.DefinitionAlias;
import dtool.ast.definitions.DefinitionAlias.DefinitionAliasFragment;
import dtool.ast.definitions.IFunctionParameter;
import dtool.ast.definitions.Module;
import dtool.ast.definitions.TemplateParameter;
import dtool.ast.expressions.ExpLiteralBool;
import dtool.ast.expressions.ExpLiteralFloat;
import dtool.ast.expressions.ExpLiteralInteger;
import dtool.ast.expressions.ExpLiteralMapArray.MapArrayLiteralKeyValue;
import dtool.ast.expressions.ExpLiteralString;
import dtool.ast.expressions.ExpPostfixOperator;
import dtool.ast.expressions.ExpReference;
import dtool.ast.references.AutoReference;
import dtool.ast.statements.SimpleVariableDef;
import dtool.ast.statements.StatementExpression;
import dtool.ast.util.ASTSourceRangeChecker;
import dtool.parser.DeeParser_Parameters.AmbiguousParameter;
import dtool.parser.DeeParser_Parameters.TplOrFnMode;
import dtool.parser.DeeParsingChecks.DeeTestsChecksParser;
import dtool.parser.DeeParsingChecks.ParametersReparseCheck;
import dtool.parser.ParserError.ParserErrorTypes;
import dtool.parser.common.Token;
import dtool.parser.common.AbstractParser.ParseRuleDescription;
import dtool.sourcegen.AnnotatedSource.MetadataEntry;
import dtool.tests.DToolTests;
import dtool.util.NewUtils;


public class DeeParserTester extends CommonTestUtils {
 
  public static final String DONT_CHECK = "#DONTCHECK";
 
  protected final String fullSource;
  protected final String parseRule;
  protected final String expectedRemainingSource;
  protected final String expectedPrintedSource;
  protected final NamedNodeElement[] expectedStructure;
  protected final ArrayList<ParserError> expectedErrors;
  protected final List<MetadataEntry> additionalMetadataOriginal;
  protected HashMap<String, Object> additionalMD;

 
  public DeeParserTester(String fullSource, String parseRule, String expectedRemainingSource,
    String expectedPrintedSource, NamedNodeElement[] expectedStructure, ArrayList<ParserError> expectedErrors,
    List<MetadataEntry> additionalMetadata) {
    this.fullSource = fullSource;
    this.parseRule = parseRule;
    this.expectedRemainingSource = expectedRemainingSource;
    this.expectedPrintedSource = expectedPrintedSource;
    this.expectedStructure = expectedStructure;
    this.expectedErrors = expectedErrors;
    this.additionalMetadataOriginal = additionalMetadata;
  }
 
  public static HashMap<String, Object> buildMetadataMap(List<MetadataEntry> entryList) {
    HashMap<String, Object> entriesMap = new HashMap<>();
   
    for (MetadataEntry metadataEntry : entryList) {
      String key = metadataEntry.name;
      Object existingEntry = entriesMap.get(key);
      if(existingEntry == null) {
        entriesMap.put(key, metadataEntry);
      } else {
        ArrayList<MetadataEntry> listEntry = null;
        if(existingEntry instanceof MetadataEntry) {
          listEntry = new ArrayList<>();
          listEntry.add((MetadataEntry) existingEntry);
        } else {
          listEntry = assertCast(existingEntry, ArrayList.class);
        }
        listEntry.add(metadataEntry);
        entriesMap.put(key, listEntry);
      }
    }
    return entriesMap;
  }
 
  public MetadataEntry getTestMetadata(String mdName) {
    return assertCast(additionalMD.get(mdName), MetadataEntry.class);
  }
 
  public MetadataEntry removeTestMetadata(String mdName) {
    return assertCast(additionalMD.remove(mdName), MetadataEntry.class);
  }
 
  public boolean removeTestMetadataFlag(String mdName) {
    return additionalMD.remove(mdName) != null;
  }
 
  public List<MetadataEntry> removeTestMetadataEntries(String mdName) {
    Object entry = additionalMD.remove(mdName);
    if(entry == null) {
      return Collections.EMPTY_LIST;
    } else if(entry instanceof MetadataEntry) {
      return NewUtils.arrayListFromElement((MetadataEntry) entry);
    }
    return assertCast(entry, List.class);
  }
 
  // The funky name here is to help locate this function in stack traces during debugging
  public void runParserTest______________________() {
    additionalMD = buildMetadataMap(additionalMetadataOriginal);
   
    final DeeTestsChecksParser deeParser = new DeeTestsChecksParser(fullSource);
    DeeParserResult result = parseUsingRule(deeParser);
    if(result == null)
      return;
   
    String parsedSource = checkParsedSource(expectedRemainingSource, deeParser);
    ASTNode mainNode = result.node; // a null result may make sense in some tests
   
    if(mainNode != null) {
      checkBasicStructureContracts(mainNode);
    }
   
    checkExpectedStructure(mainNode, expectedStructure);
   
    if(expectedErrors != null) {
      checkParserErrors(result.getErrors(), expectedErrors);
    }
   
    if(expectedPrintedSource != null) {
      assertTrue(result.errors.size() == 0 ? parsedSource.equals(expectedPrintedSource) : true);
     
      String nodePrintedSource = mainNode == null ? "" : mainNode.toStringAsCode();
      SourceEquivalenceChecker.assertCheck(nodePrintedSource, expectedPrintedSource);
    }
   
    // Check consistency of source ranges (no overlapping ranges)
    if(mainNode != null) {
      ASTSourceRangeChecker.checkConsistency(mainNode);
    }
   
    runAdditionalTests(result, parsedSource);
  }
 
  public String checkParsedSource(final String expectedRemainingSource, final DeeTestsChecksParser deeParser) {
    String parsedSource = fullSource;
    String remainingSource = fullSource.substring(deeParser.getSourcePosition());
    if(expectedRemainingSource == DeeParserTester.DONT_CHECK) {
      parsedSource = fullSource.substring(0, deeParser.getSourcePosition());
    } else if(expectedRemainingSource == null) {
      assertTrue(deeParser.lookAhead() == DeeTokens.EOF);
    } else {
      SourceEquivalenceChecker.assertCheck(remainingSource, expectedRemainingSource);
      parsedSource = fullSource.substring(0, fullSource.length() - expectedRemainingSource.length());
    }
    return parsedSource;
  }
 
  /* ============= Structure Checkers ============= */
 
  public static void checkBasicStructureContracts(ASTNode parsedNode) {
    assertTrue(parsedNode.getParent() == null);
    parsedNode.accept(new ASTVisitor() {
      ASTNode parent = null;
      ASTNode lastVisitedNode = null;
     
      @Override
      public boolean preVisit(ASTNode node) {
        assertTrue(node.isPostParseStatus());
        assertTrue(node.getParent() == parent);
        parent = node;
        lastVisitedNode = node;
        return true;
      }
     
      @Override
      public void postVisit(ASTNode node) {
        assertTrue(node.hasChildren() == (node != lastVisitedNode));
        parent = node.getParent();
      }
    });
  }
 
  public static class NamedNodeElement {
    public static final String IGNORE_ALL = "*";
    public static final String IGNORE_NAME = "?";
   
    public final String name;
    public final NamedNodeElement[] children;
   
    public NamedNodeElement(String name, NamedNodeElement[] children) {
      this.name = assertNotNull(name);
      this.children = children;
    }
   
    @Override
    public String toString() {
      boolean hasChildren = children != null && children.length > 0;
      return name + (hasChildren ? "(" + StringUtil.collToString(children, " ") + ")" : "");
    }
  }
 
  public static void checkExpectedStructure(ASTNode node, NamedNodeElement[] expectedStructure) {
    if(expectedStructure == null) {
      return; // Don't check structure
    } else if(expectedStructure.length == 0) {
      assertTrue(node == null);
      return;
    }
    ASTNode[] children = node instanceof Module ? node.getChildren() : array(node);
    checkExpectedStructure_do(children, expectedStructure);
  }
 
  public static void checkExpectedStructure_do(ASTNode[] children, NamedNodeElement[] expectedStructure) {
   
    assertTrue(children.length <= expectedStructure.length);
   
    for(int i = 0; i < children.length; i++) {
      NamedNodeElement namedElement = expectedStructure[i];
      ASTNode astNode = children[i];
     
      if(namedElement.name == NamedNodeElement.IGNORE_ALL) {
        continue;
      }
      if(namedElement.name != NamedNodeElement.IGNORE_NAME) {
        String expectedName = getExpectedNameAliases(namedElement.name);
        assertEquals(astNode.getClass().getSimpleName(), expectedName);
      }
      checkExpectedStructure_do(astNode.getChildren(), namedElement.children);
    }
    assertTrue(children.length == expectedStructure.length);
  }
 
  public static String getExpectedNameAliases(String expectedNameRaw) {
    if(expectedNameRaw.equals("Bool")) {
      return ExpLiteralBool.class.getSimpleName();
    } else if(expectedNameRaw.equals("Integer")) {
      return ExpLiteralInteger.class.getSimpleName();
    } else if(expectedNameRaw.equals("Float")) {
      return ExpLiteralFloat.class.getSimpleName();
    } else if(expectedNameRaw.equals("String")) {
      return ExpLiteralString.class.getSimpleName();
    } else if(expectedNameRaw.equals("MapEntry")) {
      return MapArrayLiteralKeyValue.class.getSimpleName();
    } else if(expectedNameRaw.equals("ExpPostfix") || expectedNameRaw.equals("ExpPostfixOp")) {
      return ExpPostfixOperator.class.getSimpleName();
    } else if(expectedNameRaw.equals("AliasFragment")) {
      return DefinitionAliasFragment.class.getSimpleName();
    } else if(expectedNameRaw.equals("AutoRef")) {
      return AutoReference.class.getSimpleName();
    } else if(expectedNameRaw.equals("SimpleVarDef")) {
      return SimpleVariableDef.class.getSimpleName();
    } if(expectedNameRaw.equals("StatementExp")) {
      return StatementExpression.class.getSimpleName();
    }
   
    return replaceRegexFirstOccurrence(expectedNameRaw,
      "(Def)(Variable|AutoVar|Function|Constructor|EnumVarFragment)", 1, "Definition");
  }
 
  /* ============= Error and Source Range Checkers ============= */
 
  public static void checkParserErrors(List<ParserError> resultErrors, ArrayList<ParserError> expectedErrors) {
    resultErrors = new ArrayList<>(resultErrors);
    Collections.sort(resultErrors, new ParserErrorComparator());
    Collections.sort(expectedErrors, new ParserErrorComparator());
   
    for(int i = 0; i < resultErrors.size(); i++) {
      ParserError error = resultErrors.get(i);
     
      assertTrue(i < expectedErrors.size());
      ParserError expError = expectedErrors.get(i);
      assertEquals(error.errorType, expError.errorType);
      assertEquals(error.sourceRange, expError.sourceRange);
      assertEquals(error.msgErrorSource, expError.msgErrorSource);
      if(expError.msgData != DONT_CHECK) {
        assertAreEqual(safeToString(error.msgData), safeToString(expError.msgData));
      }
    }
    assertTrue(resultErrors.size() == expectedErrors.size());
  }
 
  public static final class ParserErrorComparator implements Comparator<ParserError> {
    @Override
    public int compare(ParserError o1, ParserError o2) {
      int compareResult = o1.sourceRange.compareTo(o2.sourceRange);
      if(compareResult == 0) {
        compareResult = o1.errorType.ordinal() - o2.errorType.ordinal();
      }
      if(compareResult == 0) {
        compareResult = NewUtils.compareStrings(o1.msgErrorSource, o2.msgErrorSource);
      }
      return compareResult;
    }
  }
 
  /* ---------------- Rule specific tests ---------------- */
 
  public DeeParserResult parseUsingRule(DeeTestsChecksParser deeParser) {
    boolean parseAsFnParamOnly = removeTestMetadataFlag("FN_ONLY");
    boolean parseAsTplParamOnly = removeTestMetadataFlag("TPL_ONLY");
   
    if(parseRule == null) {
    } else if(parseRule.equalsIgnoreCase("EXPRESSION_ToE")) {
      return deeParser.parseUsingRule(DeeParser.RULE_EXPRESSION);
    } else if(parseRule.equalsIgnoreCase("PARAMETER_TEST")) {
     
      Object ambigParsedResult = deeParser.new DeeParser_RuleParameters(TplOrFnMode.AMBIG).parseParameter();
      String parsedSource = checkParsedSource(expectedRemainingSource, deeParser);
      parameterTest(parseAsFnParamOnly, parseAsTplParamOnly, parsedSource, ambigParsedResult);
     
      return null;
    }
    ParseRuleDescription parseRuleDesc = getParseRule(parseRule);
    if(parseRuleDesc == null) {
      return deeParser.parseModuleSource(MiscUtil.createValidPath("_parser_tests.d"));
    }
    return deeParser.parseUsingRule(parseRuleDesc);
  }
 
  public static ParseRuleDescription getParseRule(String parseRuleName) {
    if(parseRuleName == null) {
      return null;
    } else if(parseRuleName.equalsIgnoreCase(DeeParser.RULE_EXPRESSION.id)) {
      return DeeParser.RULE_EXPRESSION;
    } else if(parseRuleName.equalsIgnoreCase(DeeParser.RULE_REFERENCE.id)) {
      return DeeParser.RULE_REFERENCE;
    } else if(parseRuleName.equalsIgnoreCase(DeeParser.RULE_DECLARATION.id)) {
      return DeeParser.RULE_DECLARATION;
    } else if(parseRuleName.equalsIgnoreCase(DeeParser.RULE_TYPE_OR_EXP.id)
      || parseRuleName.equalsIgnoreCase("TypeOrExp")) {
      return DeeParser.RULE_TYPE_OR_EXP;
    } else if(parseRuleName.equalsIgnoreCase(DeeParser.RULE_INITIALIZER.id)) {
      return DeeParser.RULE_INITIALIZER;
    } else if(parseRuleName.equalsIgnoreCase(DeeParser.RULE_STATEMENT.id)) {
      return DeeParser.RULE_STATEMENT;
    } else if(parseRuleName.equalsIgnoreCase("INIT_STRUCT")) {
      return DeeParser.RULE_STRUCT_INITIALIZER;
    }
    throw assertFail();
  }
 
  public void runAdditionalTests(final DeeParserResult result, final String parsedSource) {
   
    boolean ruleBreakExpected = removeTestMetadataEntries("RULE_BROKEN").isEmpty() == false;
    if(removeTestMetadata("IGNORE_BREAK_FLAG_CHECK") == null) {
      assertTrue(result.ruleBroken == ruleBreakExpected);
      if(result.ruleBroken) {
        // if rule syntax is broken then node position must include all pending whitespace source.
        assertTrue(result.node.getEndPos() >= parsedSource.length());
        // The reason the above is not a strict equality check is just because
        // parsedSource/expectedRemainingSource is not entirely accurate in most test cases:
        // there is usually a bit of whitespace in expectedRemainingSource that may be consumed as well.
      }
    }
   
    if(parseRule == null) {
    }
    else if(parseRule.equalsIgnoreCase("REFERENCE")) {
      if(DToolTests.TESTS_LITE_MODE == false) {
        DeeTestsChecksParser parser = new DeeTestsChecksParser(parsedSource);
        DeeParserResult resultToE = parser.parseUsingRule(DeeParser.RULE_TYPE_OR_EXP);
        DeeParsingChecks.checkNodeEquality(result.node, resultToE.node);
      }
    }
    else if(parseRule.equalsIgnoreCase("EXPRESSION_ToE")) {
      DeeTestsChecksParser parser = new DeeTestsChecksParser(parsedSource);
      DeeParserResult resultToE = parser.parseUsingRule(DeeParser.RULE_TYPE_OR_EXP);
      ASTNode expNode = result.node;
      List<ParserError> resultToE_Errors = resultToE.getErrors();
      if(result.errors.size() >= 1) {
        ParserError lastError = result.getErrors().get(result.errors.size()-1);
        if(lastError.errorType == ParserErrorTypes.TYPE_USED_AS_EXP_VALUE &&
          SourceEquivalenceChecker.check(result.node.toStringAsCode(), lastError.msgErrorSource)) {
          resultToE_Errors = new ArrayList<>(resultToE.getErrors());
          resultToE_Errors.add(lastError);
          expNode = ((ExpReference) expNode).ref;
        }
      }
      if(expNode instanceof ExpReference) {
        expNode = ((ExpReference) expNode).ref;
      }
      DeeParsingChecks.checkNodeEquality(expNode, resultToE.node);
      assertEquals(result.getErrors(), resultToE_Errors);
    }
   
    runDDocTest(result);
   
    assertTrue(additionalMD.isEmpty());
  }
 
  public static void parameterTest(boolean parseAsFnParamOnly, boolean parseAsTplParamOnly,
    String nodeSource, Object ambigParsedResult) {
    assertTrue(!(parseAsFnParamOnly == true && parseAsTplParamOnly == true));
   
    if(parseAsFnParamOnly) {
      assertTrue(ambigParsedResult instanceof IFunctionParameter);
    } else if(parseAsTplParamOnly) {
      assertTrue(ambigParsedResult instanceof TemplateParameter);
    } else {
      assertTrue(ambigParsedResult instanceof AmbiguousParameter);
    }
   
    if(!DToolTests.TESTS_LITE_MODE) {
      ParametersReparseCheck.ambigParameterReparseTest(nodeSource);       
    }
  }
 
  public void runDDocTest(final DeeParserResult result) {
    MetadataEntry targetMDE = removeTestMetadata("DDOC_TEST_TARGET");
    if(targetMDE == null) {
      return;
    }
   
    DefUnit defUnit = findDDocTargetDefUnit(result, targetMDE);
    assertNotNull(defUnit);
    List<MetadataEntry> ddocTestEntries = removeTestMetadataEntries("DDOC_TEST");
   
    ArrayList<Token> commentsToCheck = defUnit.getDocComments() == null ? new ArrayList<Token>() :
      new ArrayList<>(Arrays.asList(defUnit.getDocComments()));
   
   
    Token[] comments = defUnit.getDocComments();
    CommonDefinition commonDefinition = defUnit instanceof CommonDefinition ? (CommonDefinition) defUnit : null;
   
    if(comments != null && comments.length > 0 && !(defUnit instanceof Module) && commonDefinition != null) {
      int extendedStartPos = commonDefinition.getExtendedStartPos();
      int extendedEndPos = commonDefinition.getExtendedEndPos();
     
      if(comments.length == 1) {
        assertTrue(
          comments[0].getEndPos() == extendedEndPos ||
          comments[0].getStartPos() == extendedStartPos);
      } else {
        assertTrue(comments[0].getStartPos() == extendedStartPos);
        int ddocEnd = comments[comments.length-1].getEndPos();
        assertTrue(ddocEnd < defUnit.defname.getStartPos() || ddocEnd == extendedEndPos);
      }
    }
   
    for (MetadataEntry ddocTest : ddocTestEntries) {
      checkDDocComments(commentsToCheck, ddocTest);
    }
   
    assertTrue(commentsToCheck.isEmpty()); // All comment tokens must be tagged.
  }
 
  protected DefUnit findDDocTargetDefUnit(DeeParserResult result, MetadataEntry targetMDE) {
    MetadataEntry targetMDEOverride = removeTestMetadata("DDOC_TEST_TARGET__OVERRIDE");
    if(targetMDE.sourceValue == null) {
      return getDefunitFromExtendedDefinition(result.node);
    }
    if(targetMDEOverride != null) {
      targetMDE = targetMDEOverride;
    }
   
    String defUnitName = targetMDE.sourceValue.trim();
    DefUnit targetDefUnit = null;
   
    for (ASTNode child : result.node.getChildren()) {
      DefUnit defUnit = getDefunitFromExtendedDefinition(child);
      if(defUnit != null) {
        if(defUnit.defname.name.equals(defUnitName)) {
          assertTrue(targetDefUnit == null); // check that there is only one defunit with that name
          targetDefUnit = defUnit;
        }
      }
    }
    return assertNotNull(targetDefUnit);
  }
 
  public DefUnit getDefunitFromExtendedDefinition(ASTNode node) {
    while(true) {
      if(node instanceof DefUnit) {
        return (DefUnit) node;
      }
      if(node instanceof DeclarationAttrib) {
        DeclarationAttrib declAttrib = (DeclarationAttrib) node;
        if(declAttrib.bodySyntax == AttribBodySyntax.SINGLE_DECL) {
          node = declAttrib.body;
          continue;
        }
      }
      if(node instanceof DefinitionAlias) {
        DefinitionAlias definitionAlias = (DefinitionAlias) node;
        return definitionAlias.aliasFragments.get(0);
      }
      return null;
    }
  }
 
  public void checkDDocComments(ArrayList<Token> comments, MetadataEntry ddocTest) {
    for (Token token : comments) {
      if(token.getSourceValue().equals(ddocTest.sourceValue)) {
        comments.remove(token);
        return;
      }
    }
    assertFail(); // Must find it in comments
  }
 
}
TOP

Related Classes of dtool.parser.DeeParserTester

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.