GrammarVisitor.java
/*
* Copyright 2013 Michael Mackenzie High
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.mackenziehigh.snowflake.parsergen;
import com.mackenziehigh.snowflake.ITreeNode;
import com.mackenziehigh.snowflake.TreeNode;
import com.mackenziehigh.snowflake.Utils;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
/**
* An instance of this class invokes grammar-builder methods, given a grammar's syntax-tree.
*
* @author Mackenzie High
*/
final class GrammarVisitor
{
/**
* This stack is used to store intermediate results,
* which are the either the names of anonymous rules or character-classes.
*/
final Stack<String> operands = new Stack<String>();
/**
* This is the code-generator that will be notified of each significant visitation.
*/
final ICodeGenerator pigeon;
/**
* This counter is used to create names for anonymous grammar-rules.
*/
int anon_counter = 0;
/**
* This counter is used to create names for character-classes.
*/
int class_counter = 0;
/**
* Sole Constructor.
*
* @param code is the code-generator that will be invoked by this visitor.
*/
public GrammarVisitor(final ICodeGenerator code)
{
Utils.checkNonNull(code);
this.pigeon = code;
}
/**
* This method dispatches the visitation of a node to specific visitation methods.
*
* @param node is the node to visit.
*/
final void visit(ITreeNode node)
{
if ("directive_root".equals(node.rule()))
{
this.visit_directiveRoot(node);
}
else if ("directive_hide".equals(node.rule()))
{
this.visit_directiveHide(node);
}
else if ("directive_trace".equals(node.rule()))
{
this.visit_directiveTrace(node);
}
else if ("directive_package".equals(node.rule()))
{
this.visit_directivePackage(node);
}
else if ("directive_parser".equals(node.rule()))
{
this.visit_directiveParser(node);
}
else if ("directive_visitor".equals(node.rule()))
{
this.visit_directiveVisitor(node);
}
else if ("directive_export_parser".equals(node.rule()))
{
this.visit_directiveExportParser(node);
}
else if ("directive_export_visitor".equals(node.rule()))
{
this.visit_directiveExportVisitor(node);
}
else if ("name".equals(node.rule()))
{
operands.push(node.text());
}
else if ("single_cc".equals(node.rule()))
{
this.visit_classSingle(node);
}
else if ("range_cc".equals(node.rule()))
{
this.visit_classRange(node);
}
else if ("combination_cc".equals(node.rule()))
{
this.visit_classCombination(node);
}
else if ("negation_cc".equals(node.rule()))
{
this.visit_classNegation(node);
}
else if ("exclusion_cc".equals(node.rule()))
{
this.visit_classExclusion(node);
}
else if ("named_string_rule".equals(node.rule()))
{
this.visit_namedString(node);
}
else if ("named_character_rule".equals(node.rule()))
{
this.visit_namedCharacter(node);
}
else if ("named_sequence_rule".equals(node.rule()))
{
this.visit_namedSequence(node);
}
else if ("named_sequencedlr_rule".equals(node.rule()))
{
this.visit_namedSequenceDLR(node);
}
else if ("named_choice_rule".equals(node.rule()))
{
this.visit_namedChoice(node);
}
else if ("named_option_rule".equals(node.rule()))
{
this.visit_namedOption(node);
}
else if ("named_star_rule".equals(node.rule()))
{
this.visit_namedStar(node);
}
else if ("named_plus_rule".equals(node.rule()))
{
this.visit_namedPlus(node);
}
else if ("named_repetition_rule".equals(node.rule()))
{
this.visit_namedRepetition(node);
}
else if ("named_and_rule".equals(node.rule()))
{
this.visit_namedAnd(node);
}
else if ("named_not_rule".equals(node.rule()))
{
this.visit_namedNot(node);
}
else if ("anon_string_rule".equals(node.rule()))
{
this.visit_anonString(node);
}
else if ("anon_character_rule".equals(node.rule()))
{
this.visit_anonCharacter(node);
}
else if ("anon_sequence_rule".equals(node.rule()))
{
this.visit_anonSequence(node);
}
else if ("anon_sequencedlr_rule".equals(node.rule()))
{
this.visit_anonSequenceDLR(node);
}
else if ("anon_choice_rule".equals(node.rule()))
{
this.visit_anonChoice(node);
}
else if ("anon_option_rule".equals(node.rule()))
{
this.visit_anonOption(node);
}
else if ("anon_star_rule".equals(node.rule()))
{
this.visit_anonStar(node);
}
else if ("anon_plus_rule".equals(node.rule()))
{
this.visit_anonPlus(node);
}
else if ("anon_repetition_rule".equals(node.rule()))
{
this.visit_anonRepetition(node);
}
else if ("anon_and_rule".equals(node.rule()))
{
this.visit_anonAnd(node);
}
else if ("anon_not_rule".equals(node.rule()))
{
this.visit_anonNot(node);
}
else
{
for (ITreeNode child : node.children())
{
visit(child);
}
}
}
/**
* This method performs the visitation of a %root directive.
*
* @param node is the node to visit.
*/
final void visit_directiveRoot(final ITreeNode node)
{
// Retrieve the user-specified name of the root grammar rule.
final String root = find(node, "name").text().trim();
pigeon.setRoot(root);
}
/**
* This method performs the visitation of a %hide directive.
*
* @param node is the node to visit.
*/
final void visit_directiveHide(final ITreeNode node)
{
pigeon.hide();
}
/**
* This method performs the visitation of a %trace directive.
*
* @param node is the node to visit.
*/
final void visit_directiveTrace(final ITreeNode node)
{
// Retrieve the user-specified number of trace records to store simultaneously.
final int count = Integer.parseInt(find(node, "digits").text().trim());
pigeon.setTraceCount(count);
}
/**
* This method performs the visitation of a %package directive.
*
* @param node is the node to visit.
*/
final void visit_directivePackage(final ITreeNode node)
{
// Retrieve the user-specified name of the package that the parser will be a part of.
final String namespace = TreeNode.find(node, "string_body").text();
pigeon.setPackageName(namespace);
}
/**
* This method performs the visitation of a %parser directive.
*
* @param node is the node to visit.
*/
final void visit_directiveParser(final ITreeNode node)
{
// Retrieve the user-specified name of the parser.
final String parser_name = TreeNode.find(node, "string_body").text();
pigeon.setParserName(parser_name);
}
/**
* This method performs the visitation of a %visitor directive.
*
* @param node is the node to visit.
*/
final void visit_directiveVisitor(final ITreeNode node)
{
// Retrieve the user-specified name of the visitor.
final String visitor_name = TreeNode.find(node, "string_body").text();
pigeon.setVisitorName(visitor_name);
}
/**
* This method performs the visitation of a %export-parser directive.
*
* @param node is the node to visit.
*/
final void visit_directiveExportParser(final ITreeNode node)
{
final String path = TreeNode.find(node, "string_body").text();
final File file = new File(path);
pigeon.addExportParserFile(file);
}
/**
* This method performs the visitation of a %export-visitor directive.
*
* @param node is the node to visit.
*/
final void visit_directiveExportVisitor(final ITreeNode node)
{
final String path = TreeNode.find(node, "string_body").text();
final File file = new File(path);
pigeon.addExportVisitorFile(file);
}
/**
* This method performs the visitation of a named string-rule.
*
* @param node is the node to visit.
*/
final void visit_namedString(final ITreeNode node)
{
// Retrieve the name of the new string rule.
final String name = find(node, "assignee").text();
// Retrieve the value of the new string rule.
final String value = find(node, "string_body").text();
// Create the new string rule.
pigeon.str(name, value);
}
/**
* This method performs the visitation of a named character-rule.
*
* @param node is the node to visit.
*/
final void visit_namedCharacter(final ITreeNode node)
{
// Visit the character-class.
visitChildren(node);
// Retrieve the name of the new character rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the character-class that the new rule will match.
final String clazz = operands.pop().toString();
// Create the new character rule.
pigeon.chr(name, clazz);
}
/**
* This method performs the visitation of a named sequence-rule.
*
* @param node is the node to visit.
*/
final void visit_namedSequence(final ITreeNode node)
{
// Visit the elements in the sequence.
visitChildren(node);
// Retrieve the name of the new sequence rule.
final String name = find(node, "assignee").text();
// Retrieve the names of the elements in the new sequence rule.
final List<String> elements = pop(0);
// Create the new sequence rule.
pigeon.sequence(name, elements.toArray(new String[0]));
}
/**
* This method performs the visitation of a named sequence-dlr-rule.
*
* @param node is the node to visit.
*/
final void visit_namedSequenceDLR(final ITreeNode node)
{
// Visit the base-case and the shared elements of the sequence.
visitChildren(node);
// Retrieve the name of the new sequence-DLR rule.
final String name = find(node, "assignee").text();
// These are the shared elements of the sequence.
final List<String> shared = pop(1);
// Retrieve the base-case element of the sequence.
final String base = operands.pop().toString();
// Create the new sequence-DLR rule.
pigeon.sequenceDLR(name, base, shared.toArray(new String[0]));
}
/**
* This method performs the visitation of a named choice-rule.
*
* @param node is the node to visit.
*/
final void visit_namedChoice(final ITreeNode node)
{
// Visit the options.
visitChildren(node);
// Retrieve the name of the new choice rule.
final String name = find(node, "assignee").text();
// Retrieve the names of the options in the new choice rule.
final List<String> options = pop(0);
// Create the new choice rule.
pigeon.choose(name, options.toArray(new String[0]));
}
/**
* This method performs the visitation of a named option-rule.
*
* @param node is the node to visit.
*/
final void visit_namedOption(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Retrieve the name of the new option rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the item to match.
final String item = operands.pop();
// Create the new option rule.
pigeon.option(name, item);
}
/**
* This method performs the visitation of a named star-rule.
*
* @param node is the node to visit.
*/
final void visit_namedStar(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Retrieve the name of the new star rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the item to match.
final String item = operands.pop();
// Create the new star rule.
pigeon.star(name, item);
}
/**
* This method performs the visitation of a named plus-rule.
*
* @param node is the node to visit.
*/
final void visit_namedPlus(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Retrieve the name of the new plus rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the item to match.
final String item = operands.pop();
// Create the new plus rule.
pigeon.plus(name, item);
}
/**
* This method performs the visitation of a named repetition-rule.
*
* @param node is the node to visit.
*/
final void visit_namedRepetition(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Retrieve the name of the new repetition rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the item to match.
final String item = operands.pop();
// Retrieve the minimum number of times to repeat the item.
final String minimum = find(find(node, "repetition_minimum"), "digits").text().trim();
// Retrieve the maximum number of times to repeat the item.
final String maximum = find(find(node, "repetition_maximum"), "digits").text().trim();
final int min = Integer.parseInt(minimum);
final int max = Integer.parseInt(maximum);
// Create the new repetition rule.
pigeon.repeat(name, item, min, max);
}
/**
* This method performs the visitation of a named and-rule.
*
* @param node is the node to visit.
*/
final void visit_namedAnd(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Retrieve the name of the new and-rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the item to match.
final String item = operands.pop();
// Create the new and-rule.
pigeon.and(name, item);
}
/**
* This method performs the visitation of a named not-rule.
*
* @param node is the node to visit.
*/
final void visit_namedNot(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Retrieve the name of the new not-rule.
final String name = find(node, "assignee").text();
// Retrieve the name of the item to match.
final String item = operands.pop();
// Create the new not-rule.
pigeon.not(name, item);
}
/**
* This method performs the visitation of an anonymous string-rule.
*
* @param node is the node to visit.
*/
final void visit_anonString(final ITreeNode node)
{
// Create the name of the new string rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the value of the new string rule.
final String value = find(node, "string_body").text();
// Create the new string rule.
pigeon.str(name, value);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous character-rule.
*
* @param node is the node to visit.
*/
final void visit_anonCharacter(final ITreeNode node)
{
// Visit the character-class.
visitChildren(node);
// Create the name of the new character rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the character-class that the new rule will match.
final String clazz = operands.pop().toString();
// Create the new character rule.
pigeon.chr(name, clazz);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous sequence-rule.
*
* @param node is the node to visit.
*/
final void visit_anonSequence(final ITreeNode node)
{
// The current operands are not a part of the sequence.
final int stack_size = operands.size();
// Visit the elements in the sequence.
visitChildren(node);
// Create the name of the new sequence rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the names of the elements in the sequence.
final List<String> elements = pop(stack_size);
// Create the new sequence rule.
pigeon.sequence(name, elements.toArray(new String[0]));
// The name of the new rule is an operand of another rule.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous sequence-dlr-rule.
*
* @param node is the node to visit.
*/
final void visit_anonSequenceDLR(final ITreeNode node)
{
// The current operands are not a part of the sequence.
final int stack_size = operands.size();
// Visit the base-case and shared elements of the sequence.
visitChildren(node);
// Create the name of the new sequence-DLR rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the shared elements of the sequence.
final List<String> shared = pop(stack_size + 1);
// Retrieve the base-case of the sequence.
final String base = operands.pop();
// Create the new sequence-DLR rule.
pigeon.sequenceDLR(name, base, shared.toArray(new String[0]));
// The name of the new rule is an operand of another rule.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous choice-rule.
*
* @param node is the node to visit.
*/
final void visit_anonChoice(final ITreeNode node)
{
// The current operands are not options in the choice.
final int stack_size = operands.size();
// Visit the options in the choice.
visitChildren(node);
// Create the name of the new choice-rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the options in the choice.
final List<String> options = pop(stack_size);
// Create the new choice-rule.
pigeon.choose(name, options.toArray(new String[0]));
// The name of the new rule is an operand of another rule.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous option-rule.
*
* @param node is the node to visit.
*/
final void visit_anonOption(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Create the name of the new option rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the name of the item.
final String item = operands.pop().toString();
// Create the new option rule.
pigeon.option(name, item);
// Push the name of the new option rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous star-rule.
*
* @param node is the node to visit.
*/
final void visit_anonStar(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Create the name of the new star rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the name of the item.
final String item = operands.pop().toString();
// Create the new star rule.
pigeon.star(name, item);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous plus-rule.
*
* @param node is the node to visit.
*/
final void visit_anonPlus(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Create the name of the new plus rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the name of the item.
final String item = operands.pop().toString();
// Create the new plus rule.
pigeon.plus(name, item);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous repetition-rule.
*
* @param node is the node to visit.
*/
final void visit_anonRepetition(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Create the name of the new repetition rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the name of the item to match.
final String item = operands.pop();
// Retrieve the minimum number of times to repeat the item.
final String minimum = find(find(node, "repetition_minimum"), "digits").text().trim();
// Retrieve the maximum number of times to repeat the item.
final String maximum = find(find(node, "repetition_maximum"), "digits").text().trim();
final int min = Integer.parseInt(minimum);
final int max = Integer.parseInt(maximum);
// Create the new repetition rule.
pigeon.repeat(name, item, min, max);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous and-rule.
*
* @param node is the node to visit.
*/
final void visit_anonAnd(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Create the name of the new and-rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the name of the item.
final String item = operands.pop().toString();
// Create the new and-rule.
pigeon.and(name, item);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of an anonymous not-rule.
*
* @param node is the node to visit.
*/
final void visit_anonNot(final ITreeNode node)
{
// Visit the item.
visitChildren(node);
// Create the name of the new not-rule.
final String name = '@' + Integer.toString(anon_counter++);
// Retrieve the name of the item.
final String item = operands.pop().toString();
// Create the new not-rule.
pigeon.not(name, item);
// Push the name of the new rule onto the operand stack.
operands.push(name);
}
/**
* This method performs the visitation of a single-character character-class.
*
* @param node is the node to visit.
*/
final void visit_classSingle(final ITreeNode node)
{
// Compute the value of the represented character.
final char character = extractChar(node.text());
// Create the name of the character-class.
final String name = "@class" + Integer.toString(class_counter++);
// Create the new character-class.
pigeon.range(name, character);
// The name of the new character-class is an operand, so push it onto the operand-stack.
operands.push(name);
}
/**
* This method performs the visitation of a multiple-character character-class.
*
* @param node is the node to visit.
*/
final void visit_classRange(final ITreeNode node)
{
// Compute the value of the minimum character in the range.
final char minimum = extractChar(node.childAt(0).text());
// Compute the value of the maximum character in the range.
final char maximum = extractChar(node.childAt(4).text());
// Create the name of the new character-class.
final String name = "@class" + Integer.toString(class_counter++);
// Create the new character-class itself.
pigeon.range(name, minimum, maximum);
// The name of the new character-class is an operand, so push it onto the operand-stack.
operands.push(name);
}
/**
* This method performs the visitation of a combination character-class.
*
* @param node is the node to visit.
*/
final void visit_classCombination(final ITreeNode node)
{
// The current operands are not a part of the combination.
int stack_size = operands.size();
// Visit the elements of the combination.
visitChildren(node);
// Create the name of the new character-class.
final String name = "@class" + Integer.toString(class_counter++);
// Retrieve the elements of the combination.
final List<String> elements = pop(stack_size);
// Create the new character-class.
pigeon.combine(name, elements.toArray(new String[0]));
// The name of the new character-class is an operand, so push it onto the operand-stack.
operands.push(name);
}
/**
* This method performs the visitation of a negation character-class.
*
* @param node is the node to visit.
*/
final void visit_classNegation(final ITreeNode node)
{
// The current operands are not a part of the negation.
int stack_size = operands.size();
// Visit the elements of the negation.
visitChildren(node);
// Retrieve the elements of the negation.
final List<String> elements = pop(stack_size);
// It is necessary to combine the elements into a single character-class.
// Create the name of the aforedescribed character-class.
final String negated_name = "@class" + Integer.toString(class_counter++);
// Create the name of the negation character-class.
final String name = "@class" + Integer.toString(class_counter++);
// Create the combination character-class.
pigeon.combine(negated_name, elements.toArray(new String[0]));
// Create the negation character-class, which negates the combination character-class.
pigeon.negate(name, negated_name);
// The name of the new character-class is an operand, so push it onto the operand-stack.
operands.push(name);
}
/**
* This method performs the visitation of a exclusion character-class.
*
* @param node is the node to visit.
*/
final void visit_classExclusion(final ITreeNode node)
{
// Visit the includes and excludes parts of the exclusion.
visitChildren(node);
// Retrieve the character-class that is excluded.
final String excludes = operands.pop().toString();
// Retrieve the character-class from which characters are excluded.
final String includes = operands.pop().toString();
// Create the name of the new character-class.
final String name = "@class" + Integer.toString(class_counter++);
// Create the new character-class.
pigeon.exclude(name, includes, excludes);
// The name of the new character-class is an operand, so push it onto the operand-stack.
operands.push(name);
}
final List<String> pop(final int stack_size)
{
final LinkedList<String> result = new LinkedList<String>();
while (stack_size < operands.size())
{
result.addFirst(operands.pop());
}
return result;
}
/**
* This method performs a depth-first search in order to find a node in an AST.
*
* <p>This method returns the first match that it finds.</p>
*
* @param parent is the root of the subtree.
* @param name is the name of the rule that created the node to find.
* @return the found node, or null, if no such node was found.
*/
final ITreeNode find(final ITreeNode parent,
final String name)
{
for (ITreeNode child : parent.iterableDFS())
{
if (child.rule().equals(name))
{
return child;
}
}
return null;
}
/**
* This method invokes the generalized visitation method on all the children of a node.
*
* @param parent is the node whose children will be visited.
*/
final void visitChildren(final ITreeNode parent)
{
for (ITreeNode child : parent.children())
{
visit(child);
}
}
/**
* This method converts a character-literal to a char value.
*
* <p>
* If the character is of the form 'X', then the value is the numeric form of X.
* If the character is of the form 123C, then the value is 123.
* If the character is of the form
* </p>
*
* @param literal is the literal itself.
* @return the aforedescribed value.
*/
final char extractChar(String literal)
{
literal = literal.trim();
if (literal.equals("MIN"))
{
return Character.MIN_VALUE;
}
if (literal.equals("MAX"))
{
return Character.MAX_VALUE;
}
return literal.matches("[0-9]+C")
? (char) Integer.parseInt(literal.replace("C", ""))
: literal.charAt(1);
}
}