/*
 * Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
 * Use of this file is governed by the BSD 3-clause license that
 * can be found in the LICENSE.txt file in the project root.
 */

package org.antlr.v4.semantics;

import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.Token;
import org.antlr.v4.parse.ActionSplitter;
import org.antlr.v4.parse.ActionSplitterListener;
import org.antlr.v4.tool.Alternative;
import org.antlr.v4.tool.ErrorManager;
import org.antlr.v4.tool.ErrorType;
import org.antlr.v4.tool.Grammar;
import org.antlr.v4.tool.LabelElementPair;
import org.antlr.v4.tool.LabelType;
import org.antlr.v4.tool.Rule;
import org.antlr.v4.tool.ast.ActionAST;
import org.antlr.v4.tool.ast.GrammarAST;

import java.util.List;

Trigger checks for various kinds of attribute expressions. no side-effects.
/** Trigger checks for various kinds of attribute expressions. * no side-effects. */
public class AttributeChecks implements ActionSplitterListener { public Grammar g; public Rule r; // null if action outside of rule public Alternative alt; // null if action outside of alt; could be in rule public ActionAST node; public Token actionToken; // token within action public ErrorManager errMgr; public AttributeChecks(Grammar g, Rule r, Alternative alt, ActionAST node, Token actionToken) { this.g = g; this.r = r; this.alt = alt; this.node = node; this.actionToken = actionToken; this.errMgr = g.tool.errMgr; } public static void checkAllAttributeExpressions(Grammar g) { for (ActionAST act : g.namedActions.values()) { AttributeChecks checker = new AttributeChecks(g, null, null, act, act.token); checker.examineAction(); } for (Rule r : g.rules.values()) { for (ActionAST a : r.namedActions.values()) { AttributeChecks checker = new AttributeChecks(g, r, null, a, a.token); checker.examineAction(); } for (int i=1; i<=r.numberOfAlts; i++) { Alternative alt = r.alt[i]; for (ActionAST a : alt.actions) { AttributeChecks checker = new AttributeChecks(g, r, alt, a, a.token); checker.examineAction(); } } for (GrammarAST e : r.exceptions) { ActionAST a = (ActionAST)e.getChild(1); AttributeChecks checker = new AttributeChecks(g, r, null, a, a.token); checker.examineAction(); } if ( r.finallyAction!=null ) { AttributeChecks checker = new AttributeChecks(g, r, null, r.finallyAction, r.finallyAction.token); checker.examineAction(); } } } public void examineAction() { //System.out.println("examine "+actionToken); ANTLRStringStream in = new ANTLRStringStream(actionToken.getText()); in.setLine(actionToken.getLine()); in.setCharPositionInLine(actionToken.getCharPositionInLine()); ActionSplitter splitter = new ActionSplitter(in, this); // forces eval, triggers listener methods node.chunks = splitter.getActionTokens(); } // LISTENER METHODS // $x.y @Override public void qualifiedAttr(String expr, Token x, Token y) { if ( g.isLexer() ) { errMgr.grammarError(ErrorType.ATTRIBUTE_IN_LEXER_ACTION, g.fileName, x, x.getText()+"."+y.getText(), expr); return; } if ( node.resolver.resolveToAttribute(x.getText(), node)!=null ) { // must be a member access to a predefined attribute like $ctx.foo attr(expr, x); return; } if ( node.resolver.resolveToAttribute(x.getText(), y.getText(), node)==null ) { Rule rref = isolatedRuleRef(x.getText()); if ( rref!=null ) { if ( rref.args!=null && rref.args.get(y.getText())!=null ) { g.tool.errMgr.grammarError(ErrorType.INVALID_RULE_PARAMETER_REF, g.fileName, y, y.getText(), rref.name, expr); } else { errMgr.grammarError(ErrorType.UNKNOWN_RULE_ATTRIBUTE, g.fileName, y, y.getText(), rref.name, expr); } } else if ( !node.resolver.resolvesToAttributeDict(x.getText(), node) ) { errMgr.grammarError(ErrorType.UNKNOWN_SIMPLE_ATTRIBUTE, g.fileName, x, x.getText(), expr); } else { errMgr.grammarError(ErrorType.UNKNOWN_ATTRIBUTE_IN_SCOPE, g.fileName, y, y.getText(), expr); } } } @Override public void setAttr(String expr, Token x, Token rhs) { if ( g.isLexer() ) { errMgr.grammarError(ErrorType.ATTRIBUTE_IN_LEXER_ACTION, g.fileName, x, x.getText(), expr); return; } if ( node.resolver.resolveToAttribute(x.getText(), node)==null ) { ErrorType errorType = ErrorType.UNKNOWN_SIMPLE_ATTRIBUTE; if ( node.resolver.resolvesToListLabel(x.getText(), node) ) { // $ids for ids+=ID etc... errorType = ErrorType.ASSIGNMENT_TO_LIST_LABEL; } errMgr.grammarError(errorType, g.fileName, x, x.getText(), expr); } new AttributeChecks(g, r, alt, node, rhs).examineAction(); } @Override public void attr(String expr, Token x) { if ( g.isLexer() ) { errMgr.grammarError(ErrorType.ATTRIBUTE_IN_LEXER_ACTION, g.fileName, x, x.getText(), expr); return; } if ( node.resolver.resolveToAttribute(x.getText(), node)==null ) { if ( node.resolver.resolvesToToken(x.getText(), node) ) { return; // $ID for token ref or label of token } if ( node.resolver.resolvesToListLabel(x.getText(), node) ) { return; // $ids for ids+=ID etc... } if ( isolatedRuleRef(x.getText())!=null ) { errMgr.grammarError(ErrorType.ISOLATED_RULE_REF, g.fileName, x, x.getText(), expr); return; } errMgr.grammarError(ErrorType.UNKNOWN_SIMPLE_ATTRIBUTE, g.fileName, x, x.getText(), expr); } } @Override public void nonLocalAttr(String expr, Token x, Token y) { Rule r = g.getRule(x.getText()); if ( r==null ) { errMgr.grammarError(ErrorType.UNDEFINED_RULE_IN_NONLOCAL_REF, g.fileName, x, x.getText(), y.getText(), expr); } else if ( r.resolveToAttribute(y.getText(), null)==null ) { errMgr.grammarError(ErrorType.UNKNOWN_RULE_ATTRIBUTE, g.fileName, y, y.getText(), x.getText(), expr); } } @Override public void setNonLocalAttr(String expr, Token x, Token y, Token rhs) { Rule r = g.getRule(x.getText()); if ( r==null ) { errMgr.grammarError(ErrorType.UNDEFINED_RULE_IN_NONLOCAL_REF, g.fileName, x, x.getText(), y.getText(), expr); } else if ( r.resolveToAttribute(y.getText(), null)==null ) { errMgr.grammarError(ErrorType.UNKNOWN_RULE_ATTRIBUTE, g.fileName, y, y.getText(), x.getText(), expr); } } @Override public void text(String text) { } // don't care public void templateInstance(String expr) { } public void indirectTemplateInstance(String expr) { } public void setExprAttribute(String expr) { } public void setSTAttribute(String expr) { } public void templateExpr(String expr) { } // SUPPORT public Rule isolatedRuleRef(String x) { if ( node.resolver instanceof Grammar ) return null; if ( x.equals(r.name) ) return r; List<LabelElementPair> labels = null; if ( node.resolver instanceof Rule ) { labels = r.getElementLabelDefs().get(x); } else if ( node.resolver instanceof Alternative ) { labels = ((Alternative)node.resolver).labelDefs.get(x); } if ( labels!=null ) { // it's a label ref. is it a rule label? LabelElementPair anyLabelDef = labels.get(0); if ( anyLabelDef.type==LabelType.RULE_LABEL ) { return g.getRule(anyLabelDef.element.getText()); } } if ( node.resolver instanceof Alternative ) { if ( ((Alternative)node.resolver).ruleRefs.get(x)!=null ) { return g.getRule(x); } } return null; } }