All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thesett.aima.logic.fol.isoprologparser.DynamicOperatorParser Maven / Gradle / Ivy

Go to download

Library of code developed from 'Artificial Intelligence a Modern Approach', Prentice Hall.

The newest version!
/*
 * Copyright The Sett Ltd, 2005 to 2014.
 *
 * 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.thesett.aima.logic.fol.isoprologparser;

import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.OpSymbol;
import com.thesett.aima.logic.fol.Term;
import com.thesett.common.parsing.SourceCodeException;
import com.thesett.common.util.Queue;
import com.thesett.common.util.StackQueue;

/**
 * DynamicOperatorParser is a 'deferred decision parser' that can be used to parse Prolog with dynamically defined
 * operators. Prolog operators are dynamic; they can be redefined at run-time with altered fixities, associativities and
 * priorities. This means that the grammar of prolog is not fixed and parser generator tools have a hard time coping
 * with it. A solution is to parse sequences of adjacent terms into a flat array, and then to pass this array to this
 * parser to attempt to correctly parse the sequence of terms using the currently defined operators.
 *
 * 

This parser maintains the {@link OperatorTable} in an ISO Prolog compliant manner. That is, it does not permit * the ',' operator to be redefined, it does not allow an infix and postfix operator with the same name to be * simultaneosly defined, priority must be between 0 and 1200 inclusive, setting a priority of 0 on an operator removes * it from the table. It is the responsibility of the caller to ensure that operators have valid names. * *

The grammar to parse looks like: * *

 * T := t
 * T := op T
 * T := T op
 * T := T op T
 * 
* * Where t is a term already fully parsed, for example, a functor, an atom, a variable, an operator as an atom. 'op' is * an operator, which can be pre, post, or infix by its position in the different rules. * *

This grammar can be parsed using a simple Shift-Reduce, or LR parser. The translation of the grammar into a * parsing table looks like: * *

Augment the grammar with a start state and number the rules: * *

 * 0. S := T
 * 1. T := t
 * 2. T := op T
 * 3. T := T op
 * 4. T := T op T
 * 
* *

Expand the rules into item sets by shifting a '.' down the rules: * *

Item Set 0 * *

 *   S := . T
 * + T := . t
 * + T := . op T
 * + T := . T op
 * + T := . T op T
 * 
* *

Item Set 1 * *

 *   T := t .
 * 
* *

Item Set 2 * *

 *   T := op . T
 * + T := . t
 * + T := . op T
 * + T := . T op
 * + T := . T op T
 * 
* *

Item Set 3 * *

 *   S := T .
 *   T := T . op
 *   T := T . op T
 * 
* *

Item Set 4 * *

 *   T := op T .
 *   T := T . op
 *   T := T . op T
 * 
* *

Item Set 5 * *

 *   T := T op .
 *   T := T op . T
 * + T := . t
 * + T := . op T
 * + T := . T op
 * + T := . T op T
 * 
* *

Item Set 6 * *

 *   T := T op T .
 *   T := T . op
 *   T := T . op T
 * 
* *

Which yields the transition table between these states of: * *

*
Transition Table
t op T *
0 1 2 3 *
1 *
2 1 2 4 *
3 5 *
4 5 *
5 1 2 6 *
6 5 *
* *

Which results in an LR parser table of: * *

*
LR Parser Table
Action Goto *
t op $ T *
0 s1 s2 3 *
1 r1 r1 r1 *
2 s1 s2 4 *
3 s5 acc *
4 r2 s5/r2 r2 *
5 s1/r3 s2/r3 r3 6 *
6 r4 s5/r4 r4 *
* *

From which it can be seen that there are 4 shift-reduce conflicts. It is these conflicts that make this grammar * difficult to parse with generator tools. This DynamicOperatorParser uses the operator precedence, fixity and * associativity values to decide which way to resolve the conflicts. The 's1/r2' conflict when encountering 't' in * state 5 is always shifted as non-operator terms have priority 0 in Prolog. The remaining conflicts are all * operator/operator conflicts, and are therefore a matter of deciding which operator takes precedence; the one most * recently seen, in which case reduce; the one which is encountered as the next symbol, in which case shift. The * operators priority values are compared and the one with the lower value wins. If the priorities are equal, then the * associativity of the next operator is used to decide, if it binds left then reduce, binds right then shift, if it is * non-associative then the choice is ambigious so an error is reported. * *

The parser is further complicated by the fact that operators can be overloaded in Prolog. This means that each * {@link CandidateOpSymbol} encountered can map onto multiple {@link OpSymbol} definitions and a choice as to which one * is used must be made in the context of the position in which is encountered. Consider the following sequences of * symbols when and operator/operator conflict is encountered: * *

 * alpha OpA beta OpB ...
 * 
* *

Where alpha and beta are sequences of symbols, possibly empty. Now consider for the empty or non-empty values of * alpha and beta, what fixities of operator are possible for OpA and OpB. Note that OpA and OpB must be prefix, * postfix, or infix and cannot be an constant atom, because operators as constant atoms must be bracketed in ISO * Prolog, and will therefore have already been resolved from a {@link CandidateOpSymbol} to an {@link OpSymbol}. * *

*
Possibly Fixity Combinations
alpha = not_empty, beta = not_empty (OpA = in, OpB = post) or (OpA = in, OpB = in) *
alpha = empty, beta = not_empty (OpA = pre, OpB = post) or (OpA = in, OpB = in) *
alpha = not_empty, beta = empty (OpA = post, OpB = in) or (OpA = post, OpB = post) or (OpA = in, OpB = pre) *
alpha = empty, beta = empty (OpA = pre, OpB = pre) *
* *

ISO Prolog does not permit a postfix and infix operator with the same name to be defined simultaneously, which * reduces the above table to only one possible operator combination in each case. * *

The evaluation of the fixity combinations, and operator precedence to decide conflicts can be seen in the * {@link ResolveAction#apply()} method. * *

Error reporting is added to the LR parser by filling the blank transitions in the parse tables with error actions * with custom error messages specific to the empty transition. * *

*
CRC Card
Responsibilities Collaborations *
Add new operators to the table following ISO Prolog rules. *
Find all candidate operators matching a given name. *
Parse sequences of terms and candidate operators using ISO Prolog rules. * {@link Term}, {@link CandidateOpSymbol}, {@link OpSymbol}. *
* * @author Rupert Smith */ public class DynamicOperatorParser implements OperatorTable { /** Used for debugging purposes. */ /* private static final Logger log = Logger.getLogger(DynamicOperatorParser.class.getName()); */ /** Encodes the possible symbols that this parser accepts. */ private enum Symbol { /** A term which is not a {@link CandidateOpSymbol}. It may be a previously resolved {@link OpSymbol} though. */ Term, /** A symbol which is a {@link CandidateOpSymbol} to be resovled onto an {@link OpSymbol}. */ Op, /** A final symbol to complete the sequence. */ Final } /** Defines the action to shift to state 1. */ private ShiftAction s1 = new ShiftAction(1); /** Defines the action to shift to state 2. */ private ShiftAction s2 = new ShiftAction(2); /** Defines the action to shift to state 5. */ private ShiftAction s5 = new ShiftAction(5); /** Defines the action to resolve using rule 1. */ private ReduceAction r1 = new ReduceAction(1); /** Defines the action to resolve using rule 2. */ private ReduceAction r2 = new ReduceAction(2); /** Defines the action to resolve using rule 3. */ private ReduceAction r3 = new ReduceAction(3); /** Defines the action to resolve using rule 4. */ private ReduceAction r4 = new ReduceAction(4); /** Defines an error message when the final symbol is encountered in the start state. */ private ErrorAction e1 = new ErrorAction("Term sequence cannot be empty."); /** Defines an error message when nothing is specified to apply an operator to. */ private ErrorAction e2 = new ErrorAction("Something expected after operator."); /** Defines an error message when two adjacement terms no seperated by an operator are encounterd. */ private ErrorAction e3 = new ErrorAction("Cannot have two adjacent non-operator terms."); /** * Holds the LR parser action table, that describes which action to perform when encountering a given symbol in a * given state. The first index to the two dimensional array is the state number, the second is the {@link Symbol}s * ordinal value. Note that there are three operator/operator shift/reduce conflicts in this table. */ private Action[][] actionTable = new Action[][] { { s1, s2, e1 }, { r1, r1, r1 }, { s1, s2, e2 }, { e3, s5, new Accept() }, { r2, new ResolveAction(s5, r2), r2 }, { s1, new ResolveAction(s2, r3), r3 }, { r4, new ResolveAction(s5, r4), r4 } }; /** Holds the LR parser goto table, that describes the state to transition to after resolving a rule. */ private Integer[] gotoTable = new Integer[] { 3, null, 4, null, null, 6, null }; /** Holds the rules to apply when resolving. */ private Action[] rules = { null, new Rule1(), new Rule2(), new Rule3(), new Rule4() }; /** Holds the parser state stack. */ private Queue stack = new StackQueue(); /** Holds the parsers current state. */ private int state; /** Holds the parsers current position within the input sequence of terms. */ private int position; /** Holds the output stack onto which terms are placed pending their consumption by rule reductions. */ private Queue outputStack = new StackQueue(); /** Holds the current next term on the input sequence. */ private Term nextTerm; /** Holds the table of defined operators by name and fixity. */ private Map> operators = new HashMap>(); /** * Parses a flat list of terms, which are literals, variables, functors, or operators into a tree in such a way that * the operators associativity and precendence is obeyed. * * @param terms A flat list of terms possibly containing operators, to be parsed. * * @return The functor at the root of the sequence of terms parsed into an abstract syntax tree. * * @throws SourceCodeException If the list of terms does not form a valid syntactical construction under the current * set of defined operators. */ public Functor parseOperators(Term[] terms) throws SourceCodeException { // Initialize the parsers state. stack.offer(0); state = 0; position = 0; nextTerm = null; // Consume the terms from left to right. for (position = 0; position <= terms.length;) { Symbol nextSymbol; // Decide what the next symbol to parse is; candidate op, term or final. if (position < terms.length) { nextTerm = terms[position]; if (nextTerm instanceof CandidateOpSymbol) { nextSymbol = Symbol.Op; } else { nextSymbol = Symbol.Term; } } else { nextSymbol = Symbol.Final; } // Look in the action table to find the action associated with the current symbol and state. Action action = actionTable[state][nextSymbol.ordinal()]; // Apply the action. action.apply(); } return (Functor) outputStack.poll(); } /** * Sets the priority and associativity of a named operator in this table. This method may be used to remove * operators by some implementations, through a special setting of the priority value. A priority value of zero will * remove any existing operator matching the fixity of the one specified (that is pre, or post/infix). To be * accepted, the operator must have a priority between 0 and 1200 inclusive, and can only be a postfix operator when * an infix is not already defined with the same name, and similarly for infix operators when a postfix operator is * already defined. * * @param name The interned name of the operator to set in the table. * @param textName The text name of the operator to set in the table. * @param priority The priority of the operator. Zero removes the operator. * @param associativity The associativity of the operator. */ public void setOperator(int name, String textName, int priority, OpSymbol.Associativity associativity) { // Check that the name of the operator is valid. // Check that the priority of the operator is valid. if ((priority < 0) || (priority > 1200)) { throw new IllegalArgumentException("Operator priority must be between 0 and 1200 inclusive."); } OpSymbol opSymbol = new OpSymbol(name, textName, associativity, priority); // Consult the defined operators to see if there are any already defined that match the name of the // new definition, otherwise a map of operators by fixity needs to be created. EnumMap operatorMap = operators.get(textName); // Check if the priority is non-zero in which case the operator is being added or redefined. if (priority > 0) { if (operatorMap == null) { operatorMap = new EnumMap(OpSymbol.Fixity.class); operators.put(textName, operatorMap); } // Check if the operators fixity to see if further rules regarding simultaneous definition of post and // infix operators need to be applied. if (opSymbol.isPostfix()) { // Postfix, so check if an infix definition already exists, which is not allowed. if (operatorMap.containsKey(OpSymbol.Fixity.In)) { throw new IllegalArgumentException( "Cannot define a postfix operator when an infix one with the same name already exists."); } } else if (opSymbol.isInfix()) { // Infix, so check if a postfix definition already exists, which is not allowed. if (operatorMap.containsKey(OpSymbol.Fixity.Post)) { throw new IllegalArgumentException( "Cannot define an infix operator when an postfix one with the same name already exists."); } } // Add the operator to the table replacing any previous definition of the same fixity. operatorMap.put(opSymbol.getFixity(), opSymbol); } else { // The priority is zero, in which case the operator is to be removed. if ((operatorMap != null) && opSymbol.isPrefix()) { // Remove it from the prefix table, if it exists there. operatorMap.remove(OpSymbol.Fixity.Pre); } else if ((operatorMap != null) && (opSymbol.isPostfix() || opSymbol.isInfix())) { // Remove it from the postfix/infix table, if it exists there. operatorMap.remove(OpSymbol.Fixity.Post); operatorMap.remove(OpSymbol.Fixity.In); } } } /** * Checks the operator table for all possible operators matching a given name. * * @param name The interned name of the operator to find. * * @return An array of matching operators, or null if none can be found. */ public EnumMap getOperatorsMatchingNameByFixity(String name) { return operators.get(name); } /** * Prints the current state of this parser as a string, mainly for debugging purposes. * * @return The current state of this parser as a string. */ public String toString() { return "DynamicOperatorsParser: [ state = " + state + ", nextTerm = " + nextTerm + " stack = " + stack + " outputStack = " + outputStack + " ]"; } /** * Checks if a candidate operator symbol can have one of the specified fixities, and resolve it to an oeprator with * that fixity if so. If it cannot be resolved an exception is raised. * * @param candidate The candidate operator symbol to resolve. * @param fixities The possible fixities to resolve the symbol to. * * @return The candidate operator resolved to an actual operator. * * @throws SourceCodeException If the candidate operator cannot be resolved. */ protected static OpSymbol checkAndResolveToFixity(CandidateOpSymbol candidate, OpSymbol.Fixity... fixities) throws SourceCodeException { OpSymbol result = null; for (OpSymbol.Fixity fixity : fixities) { result = candidate.getPossibleOperators().get(fixity); if (result != null) { break; } } if (result == null) { throw new SourceCodeException("Operator " + candidate + " must be one of " + Arrays.toString(fixities) + ", but does not have the required form.", null, null, null, candidate.getSourceCodePosition()); } return result; } /** * A base class for defining parsers actions and rule reductions from. */ private abstract static class Action { /** * Applies a parser action; shift, reduce, resolve or accept. * * @throws SourceCodeException With an error location if the action cannot be performed because the input * sequence does not form a valid instance of the grammar. */ public abstract void apply() throws SourceCodeException; } /** * Defines a shift action. Shift changes to a new state, places the new state on the stack, consumes one input * symbol and places the symbol on the output stack. */ private class ShiftAction extends Action { /** Holds the state to shift to. */ public int toState; /** * Creates a shift action to the specified state. * * @param toState The state to shift to. */ private ShiftAction(int toState) { this.toState = toState; } /** * Performs a shift action. Changes to a new state, places the new state on the stack, consumes one input symbol * and places the symbol on the output stack. */ public void apply() { state = toState; stack.offer(state); position++; outputStack.offer(nextTerm); } } /** * Defines a reduce action. Reduce consumes states from the state stack, and symbols from the output stack in number * equal to the number of symbols on the right hand side of the rule that is being reduced by. It then shifts to the * state given by the goto table from the state left on top of the state stack. In this implementation the * consumption of symbols and states from the stacks is delegated to a {@link Action} implementation. */ private class ReduceAction extends Action { /** Holds the rule number to reduce by. */ public int ruleNum; /** * Creates a reduce action to reduce by the specified rule. * * @param rule The rule number to reduce by. */ private ReduceAction(int rule) { this.ruleNum = rule; } /** * Performs a reduce by the specified rule number. * * @throws SourceCodeException With an error location if the action cannot be performed because the input * sequence does not form a valid instance of the grammar. */ public void apply() throws SourceCodeException { Action rule = rules[ruleNum]; rule.apply(); state = gotoTable[stack.peek()]; stack.offer(state); } } /** * ResolveAction decides a shift-reduce conflict between a pair of {@link CandidateOpSymbol}s. The resolution * procedure is as described in the class level comment for {@link DynamicOperatorParser}. */ private class ResolveAction extends Action { /** The shift action to perform if a shift resolution is chosen. */ ShiftAction shift; /** The reduce action to perform if a reduce resolution is chosen. */ ReduceAction reduce; /** * Creates a resolve action between the specified shift and reduce actions. * * @param shift The shift action to perform if a shift resolution is chosen. * @param reduce The reduce action to perform if a reduce resolution is chosen. */ private ResolveAction(ShiftAction shift, ReduceAction reduce) { this.shift = shift; this.reduce = reduce; } /** * Performs shift-reduce resolution between a pair of operators as describe in {@link DynamicOperatorParser}. * * @throws SourceCodeException With an error location if the action cannot be performed because the input * sequence does not form a valid instance of the grammar. */ public void apply() throws SourceCodeException { // This cast should not fail, as resolve is only called when the next symbol is a candidate operator. CandidateOpSymbol nextCandidate = (CandidateOpSymbol) nextTerm; // Walk back down the output stack looking for the last symbol and checking out what other terms may // exist on the stack too. This evaluates the output stack, looking at the previously parsed sequence // of terms that led to this conflict as a sequence of the form 'alpha OpA beta OpB', where OpA is the // previously unresolved operator conflicting with the nextTerm, and alpha and beta are potentially // empty sequences of symbols. The following code works out what the previous operator is and whether // alpha and beta are empty. At the end of this lastCandidate cannot be null, as resolve is only called // when a previous candidate symbol has been encountered. boolean alpha = false; boolean beta = false; CandidateOpSymbol lastCandidate = null; int pos = 0; for (Term nextTerm : outputStack) { boolean isOpSymbol = (nextTerm instanceof CandidateOpSymbol); if ((pos == 0) && !isOpSymbol) { beta = true; } else if ((pos == 0) && isOpSymbol) { lastCandidate = (CandidateOpSymbol) nextTerm; } else if ((pos == 1) && beta) { lastCandidate = (CandidateOpSymbol) nextTerm; } else if ((pos == 1) && !beta) { alpha = true; break; } else if (pos == 2) { alpha = true; break; } pos++; } if (lastCandidate == null) { throw new RuntimeException("'lastCandidate' is null but this should not be the case as this resolve " + "action is only called when a previous candidate symbol exists."); } // Based on the form of the symbol sequence preceding the next symbol, work out which combination // of operator fixities in uniquely possible, and resolve the candidate operators onto that combination. OpSymbol lastOp; OpSymbol nextOp; if (alpha && beta) { lastOp = checkAndResolveToFixity(lastCandidate, OpSymbol.Fixity.In); nextOp = checkAndResolveToFixity(nextCandidate, OpSymbol.Fixity.In, OpSymbol.Fixity.Post); } else if (!alpha && beta) { lastOp = checkAndResolveToFixity(lastCandidate, OpSymbol.Fixity.Pre); nextOp = checkAndResolveToFixity(nextCandidate, OpSymbol.Fixity.In, OpSymbol.Fixity.Post); } else if (alpha && !beta) { lastOp = checkAndResolveToFixity(lastCandidate, OpSymbol.Fixity.In, OpSymbol.Fixity.Post); if (lastOp.isPostfix()) { nextOp = checkAndResolveToFixity(nextCandidate, OpSymbol.Fixity.In, OpSymbol.Fixity.Post); } else { nextOp = checkAndResolveToFixity(nextCandidate, OpSymbol.Fixity.Pre); } } else { lastOp = checkAndResolveToFixity(lastCandidate, OpSymbol.Fixity.Pre); nextOp = checkAndResolveToFixity(nextCandidate, OpSymbol.Fixity.Pre); } // Compare the priority and associativities of the conflicting operators to decide whether to shift // or reduce. int comparison = lastOp.compareTo(nextOp); if (comparison < 0) { reduce.apply(); } else if (comparison > 0) { shift.apply(); } else { if (nextOp.isLeftAssociative()) { reduce.apply(); } else if (nextOp.isRightAssociative()) { shift.apply(); } else { throw new SourceCodeException("Ambiguous operator associativity. Expression requires brackets.", null, null, null, nextOp.getSourceCodePosition()); } } } } /** * An error handling action to be used when an unknown transition in the parser table is attempted. This action * creates a source code exception with the location of the error in it. */ private class ErrorAction extends Action { /** Holds a custom error description. */ private String errorMessage; /** * Creates an error action with the specified error message. * * @param errorMessage The error message. */ private ErrorAction(String errorMessage) { this.errorMessage = errorMessage; } /** * Applies a parser error action that will generate an exception with the location of the current parse term in * it. * * @throws SourceCodeException With an error location for the current parse term. */ public void apply() throws SourceCodeException { throw new SourceCodeException(errorMessage, null, null, null, nextTerm.getSourceCodePosition()); } } /** * Performs an accept action. This advances over the last symbol in the input sequence so that the parser * terminates. */ private class Accept extends Action { /** Accepts the input sequence as valid. */ public void apply() { // Advance one beyond the final position to indicate acceptance of the sentence as a valid instance of // the grammar. position++; } } /** * Implements a reduction by rule 1 of the grammar. This consumes a single state from the state stack. */ private class Rule1 extends Action { /** Defines the number of symbols on the right hand side of this rule. */ private static final int NUM_SYMBOLS_RHS = 1; /** Reduces by rule 1. */ public void apply() { for (int i = 0; i < NUM_SYMBOLS_RHS; i++) { stack.poll(); } } } /** * Implements a reduction by rule 2 of the grammar. This consumes two states from the state stack, and expects the * top two symbols on the output stack to be a terminal follow by an operator candidate that resolves to a prefix * operator. */ private class Rule2 extends Action { /** Defines the number of symbols on the right hand side of this rule. */ private static final int NUM_SYMBOLS_RHS = 2; /** * Reduces by rule 2. * * @throws SourceCodeException With an error location if the action cannot be performed because the input * sequence does not form a valid instance of the grammar. */ public void apply() throws SourceCodeException { // Consume from the state stack for the number of RHS symbols. for (int i = 0; i < NUM_SYMBOLS_RHS; i++) { stack.poll(); } // Attempt to consume a term and an operator in prefix order from the output stack. Term t = outputStack.poll(); CandidateOpSymbol candidate = (CandidateOpSymbol) outputStack.poll(); OpSymbol op = checkAndResolveToFixity(candidate, OpSymbol.Fixity.Pre); // Clone the operator symbol from the operator table before adding the unique source code position and // argument for this symbol instance. op = op.copySymbol(); op.setSourceCodePosition(candidate.getSourceCodePosition()); op.setArguments(new Term[] { t }); // Place the fully parsed, promoted operator back onto the output stack. outputStack.offer(op); } } /** * Implements a reduction by rule 3 of the grammar. This consumes two states from the state stack, and expects the * top two symbols on the output stack to be an operator candidate that resolves to a postfix operator followed by a * terminal. */ private class Rule3 extends Action { /** Defines the number of symbols on the right hand side of this rule. */ private static final int NUM_SYMBOLS_RHS = 2; /** * Reduces by rule 3. * * @throws SourceCodeException With an error location if the action cannot be performed because the input * sequence does not form a valid instance of the grammar. */ public void apply() throws SourceCodeException { // Consume from the state stack for the number of RHS symbols. for (int i = 0; i < NUM_SYMBOLS_RHS; i++) { stack.poll(); } // Attempt to consume an operator and a term in postfix order from the output stack. CandidateOpSymbol candidate = (CandidateOpSymbol) outputStack.poll(); Term t = outputStack.poll(); OpSymbol op = checkAndResolveToFixity(candidate, OpSymbol.Fixity.Post); // Clone the operator symbol from the operator table before adding the unique source code position and // argument for this symbol instance. op = op.copySymbol(); op.setSourceCodePosition(candidate.getSourceCodePosition()); op.setArguments(new Term[] { t }); outputStack.offer(op); } } /** * Implements a reduction by rule 3 of the grammar. This consumes three states from the state stack, and expects the * top three symbols on the output stack to be a terminal followed by ann operator candidate that resolves to a * infix operator followed by a terminal. */ private class Rule4 extends Action { /** Defines the number of symbols on the right hand side of this rule. */ private static final int NUM_SYMBOLS_RHS = 3; /** * Reduces by rule 4. * * @throws SourceCodeException With an error location if the action cannot be performed because the input * sequence does not form a valid instance of the grammar. */ public void apply() throws SourceCodeException { // Consume from the state stack for the number of RHS symbols. for (int i = 0; i < NUM_SYMBOLS_RHS; i++) { stack.poll(); } // Attempt to consume a term, ann operator and a term in infix order from the output stack. Term t1 = outputStack.poll(); CandidateOpSymbol candidate = (CandidateOpSymbol) outputStack.poll(); Term t2 = outputStack.poll(); OpSymbol op = checkAndResolveToFixity(candidate, OpSymbol.Fixity.In); // Clone the operator symbol from the operator table before adding the unique source code position and // argument for this symbol instance. // Note that the order of the arguments is swapped here, because they come off the stack backwards. op = op.copySymbol(); op.setSourceCodePosition(candidate.getSourceCodePosition()); op.setArguments(new Term[] { t2, t1 }); outputStack.offer(op); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy