package edu.ucsd.arcum.interpreter.parser; import static edu.ucsd.arcum.interpreter.parser.ArcumKeyword.*; import static edu.ucsd.arcum.interpreter.parser.BacktrackingScanner.TokenNameARCUMBEGINQUOTE; import static edu.ucsd.arcum.interpreter.parser.TokenData.tokenToString; import static org.eclipse.jdt.internal.compiler.parser.TerminalTokens.*; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import com.google.common.collect.Lists; import edu.ucsd.arcum.exceptions.ArcumError; import edu.ucsd.arcum.exceptions.SourceLocation; import edu.ucsd.arcum.interpreter.ast.*; import edu.ucsd.arcum.interpreter.ast.expressions.*; import edu.ucsd.arcum.interpreter.parser.BacktrackingScanner.TokenCountListener; import edu.ucsd.arcum.interpreter.query.ArcumDeclarationTable; import edu.ucsd.arcum.interpreter.query.EntityType; // Used in the parsing of an .arcum source file @SuppressWarnings("restriction") public class ArcumStructureParser { private enum ParseMode { // Accept only valid function arguments: Variable references, implied exists, // and immediate-literal patterns FUNCTION_ARGUMENTS, // Any constraint expression is allowed CONSTRAINT_EXPR, // Anything on the right-hand side of a unification: Constants, // variable references, and all patterns (immediate-literal and matching), // a select expression, or a disjunct of the same VALUES_ONLY } private final IResource resource; private final IProject project; private final String importsString; private int numErrors; public ArcumStructureParser(IResource resource, IProject project, String importsString) { this.resource = resource; this.project = project; this.importsString = importsString; this.numErrors = 0; } public void parse(BacktrackingScanner scanner, ArcumDeclarationTable table) { // e.g., // option InternalField { ... // option interface AttributeConcept { ... if (arcumKeyword(OPTION_KEYWORD, scanner)) { match(scanner); if (scanner.lookaheadEquals(TokenNameinterface)) { match(TokenNameinterface, scanner); parseOptionInterface(table, importsString, scanner); } else { parseOption(table, importsString, scanner); } } // interface AttributeConcept { ... else if (scanner.lookaheadEquals(TokenNameinterface)) { match(scanner); parseOptionInterface(table, importsString, scanner); } // require { Option1(); Option2(); ... } else if (arcumKeyword(REQUIRE_KEYWORD, scanner)) { match(scanner); parseTopLevelRequire(table, importsString, project, scanner); } else { SourceLocation location = scanner.getCurrentLocation(); ++numErrors; ArcumError .fatalUserError(location, "Expected start of option," + " option interface, or requires: %s%n", scanner .getCurrentTokenString()); } } private void parseOptionInterface(ArcumDeclarationTable table, String importsString, BacktrackingScanner scanner) { String name = scanner.getCurrentTokenString(); SourceLocation location = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); OptionInterface optionInterface = OptionInterface.newOptionInterface(location, name, importsString, table); match(TokenNameLBRACE, scanner); parseOptionInterfaceMembers(optionInterface, scanner); match(TokenNameRBRACE, scanner); } // e.g.: // abstract attrGet(Expr root, Expr targetExpr) { require: ... } // abstract AccessSpecifier spec; // define classGraph(Type toType, Type fromType, Field edge) { ... } private void parseOptionInterfaceMembers(OptionInterface optionInterface, BacktrackingScanner scanner) { int ctorsFound = 0; while (scanner.lookaheadIsNoneOf(TokenNameRBRACE, TokenNameEOF)) { if (arcumKeyword(DEFINE_KEYWORD, scanner)) { RealizationStatement statement = parseRealizationStatement( optionInterface, scanner); List<TraitSignature> tuplesRealized = statement.getTuplesRealized(); if (tuplesRealized.size() != 1) { ArcumError.fatalError("Internal error found in" + " parseOptionInterfaceMembers"); } TraitSignature signature = tuplesRealized.get(0); } else if (scanner.lookaheadEquals(TokenNameabstract)) { match(TokenNameabstract, scanner); String typeOrTraitName = scanner.getCurrentTokenString(); SourceLocation startLocation = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); TraitSignature signature; if (scanner.lookaheadEquals(TokenNameLPAREN)) { String traitName = typeOrTraitName; List<FormalParameter> formals = parseParameterList(scanner, false); signature = TraitSignature.makeAbstractTraitSignature(traitName, formals); } else if (scanner.lookaheadEquals(TokenNameIdentifier)) { String typeName = typeOrTraitName; String varName = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); EntityType type = getEntityType(typeName, startLocation); FormalParameter formal = new FormalParameter(type, varName); signature = TraitSignature.makeSingleton(varName, formal); } else { ArcumError.fatalUserError(scanner.getCurrentLocation(), "Expected either a \'(\' or an identifier"); return; } if (scanner.lookaheadEquals(TokenNameSEMICOLON)) { match(TokenNameSEMICOLON, scanner); } else { match(TokenNameLBRACE, scanner); if (!scanner.lookaheadEquals(TokenNameRBRACE) && !arcumKeyword(REQUIRE_KEYWORD, scanner)) { ConstraintExpression expr = parseConstraintExpr(scanner); signature.setInterfaceConjunct(expr); } parseOptionalRequireClauses(scanner, signature); match(TokenNameRBRACE, scanner); } optionInterface.addTupleSetType(signature); } else if (isFreeStandingRequirementStart(scanner)) { FreeStandingRequirements requirements; requirements = optionInterface.getFreeStandingRequirements(); parseFreeStandingRequirements(scanner, requirements); } else if (scanner.lookaheadEquals(TokenNameIdentifier)) { String ctorName = scanner.getCurrentTokenString(); SourceLocation location = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); if (!ctorName.equals(optionInterface.getName())) { ++numErrors; ArcumError.fatalUserError(location, "The constructor must be named %s (expected constructor," + " or a clause beginning with \'abstract\' or \'define\')", optionInterface.getName()); } if (ctorsFound > 1) { ++numErrors; ArcumError.fatalUserError(scanner.getCurrentLocation(), "A constructor for this interface already exists"); } ++ctorsFound; TraitSignature tupleSet = parseConstructor(ctorName, scanner); parseOptionalRequireClauses(scanner, tupleSet); optionInterface.addTupleSetType(tupleSet); } else { ++numErrors; ArcumError.fatalUserError(scanner.getCurrentLocation(), "Unexpected \'%s\', expected \'abstract\' or \'define\' clause", scanner.getCurrentTokenString()); } } if (ctorsFound < 1) { ++numErrors; ArcumError.fatalUserError(optionInterface.getLocation(), "A constructor named %s must be defined", optionInterface.getName()); } } private ErrorMessage parseOptionalOnFailMessage(BacktrackingScanner scanner) { ErrorMessage result = null; if (arcumKeyword(ArcumKeyword.ONFAIL_KEYWORD, scanner)) { matchKeyword(ArcumKeyword.ONFAIL_KEYWORD, scanner); match(TokenNameLBRACE, scanner); result = parseErrorMessageText(scanner); match(TokenNameRBRACE, scanner); } return result; } private boolean isErrorMessageTextStart(BacktrackingScanner scanner) { return scanner.lookaheadEquals(TokenNameStringLiteral); } // An error message in the form of a series of concatenated string literals. An // optional argument is the program element to which the error message applies. private ErrorMessage parseErrorMessageText(BacktrackingScanner scanner) { StringBuilder buff = new StringBuilder(); SourceLocation messageLocation = scanner.getCurrentLocation(); if (scanner.lookaheadIsNoneOf(TokenNameStringLiteral)) { ++numErrors; ArcumError.fatalUserError(scanner.getCurrentLocation(), "Expected a string literal here"); } String messagePart = scanner.getCurrentStringLiteral(); match(TokenNameStringLiteral, scanner); buff.append(messagePart); while (scanner.lookaheadEquals(TokenNamePLUS)) { match(scanner); messagePart = scanner.getCurrentStringLiteral(); match(TokenNameStringLiteral, scanner); buff.append(messagePart); } String message = buff.toString(); ConstraintExpression optLocationExpr = null; if (scanner.lookaheadEquals(TokenNameCOMMA)) { match(scanner); optLocationExpr = parseBooleanFactor(scanner, ParseMode.FUNCTION_ARGUMENTS); } return new ErrorMessage(messageLocation, message, optLocationExpr); } // parses a constructor private TraitSignature parseConstructor(String name, BacktrackingScanner scanner) { List<FormalParameter> params = parseParameterList(scanner, true); TraitSignature traitSignature = TraitSignature.makeSingleton(name, params); if (scanner.lookaheadEquals(TokenNameLBRACE)) { match(TokenNameLBRACE, scanner); parseOptionalRequireClauses(scanner, traitSignature); match(TokenNameRBRACE, scanner); } else { match(TokenNameSEMICOLON, scanner); } return traitSignature; } // expr ::= term // expr ::= expr || term private ConstraintExpression parseConstraintExpr(BacktrackingScanner scanner) { ConstraintExpression term = parseBooleanTerm(scanner); Parsing: for (;;) { switch (scanner.lookahead()) { case TokenNameOR_OR: { match(scanner); ConstraintExpression operand = parseBooleanTerm(scanner); term = addNewOperand(term, BooleanDisjunction.class, operand); break; } default: break Parsing; } } return term; } // term ::= factor // term ::= term && factor private ConstraintExpression parseBooleanTerm(BacktrackingScanner scanner) { ConstraintExpression factor = parseBooleanFactor(scanner, ParseMode.CONSTRAINT_EXPR); Parsing: for (;;) { switch (scanner.lookahead()) { case TokenNameAND_AND: { match(scanner); ConstraintExpression operand; operand = parseBooleanFactor(scanner, ParseMode.CONSTRAINT_EXPR); factor = addNewOperand(factor, BooleanConjunction.class, operand); break; } default: break Parsing; } } return factor; } private <T extends VariadicOperator> ConstraintExpression addNewOperand( ConstraintExpression term, Class<T> clazz, ConstraintExpression condition) { T result = null; if (clazz.isAssignableFrom(term.getClass())) { result = clazz.cast(term); } else { try { Constructor<T> ctor = clazz.getConstructor(SourceLocation.class); result = ctor.newInstance(term.getPosition()); result.addClause(term); } catch (RuntimeException e) { throw e; } catch (Exception e) { e.printStackTrace(); } } result.addClause(condition); return result; } // factor ::= ( expr [<=> expr]* ) [zero or more]* // factor ::= functionName(exprs,..) // factor ::= exists (..) {..} // factor ::= forall (..) {..} // factor ::= select { .. } // factor ::= true // factor ::= false // factor ::= <string-literal> // factor ::= [ pattern ] // factor ::= < immediate-pattern > private ConstraintExpression parseBooleanFactor(BacktrackingScanner scanner, ParseMode mode) { ConstraintExpression result; boolean argOK = false; boolean valueOK = false; int theToken = scanner.lookahead(); switch (theToken) { case TokenNameLPAREN: match(TokenNameLPAREN, scanner); result = parseConstraintExpr(scanner); // the <=> operator while (scanner.lookaheadEquals(TokenNameLESS_EQUAL)) { BooleanEquivalence boolEquiv; if (result instanceof BooleanEquivalence) { boolEquiv = (BooleanEquivalence)result; } else { boolEquiv = new BooleanEquivalence(result.getPosition()); boolEquiv.addClause(result); result = boolEquiv; } int pos = scanner.getCurrentTokenStartPosition(); match(TokenNameLESS_EQUAL, scanner); if (scanner.lookaheadIsNoneOf(TokenNameGREATER) || scanner.getCurrentTokenStartPosition() != (pos + 2)) { ++numErrors; ArcumError.fatalUserError(scanner.getCurrentLocation(), "Insert '>' symbol to complete '<=>' operator"); } match(TokenNameGREATER, scanner); ConstraintExpression nextTerm = parseConstraintExpr(scanner); boolEquiv.addClause(nextTerm); } match(TokenNameRPAREN, scanner); break; case TokenNameIdentifier: if (arcumKeyword(EXISTS_KEYWORD, scanner)) { result = parseExistsExpression(scanner); } else if (arcumKeyword(FORALL_KEYWORD, scanner)) { result = parseForallExpression(scanner); } else if (arcumKeyword(SELECT_KEYWORD, scanner)) { result = parseSelectExpression(scanner); valueOK = true; } else { String id = scanner.getCurrentTokenString(); SourceLocation start = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); // id(args) /*functional expr*/ if (scanner.lookaheadEquals(TokenNameLPAREN)) { FunctionalExpression function; function = parseBooleanFunction(start, id, scanner); if (mode == ParseMode.FUNCTION_ARGUMENTS || mode == ParseMode.VALUES_ONLY) { // TODO: FRIDAY: Change the parser here! // if (!function.isAccessor()) { // ++numErrors; // fatalUserError(function.getPosition(), // "Only accessor functions are allowed in this context, not predicates"); // } } result = function; argOK = true; valueOK = true; } // id == factor /*unify*/ else if (scanner.lookaheadEquals(TokenNameEQUAL_EQUAL)) { match(TokenNameEQUAL_EQUAL, scanner); ConstraintExpression rhs; if (scanner.lookaheadEquals(TokenNameLPAREN)) { BooleanDisjunction union; union = new BooleanDisjunction(scanner.getCurrentLocation()); match(TokenNameLPAREN, scanner); for (;;) { ConstraintExpression disjunct; disjunct = parseBooleanFactor(scanner, ParseMode.VALUES_ONLY); union.addClause(disjunct); if (scanner.lookaheadEquals(TokenNameOR_OR)) { match(TokenNameOR_OR, scanner); } else { break; } } match(TokenNameRPAREN, scanner); rhs = union; } else { rhs = parseBooleanFactor(scanner, ParseMode.VALUES_ONLY); } SourceLocation extent = start.extendedTo(rhs.getPosition()); result = new UnificationExpression(extent, id, rhs); } // id /*just an id*/ else { result = new VariableReferenceExpression(start, id); argOK = true; valueOK = true; } } break; case TokenNametrue: result = new TrueLiteral(scanner.getCurrentLocation()); match(scanner); valueOK = true; break; case TokenNamefalse: result = new FalseLiteral(scanner.getCurrentLocation()); match(scanner); valueOK = true; break; case TokenNameStringLiteral: { String text = scanner.getCurrentTokenString(); result = new StringLiteral(scanner.getCurrentLocation(), text); match(scanner); valueOK = true; break; } case TokenNameLESS: { PatternExpression patExpr; patExpr = parsePatternExpression(scanner, TokenNameLESS, TokenNameGREATER); patExpr.setImmediate(true); result = patExpr; argOK = true; valueOK = true; break; } case TokenNameLBRACKET: result = parsePatternExpression(scanner, TokenNameLBRACKET, TokenNameRBRACKET); valueOK = true; break; case TokenNameNOT: { SourceLocation start = scanner.getCurrentLocation(); match(scanner); ConstraintExpression factor = parseBooleanFactor(scanner, ParseMode.CONSTRAINT_EXPR); SourceLocation location = start.extendedTo(factor.getPosition()); result = new BooleanNegation(location, factor); break; } default: SourceLocation location = scanner.getCurrentLocation(); ++numErrors; ArcumError.fatalUserError(location, "Unexpected \"%s\"", tokenToString(theToken)); return null /*unreachable*/; } if (mode == ParseMode.FUNCTION_ARGUMENTS && !argOK) { ++numErrors; ArcumError.fatalUserError(result.getPosition(), "Construct not allowed for function arguments"); } if (mode == ParseMode.VALUES_ONLY && !valueOK) { ++numErrors; ArcumError.fatalUserError(result.getPosition(), "Unification can only be on values or union of values"); } return result; } // exists (Method m) { acceptMethods(m) && within(root, m) } private ConstraintExpression parseExistsExpression(BacktrackingScanner scanner) { SourceLocation start = scanner.getCurrentLocation(); matchKeyword(EXISTS_KEYWORD, scanner); List<FormalParameter> boundVars = parseParameterList(scanner, false); match(TokenNameLBRACE, scanner); ConstraintExpression body = parseConstraintExpr(scanner); SourceLocation location = start.extendedTo(scanner.getCurrentLocation()); match(TokenNameRBRACE, scanner); return new ExistentialQuantifier(location, boundVars, body); } // forall (Type t : targetType(t)) { hasSignature(visitorInterface, [...]) } // forall (Type t : ...) { ... } onfail { "text" } private UniversalQuantifier parseForallExpression(BacktrackingScanner scanner) { SourceLocation start = scanner.getCurrentLocation(); matchKeyword(FORALL_KEYWORD, scanner); List<FormalParameter> boundVars = parseParameterList(scanner, false); match(TokenNameCOLON, scanner); ConstraintExpression initialSet = parseConstraintExpr(scanner); match(TokenNameLBRACE, scanner); ConstraintExpression body = parseConstraintExpr(scanner); SourceLocation location = start.extendedTo(scanner.getCurrentLocation()); match(TokenNameRBRACE, scanner); ErrorMessage errorMsg = parseOptionalOnFailMessage(scanner); if (errorMsg == null) { errorMsg = ErrorMessage.EMPTY_MESSAGE; } return new UniversalQuantifier(location, boundVars, initialSet, body, errorMsg); } // stmt == select { // targetType(toType): <visitor.visit(this.`edge);>, // else: <this.`edge.`traversalName(visitor);> // } private ConstraintExpression parseSelectExpression(BacktrackingScanner scanner) { SourceLocation start = scanner.getCurrentLocation(); matchKeyword(SELECT_KEYWORD, scanner); match(TokenNameLBRACE, scanner); List<ConstraintExpression> conditions = Lists.newArrayList(); List<ConstraintExpression> values = Lists.newArrayList(); while (scanner.lookaheadIsNoneOf(TokenNameelse)) { ConstraintExpression condition = parseConstraintExpr(scanner); match(TokenNameCOLON, scanner); ConstraintExpression value = parseBooleanFactor(scanner, ParseMode.VALUES_ONLY); match(TokenNameCOMMA, scanner); conditions.add(condition); values.add(value); } match(TokenNameelse, scanner); match(TokenNameCOLON, scanner); ConstraintExpression defaultValue = parseConstraintExpr(scanner); if (scanner.lookaheadEquals(TokenNameCOMMA)) { // final comma is optional match(scanner); } values.add(defaultValue); SourceLocation location = start.extendedTo(scanner.getCurrentLocation()); match(TokenNameRBRACE, scanner); return new SelectExpression(location, conditions, values); } private PatternExpression parsePatternExpression(BacktrackingScanner scanner, int leftDelim, int rightDelim) { int startPosition = scanner.getStartPosition() + 1; SourceLocation patStartLoc = scanner.getCurrentLocation(); List<EmbeddedExpression> embeddedExpressions = Lists.newArrayList(); int nesting = 0; eatTokens: for (;;) { if (scanner.lookaheadEquals(leftDelim)) { ++nesting; } else if (scanner.lookaheadEquals(rightDelim)) { --nesting; if (nesting == 0) { break eatTokens; } } else if (scanner.lookaheadEquals(TokenNameARCUMBEGINQUOTE)) { embeddedExpressions.add(parseEmbeddedExpressionBody(this, scanner)); } else if (scanner.lookaheadEquals(TokenNameEOF)) { ++numErrors; ArcumError.fatalUserError(patStartLoc, "Reached EOF before corresponding \']\' was found"); break eatTokens; } match(scanner); } int length = scanner.getStartPosition() - startPosition; SourceLocation patEndLoc = scanner.getCurrentLocation(); match(rightDelim, scanner); SourceLocation location = patStartLoc.extendedTo(patEndLoc); String text = scanner.getText(startPosition, length); return new PatternExpression(location, text, this, embeddedExpressions); } public class EmbeddedExpression { private final FormalParameter boundVar; private final ConstraintExpression constraintExpression; private int count; private EmbeddedExpression(BacktrackingScanner scanner) { scanner.match(TokenNameARCUMBEGINQUOTE); this.count = 0; TokenCountListener counter = new TokenCountListener() { public void count() { ++EmbeddedExpression.this.count; } }; scanner.addTokenCountListener(counter); this.boundVar = parseFormal(scanner, false); match(TokenNameCOLON, scanner); this.constraintExpression = parseConstraintExpr(scanner); match(TokenNameRBRACKET, scanner); scanner.removeTokenCountListener(counter); } public FormalParameter getBoundVar() { return boundVar; } public ConstraintExpression getConstraintExpression() { return constraintExpression; } public int getNumTokens() { return count; } } public static EmbeddedExpression parseEmbeddedExpressionBody( ArcumStructureParser thiz, BacktrackingScanner scanner) { return thiz.new EmbeddedExpression(scanner); } private FunctionalExpression parseBooleanFunction(SourceLocation startLocation, String name, BacktrackingScanner scanner) { match(TokenNameLPAREN, scanner); List<ConstraintExpression> args = parseFunctionArgumentList(scanner); SourceLocation end = scanner.getCurrentLocation(); SourceLocation wholeExpression = startLocation.extendedTo(end); match(TokenNameRPAREN, scanner); IFunction function = BuiltInFunction.lookup(name); if (function == BuiltInFunction.STATIC_TRAIT) { function = new TraitFunction(name); } return new FunctionalExpression(wholeExpression, function, args); } private List<ConstraintExpression> parseFunctionArgumentList( BacktrackingScanner scanner) { List<ConstraintExpression> result = Lists.newArrayList(); for (;;) { ConstraintExpression arg = parseBooleanFactor(scanner, ParseMode.FUNCTION_ARGUMENTS); result.add(arg); if (scanner.lookahead() != TokenNameCOMMA) break; match(TokenNameCOMMA, scanner); } return result; } private List<FormalParameter> parseParameterList(BacktrackingScanner scanner, boolean allowSubtraits) { List<FormalParameter> result = new ArrayList<FormalParameter>(); match(TokenNameLPAREN, scanner); if (scanner.lookaheadIsNoneOf(TokenNameRPAREN, TokenNameLBRACE)) { for (;;) { FormalParameter formal = parseFormal(scanner, allowSubtraits); result.add(formal); if (scanner.lookahead() != TokenNameCOMMA) break; match(TokenNameCOMMA, scanner); } } match(TokenNameRPAREN, scanner); return result; } // E.g, the formals in: // singleton VisitorConcept( // Type rootType, // targetType(Type type), // viaEdge(Field field) default isField(field), // bypassEdge(Field field) default false, // sourceScope(Package pack) default onBuildPath(pack)); private FormalParameter parseFormal(BacktrackingScanner scanner, boolean allowSubtraits) { FormalParameter formal; String lexeme = scanner.getCurrentTokenString(); SourceLocation lexemePosition = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); if (scanner.lookahead() == TokenNameLPAREN) { String subtraitName = lexeme; List<FormalParameter> traitArguments = parseParameterList(scanner, false); formal = new FormalParameter(EntityType.TRAIT, subtraitName); formal.addTraitArguments(traitArguments); if (!allowSubtraits) { SourceLocation location = scanner.getCurrentLocation(); ++numErrors; ArcumError.fatalUserError(location, "Sub-traits are (currently) only permitted for option constructors"); } } else { EntityType type = getEntityType(lexeme, lexemePosition); String name = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); formal = new FormalParameter(type, name); } if (scanner.lookaheadEquals(TokenNamedefault)) { match(scanner); if (scanner.lookaheadEquals(TokenNameStringLiteral)) { formal.addDefaultValue(scanner.getCurrentStringLiteral()); } else { ConstraintExpression defaultBody = parseConstraintExpr(scanner); formal.addDefaultValue(defaultBody); } } return formal; } private EntityType getEntityType(String lexeme, SourceLocation location) { EntityType type = EntityType.lookup(lexeme); if (type == EntityType.ERROR) { ++numErrors; ArcumError.fatalUserError(location, "Expected \"%s\" to be a type name", lexeme); } return type; } private void parseOption(ArcumDeclarationTable table, String importsString, BacktrackingScanner scanner) { String name = scanner.getCurrentTokenString(); SourceLocation location = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); match(TokenNameimplements, scanner); String trait = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); Option option = Option.newOption(name, trait, importsString, table, location); match(TokenNameLBRACE, scanner); parseOptionMembers(option, scanner); match(TokenNameRBRACE, scanner); } // Clauses that start with "realize", "define" or free-standing "require" private void parseOptionMembers(Option option, BacktrackingScanner scanner) { Parsing: for (;;) { if (arcumKeyword(REALIZE_KEYWORD, scanner) || arcumKeyword(DEFINE_KEYWORD, scanner)) { parseRealizationStatement(option, scanner); } else if (isFreeStandingRequirementStart(scanner)) { FreeStandingRequirements requirements; requirements = option.getFreeStandingRequirements(); parseFreeStandingRequirements(scanner, requirements); } else if (scanner.lookahead() == TokenNameRBRACE) { break Parsing; } else { // EXAMPLE: Before each call to fatalUserError, increment error count ++numErrors; ArcumError.fatalUserError(scanner.getCurrentLocation(), "Expected \"realize\", \"define\" or \"require\" keyword"); } } } private boolean isFreeStandingRequirementStart(BacktrackingScanner scanner) { return arcumKeyword(REQUIRE_KEYWORD, scanner); } private void parseFreeStandingRequirements(BacktrackingScanner scanner, FreeStandingRequirements requirements) { if (!arcumKeyword(REQUIRE_KEYWORD, scanner)) { ArcumError.fatalError("Internal error in parseFreeStandingRequirements"); } matchKeyword(REQUIRE_KEYWORD, scanner); ErrorMessage message; message = parseErrorMessageText(scanner); match(TokenNameCOLON, scanner); ConstraintExpression expr = parseConstraintExpr(scanner); match(TokenNameSEMICOLON, scanner); requirements.addRequiresClause(expr, message); } // E.g., // realize id(args) { expr } // define id(args) { expr } private RealizationStatement parseRealizationStatement(TopLevelConstruct declaration, BacktrackingScanner scanner) { SourceLocation location = scanner.getCurrentLocation(); RealizationStatement stmt; if (arcumKeyword(DEFINE_KEYWORD, scanner)) { // e.g.: // define properAccess(Expr call, Expr e) { // call == ([`e.put(`_, `_)] || [`e.get(`_)]) // } match(scanner); String name = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); List<FormalParameter> params = parseParameterList(scanner, false); match(TokenNameLBRACE, scanner); ConstraintExpression patternExpr = parseConstraintExpr(scanner); stmt = StaticRealizationStatement.makeStatic(declaration, name, patternExpr, params, location); } else { // e.g.: // realize attrGet(getExpr, targetExpr) { // getExpr == [`targetType.`mapField.get(`targetExpr)] // } matchKeyword(ArcumKeyword.REALIZE_KEYWORD, scanner); // eat "realize" keyword stmt = new RealizationStatement(declaration, location); parseRealizedTupleSetList(stmt, declaration, scanner); match(TokenNameLBRACE, scanner); ConstraintExpression bodyExpr = parseConstraintExpr(scanner); stmt.setBodyExpression(bodyExpr); } parseOptionalRequireClauses(scanner, stmt); stmt.setPosition(location.extendedTo(scanner.getCurrentLocation())); stmt.verifyValidVariables(); match(TokenNameRBRACE, scanner); ErrorMessage optionalOnFailMessage = parseOptionalOnFailMessage(scanner); if (optionalOnFailMessage != null) { stmt.addSingletonErrorMessage(optionalOnFailMessage); } return stmt; } // parse a sequence of require clauses, e.g.: // require "The name `attrName must be a valid Java identifier: // isJavaIdentifier(attrName); // // require "The attribute type cannot be a primitive type": // isReferenceType(attrType); // // Errors occur when the condition after the requirement keyword fails. private void parseOptionalRequireClauses(BacktrackingScanner scanner, Constrainable constrainable) { scanner.matchIfPresent(TokenNameSEMICOLON); // Eat any leading semi-colons if (arcumKeyword(REQUIRE_KEYWORD, scanner)) { while (arcumKeyword(REQUIRE_KEYWORD, scanner)) { match(TokenNameIdentifier, scanner); ErrorMessage message; if (this.isErrorMessageTextStart(scanner)) { message = parseErrorMessageText(scanner); match(TokenNameCOLON, scanner); } else { message = ErrorMessage.EMPTY_MESSAGE; } ConstraintExpression expr = parseConstraintExpr(scanner); constrainable.addRequiresClause(expr, message); scanner.matchIfPresent(TokenNameSEMICOLON); } } } private void parseRealizedTupleSetList(RealizationStatement stmt, TopLevelConstruct declaration, BacktrackingScanner scanner) { parseRealizedTupleSet(stmt, declaration, scanner); while (scanner.lookahead() == TokenNameCOMMA) { match(TokenNameCOMMA, scanner); parseRealizedTupleSet(stmt, declaration, scanner); } } private void parseRealizedTupleSet(RealizationStatement stmt, TopLevelConstruct declaration, BacktrackingScanner scanner) { SourceLocation startLocation = scanner.getCurrentLocation(); String typeOrTraitName = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); // trait -- e.g.: realize visit(Expr root, Expr target, Expr visitor) with if (scanner.lookaheadEquals(TokenNameLPAREN)) { String traitName = typeOrTraitName; List<FormalParameter> formals = parseParameterList(scanner, false); TraitSignature signature = TraitSignature.makeTraitSignature(traitName, formals); stmt.addTraitSignature(signature); } // singleton -- e.g.: realize Field field, AccessSpecifier spec with else if (scanner.lookaheadEquals(TokenNameIdentifier)) { { String typeName = typeOrTraitName; String varName = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); EntityType type = getEntityType(typeName, startLocation); TraitSignature singleton = TraitSignature.makeSingleton(varName, new FormalParameter(type, varName)); stmt.addTraitSignature(singleton); } while (scanner.lookaheadEquals(TokenNameCOMMA)) { match(TokenNameCOMMA, scanner); String typeName = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); String varName = scanner.getCurrentTokenString(); match(TokenNameIdentifier, scanner); EntityType type = getEntityType(typeName, startLocation); TraitSignature singleton = TraitSignature.makeSingleton(varName, new FormalParameter(type, varName)); stmt.addTraitSignature(singleton); } } else { ArcumError.fatalUserError(scanner.getCurrentLocation(), "Expected either a \'(\' or an identifier"); return; } } // require { // InternalField(targetType=User, attrType=String, attrName="nickName"); // } private void parseTopLevelRequire(ArcumDeclarationTable table, String importsString, IProject project, BacktrackingScanner scanner) { RequireMap map = RequireMap.newArcumMap(importsString, project, table, resource); match(TokenNameLBRACE, scanner); parseMapMembers(map, scanner); match(TokenNameRBRACE, scanner); } private void parseMapMembers(RequireMap map, BacktrackingScanner scanner) { Parsing: for (;;) { if (scanner.lookahead() == TokenNameIdentifier) { String optionUsed; int start; int end; int length; List<MapNameValueBinding> bindings; String optionClauseText; SourceLocation location; optionUsed = scanner.getCurrentTokenString(); start = scanner.getCurrentTokenStartPosition(); match(TokenNameIdentifier, scanner); match(TokenNameLPAREN, scanner); bindings = parseMapNameValuePairs(map, scanner); match(TokenNameRPAREN, scanner); end = scanner.getCurrentTokenStartPosition(); length = end - start; optionClauseText = scanner.getText(start, length); location = new SourceLocation(resource, start, end, scanner .getLineNumber(start)); map.addBindings(optionUsed, bindings, optionClauseText, location); match(TokenNameSEMICOLON, scanner); } else { break Parsing; } } } // e.g., // traversalName: "visitBooks", // rootType: Library, // targetType(type): type == ([Book] || [Paper]) private List<MapNameValueBinding> parseMapNameValuePairs(RequireMap map, BacktrackingScanner scanner) { List<MapNameValueBinding> pairs = new ArrayList<MapNameValueBinding>(); while (scanner.lookahead() == TokenNameIdentifier) { String name = scanner.getCurrentTokenString(); SourceLocation nameStart = scanner.getCurrentLocation(); match(TokenNameIdentifier, scanner); if (scanner.lookaheadEquals(TokenNameCOLON)) { // e.g., traversalName: "visitBooks" match(TokenNameCOLON, scanner); pairs.add(parseMapNameValueBinding(nameStart, name, scanner)); } else if (scanner.lookaheadEquals(TokenNameLPAREN)) { // e.g., targetType(Type type): type == ([Book] || [Paper]) List<FormalParameter> formals = parseParameterList(scanner, false); match(TokenNameCOLON, scanner); ConstraintExpression expr = parseConstraintExpr(scanner); SourceLocation curLoc = scanner.getCurrentLocation(); SourceLocation location = nameStart.extendedTo(curLoc); MapNameValueBinding mta = new MapTraitArgument(location, map, name, formals, expr); pairs.add(mta); } if (scanner.lookahead() != TokenNameCOMMA) break; match(TokenNameCOMMA, scanner); } return pairs; } // parses the binding (value) end of the name=value construct private MapNameValueBinding parseMapNameValueBinding(SourceLocation nameStart, String name, BacktrackingScanner scanner) { SourceLocation location; if (scanner.lookahead() == TokenNameStringLiteral) { String lit = scanner.getCurrentStringLiteral(); location = nameStart.extendedTo(scanner.getCurrentLocation()); match(scanner); return new MapStringArgument(location, name, lit); } else if (scanner.lookahead() == TokenNamefalse || scanner.lookahead() == TokenNametrue) { boolean value = scanner.lookahead() == TokenNametrue; location = nameStart.extendedTo(scanner.getCurrentLocation()); match(scanner); return new MapBooleanArgument(location, name, value); } else { // NOTE: Code duplication: // consume tokens until we reach the end of the ambiguous value // in the future, we may need to allow nested parenthesis and commas // within them. But for now this is mostly to handle type names int startPositition = scanner.getStartPosition(); location = nameStart.extendedTo(scanner.getCurrentLocation()); while (scanner.lookaheadIsNoneOf(TokenNameCOMMA, TokenNameRPAREN, TokenNameEOF)) { location = nameStart.extendedTo(scanner.getCurrentLocation()); match(scanner); } int length = scanner.getStartPosition() - startPositition; String text = scanner.getText(startPositition, length); return new MapAmbiguousArgument(location, name, text); } } private void matchKeyword(ArcumKeyword keyword, BacktrackingScanner scanner) { if (arcumKeyword(keyword, scanner)) { match(TokenNameIdentifier, scanner); } else { ++numErrors; ArcumError.fatalUserError(scanner.getCurrentLocation(), "Expected keyword \"%s\"", keyword); } } private boolean arcumKeyword(ArcumKeyword keyword, BacktrackingScanner scanner) { if (scanner.lookaheadEquals(TokenNameIdentifier)) { String lexeme = scanner.getCurrentTokenString(); return lexeme.equals(keyword.getLexeme()); } return false; } // for testing/debugging // private void outputTokens(char[] sourceString) { // Scanner scanner = ArcumSourceFileParser.newScanner(sourceString); // for (;;) { // int token = nextToken(scanner); // if (token == TokenNameEOF) // break; // System.out.printf("\"%s\" (code: %d)%n", scanner // .getCurrentTokenString(), token); // } // } private void match(BacktrackingScanner scanner) { scanner.match(); } private void match(int expectedToken, BacktrackingScanner scanner) { if (!scanner.lookaheadEquals(expectedToken)) { SourceLocation location = scanner.getCurrentLocation(); if (scanner.lookaheadEquals(TokenNameEOF)) { ++numErrors; ArcumError.fatalUserError(location, "Unexpected EOF"); } else { String message = String.format("Expected \"%s\" instead of \"%s\".", tokenToString(expectedToken), tokenToString(scanner.lookahead())); if (location != null) { ++numErrors; ArcumError.fatalUserError(location, "%s", message); } else { System.out.printf("%s", scanner); ++numErrors; ArcumError.fatalError("%s", message); } } } scanner.nextToken(); } private int nextToken(BacktrackingScanner scanner) { return scanner.nextToken(); } // Returns the number of parser errors found public int getNumErrors() { return numErrors; } public IResource getResource() { return resource; } }