package grammar;
import static util.MemberAccessor.setFinal;
import grammar.Expression.Rule;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* A grammar is a set of named parsing expressions. One of the rule is the root
* rule, which is the entry point to the grammar.
*/
public class Grammar
{
/*****************************************************************************
* A mapping from rule names to the corresponding parsing expression.
*/
private final Map<String, Rule> rules = new HashMap<>();
/****************************************************************************/
public final ExpressionTreeCleaner cleaner = new ExpressionTreeCleaner(this);
/*****************************************************************************
* Builds the grammar from a class with parsing expression fields. The fields
* whose type is Expression (or one of its subclasses) are converted to rules,
* using the field name as rule name. The root rule needs to be held in a
* field named "root".
*
* Mostly, building a grammar means building a proper expression graph from
* the class. When modeling a grammar as a Java class, reference to other
* rules can be implemented as reference to the field that holds the rule, but
* because of recursion, that alone doesn't suffice. Hence the presence of
* reference expressions, to allow referencing a rule before its field gets
* declared.
*
* With reference expressions, expressions form a graph. Once reference are
* resolved, this tree becomes a graph. If there is some recursion in the
* grammar, this graph will contain loops.
*
* @see Expression.tidy()
*/
public Grammar(Class<?> klass)
{
try {
Object grammar = klass.newInstance();
for (int i = 0 ; i < 2 ; ++i)
for (Field field : klass.getFields())
{
Object o = field.get(grammar);
if (Rule.class.isAssignableFrom(o.getClass()))
{
Rule rule = (Rule) o;
if (i == 1) {
// always returns $rule
cleaner.clean(rule);
}
else {
if (rule.name == null) {
setFinal(o, "name", field.getName());
}
registerRule(rule);
}
}
}
// If there is an initialize(grammar) method, call it.
Method method = klass.getMethod("initialize", Grammar.class);
method.invoke(grammar, this);
}
catch (NoSuchMethodException e) {
/* ignore (from getMethod()) */
}
catch (Exception e) {
e.printStackTrace();
throw new Error("Problem while extracting grammar rules "
+ "from class " + klass, e);
}
}
/*****************************************************************************
* Adds an alternative (which is also a rule) to a rule of the base grammar.
* The added rule should have its name set, and should have gone through the
* cleaner. No rule with the same name as the added rule should be in the
* grammar.
*/
public void addRuleAlternative(
Rule extendedRule, Rule rule, boolean prioritary)
{
registerRule(rule);
addExistingRuleAlternative(extendedRule, rule, prioritary);
}
/*****************************************************************************
* Same as {@link #addRuleAlternative(Rule, Rule)}, but allows the alternative
* to already be known by the grammar.
*/
public void addExistingRuleAlternative(
Rule extendedRule, Rule rule, boolean prioritary)
{
if (prioritary) {
extendedRule.children().add(0, rule);
}
else {
extendedRule.children().add(rule);
}
}
/****************************************************************************/
public void removeRuleAlternative(Rule extendedRule, Rule rule)
{
unregisterRule(rule);
extendedRule.children().remove(rule);
}
/*****************************************************************************
* Registers a rule with the grammar. Throws an error if there is already a
* rule with the same name.
*/
public void registerRule(Rule rule)
{
if (rules.put(rule.name, rule) != null)
{
throw new Error("Trying to register a rule"
+ " with an already registered name: \"" + rule.name + "\".");
}
}
/*****************************************************************************
* Makes the grammar forget about the given rule. Throws an error if the
* grammar does not know about the rule.
*/
public void unregisterRule(Rule rule)
{
if (rules.remove(rule.name) == null)
{
throw new Error("Trying to unregister an unknown rule: \""
+ rule.name + "<\".");
}
}
/*****************************************************************************
* Retrieves the rule with the given name. Exits the program with an error
* if the rule does not exist.
*/
public Rule rule(String name)
{
Rule rule = rules.get(name);
if (rule == null) {
throw new Error("No rule named \"" + name + "\".");
}
return rule;
}
/*****************************************************************************
* Returns the rule with the given name if one exists, or null.
*/
Rule maybeRule(String name)
{
return rules.get(name);
}
/*****************************************************************************
* Runs a new expression through the cleaner, in order to make its use valid.
*/
public Expression clean(Expression expr)
{
return cleaner.clean(expr);
}
/*****************************************************************************
* Clean a rule which is not registered. This method is necessary to allow
* for recursive rules.
*/
public Rule cleanUnregisteredRule(Rule rule)
{
registerRule(rule);
Rule out = (Rule) clean(rule);
unregisterRule(rule);
return out;
}
}