/* * xtc - The eXTensible Compiler * Copyright (C) 2004 Robert Grimm * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package xtc.parser; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.Set; import xtc.Constants; import xtc.util.Utilities; import xtc.tree.Attribute; import xtc.tree.Node; import xtc.tree.Visitor; /** * Visitor to check a grammar for consistency. This visitor should be * applied on a grammar right after parsing, before any other * visitors. * * @author Robert Grimm * @version $Revision: 1.1 $ */ public class Checker extends Visitor { /** The analyzer utility. */ protected final Analyzer analyzer; /** The source file, one line per array entry. */ protected String[] source; /** Flag for whether we have seen an error. */ protected boolean error; /** Flag for whether we are currently processing a predicate. */ protected boolean predicate; /** * Create a new checker. * * @param analyzer The analyzer utility for the new checker. */ public Checker(Analyzer analyzer) { this.analyzer = analyzer; } /** * Print the specified error message. Calling this method also sets * the error flag. * * @param msg The error message. * @param n The offending node. */ protected void msg(String msg, Node n) { String context = (null != analyzer.current())? analyzer.current().nonTerminal.name : null; System.err.println(); Utilities.msg(msg, n.location, context, source); error = true; } /** * Analyze the specified grammar. This method returns a boolean flag * indicating whether the grammar contains errors. */ public Boolean visit(Grammar g) { // (Re)initialize the per-grammar state. LeftRecurser recurser = new LeftRecurser(analyzer); g.accept(recurser); Set recursive = recurser.recursive(); source = (String[])g.getProperty(Constants.SOURCE); error = false; predicate = false; // (Re)initialize the analyzer. analyzer.register(this); analyzer.init(g); // Check attributes (or, options). if (null != g.attributes) { int length = g.attributes.size(); for (int i=0; i<length; i++) { Attribute att = (Attribute)g.attributes.get(i); if ((! CodeGenerator.ATT_LOCATION.equals(att.name)) && (! CodeGenerator.ATT_CONSTANT_BINDING.equals(att.name)) && (! CodeGenerator.ATT_DEBUG.equals(att.name)) && (! CodeGenerator.ATT_NO_MATCHING_ERRORS.equals(att.name))) { msg("unrecognized option " + att.name, att); } else { for (int j=0; j<i; j++) { if (att.equals(g.attributes.get(j))) { msg("duplicate option " + att.name, att); break; } } } } } // Check the top-level declarations. int length = g.topLevel.size(); for (int i=0; i<length; i++) { NonTerminal nt = (NonTerminal)g.topLevel.get(i); if (! analyzer.isDefined(nt)) { msg("undefined top-level nonterminal " + nt.name, nt); } else if (Type.isVoidT(analyzer.lookup(nt).type)) { msg("void top-level nonterminal " + nt.name, nt); } else { for (int j=0; j<i; j++) { if (nt.equals(g.topLevel.get(j))) { msg("duplicate top-level nonterminal " + nt.name, nt); break; } } } } // Check the actual productions. length = g.productions.size(); for (int i=0; i<length; i++) { Production p = (Production)g.productions.get(i); // Check the production's elements. analyzer.process(p); // Check for left-recursive definitions. if (recursive.contains(p.nonTerminal)) { msg("left-recursive definition for nonterminal " + p.nonTerminal.name, p); } // Check for duplicate definitions. for (int j=0; j<i; j++) { Production p2 = (Production)g.productions.get(j); if (p.nonTerminal.equals(p2.nonTerminal)) { msg("duplicate definition for nonterminal " + p.nonTerminal.name, p); break; } } } // Error check. return (error)? Boolean.TRUE : Boolean.FALSE; } /** Analyze the specified production. */ public void visit(Production p) { if (Type.isPrimitive(p.type)) { msg("primitive type " + p.type + " for production " + p.nonTerminal.name, p); } p.element.accept(this); } /** Analyze the specified ordered choice. */ public void visit(OrderedChoice c) { Iterator iter = c.options.iterator(); while (iter.hasNext()) { ((Element)iter.next()).accept(this); } } /** Analyze the specified repetition. */ public void visit(Repetition r) { if (analyzer.strip(r.element) instanceof Action) { msg("repeated action", r); } r.element.accept(this); } /** Analyze the specified option. */ public void visit(Option o) { if (analyzer.strip(o.element) instanceof Action) { msg("optional action", o); } o.element.accept(this); } /** Analyze the specified sequence. */ public void visit(Sequence s) { Iterator iter = s.elements.iterator(); while (iter.hasNext()) { ((Element)iter.next()).accept(this); } } /** Analyze the specified predicate. */ public void visit(Predicate p) { if (predicate) { msg("syntactic predicate within syntactic predicate", p); } boolean pred = predicate; predicate = true; p.element.accept(this); predicate = pred; } /** Analyze the specified semantic predicate. */ public void visit(SemanticPredicate p ) { if (! (p.element instanceof Action)) { msg("malformed semantic predicate", p); } else { Action a = (Action)p.element; if ((null == a.code) || (0 >= a.code.size())) { msg("empty test for semantic predicate", p); } } p.element.accept(this); } /** Analyze the specified binding. */ public void visit(Binding b) { Element bound = analyzer.strip(b.element); if (bound instanceof Sequence) { msg("binding for sequence", b); } else if (bound instanceof NonTerminal) { NonTerminal nt = (NonTerminal)bound; Production p = analyzer.lookup(nt); if ((null != p) && Type.isVoidT(p.type)) { msg("binding for void nonterminal " + nt.name, b); } } else if (bound instanceof Action) { msg("binding for action", b); } b.element.accept(this); } /** Analyze the specified string match. */ public void visit(StringMatch m) { Element matched = analyzer.strip(m.element); if (matched instanceof Sequence) { msg("match for sequence", m); } else if (matched instanceof NonTerminal) { NonTerminal nt = (NonTerminal)matched; Production p = analyzer.lookup(nt); if ((null != p) && Type.isVoidT(p.type)) { msg("match for void nonterminal " + nt.name, m); } } else if (matched instanceof Terminal) { msg("match for terminal", m); } else if (matched instanceof Action) { msg("match for action", m); } m.element.accept(this); } /** Analyze the specified nonterminal. */ public void visit(NonTerminal nt) { if (! analyzer.isDefined(nt)) { msg("undefined nonterminal " + nt.name, nt); } } /** Analyze the specified terminal. */ public void visit(Terminal t) { // Nothing to do. } /** Analyze the specified string literal. */ public void visit(StringLiteral l) { if (0 == l.text.length()) { msg("empty string literal", l); } } /** Analyze the specified character class. */ public void visit(CharClass c) { final int length = c.ranges.size(); if (0 >= length) { msg("empty character class", c); } else { ArrayList list = new ArrayList(c.ranges); Collections.sort(list); for (int i=0; i<length-1; i++) { CharRange r1 = (CharRange)list.get(i); CharRange r2 = (CharRange)list.get(i+1); if (r1.last >= r2.first) { boolean single1 = (r1.first == r1.last); boolean single2 = (r2.first == r2.last); if (single1) { if (single2) { msg("duplicate character \'" + Utilities.escape(r1.last, Utilities.FULL_ESCAPES) + "\' in character class", c); } else { msg("character \'" + Utilities.escape(r1.last, Utilities.FULL_ESCAPES) + "\' already contained in range " + Utilities.escape(r2.first, Utilities.FULL_ESCAPES) + "-" + Utilities.escape(r2.last, Utilities.FULL_ESCAPES), c); } } else { if (single2) { msg("character \'" + Utilities.escape(r2.first, Utilities.FULL_ESCAPES) + "\' already contained in range " + Utilities.escape(r1.first, Utilities.FULL_ESCAPES) + "-" + Utilities.escape(r1.last, Utilities.FULL_ESCAPES), c); } else { msg("ranges " + Utilities.escape(r1.first, Utilities.FULL_ESCAPES) + "-" + Utilities.escape(r1.last, Utilities.FULL_ESCAPES) + " and " + Utilities.escape(r2.first, Utilities.FULL_ESCAPES) + "-" + Utilities.escape(r2.last, Utilities.FULL_ESCAPES) + " overlap", c); } } } } } } /** Analyze the specified action. */ public void visit(Action a) { // Nothing to do. } /** Analyze the specified internal element. */ public void visit(InternalElement e) { msg("internal element", (Element)e); } }