001: /*
002: * CommandGrammar.java
003: *
004: * Copyright (C) 2007 Ferran Busquets
005: *
006: * This program is free software: you can redistribute it and/or modify
007: * it under the terms of the GNU General Public License as published by
008: * the Free Software Foundation, either version 3 of the License, or
009: * any later version.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * along with this program. If not, see <http://www.gnu.org/licenses/>.
018: *
019: */
020: package org.naturalcli;
021:
022: import java.util.LinkedList;
023: import java.util.List;
024:
025: /**
026: * Implements a class with a syntax definition and parser.
027: *
028: * @author Ferran Busquets
029: *
030: */
031: public class Syntax {
032:
033: /** Syntax definition */
034: private String definition;
035:
036: /** List of tokens defining the grammar */
037: private List<Token> grammar;
038:
039: /** Number of parameters */
040: private int paramCount;
041:
042: /**
043: * Constructor for the Syntax class
044: *
045: * @param definition the syntax definition
046: * @throws InvalidSyntaxException
047: */
048: public Syntax(String definition) throws InvalidSyntaxException {
049: this .setDefinition(definition);
050: }
051:
052: /**
053: * Gets the syntax definition
054: *
055: * @return the definition
056: */
057: public String getDefinition() {
058: return definition;
059: }
060:
061: /**
062: * Sets the definition for the syntax
063: *
064: * @param definition the definition to set
065: * @throws InvalidSyntaxException
066: */
067: private void setDefinition(String definition)
068: throws InvalidSyntaxException {
069: if (definition == null || definition.length() == 0)
070: throw new IllegalArgumentException(
071: "The definition cannot be null or empty string.");
072: this .definition = definition;
073: this .compile();
074: }
075:
076: /**
077: * Creates the grammar for the command
078: * @throws InvalidSyntaxException
079: *
080: */
081: private void compile() throws InvalidSyntaxException {
082: grammar = new LinkedList<Token>();
083: String[] tokens = definition.split(" ");
084: Token last_t = null;
085: for (int i = 0; i < tokens.length; i++) {
086: String s = tokens[i];
087: // Create the token
088: Token t;
089: try {
090: t = new Token(s);
091: } catch (InvalidTokenException e) {
092: throw new InvalidSyntaxException("Bad token.", e);
093: }
094: // Validating the variable argument token
095: if (t.isVarArgs()) {
096: if (last_t == null || i != tokens.length - 1)
097: throw new InvalidSyntaxException(
098: "Variable arguments token only allowed at the end.");
099: if (!last_t.isMandatoryParameter())
100: throw new InvalidSyntaxException(
101: "Variable arguments have to follow a mandatory parameter.");
102: }
103: // Validating optional parameters
104: if (t.isParameter()
105: && last_t != null
106: && last_t.isOptional()
107: && t.getParameterTypeName().equals(
108: last_t.getParameterTypeName())) {
109: throw new InvalidSyntaxException(
110: "An optional parameter cannot be followed by a parameter of the same type.");
111: }
112: // Add the token
113: grammar.add(t);
114: //
115: if (t.isParameter())
116: paramCount++;
117: //
118: last_t = t;
119: }
120: }
121:
122: /**
123: * Parse the tokens to see if match with the syntax
124: *
125: * @param candidates the candidate tokens to match
126: * @param first the first item in the tokens list
127: * @param pv the parameter validator
128: * @return <code>null</code> if the candidate does not match,
129: * or a <code>ParseData</code> object
130: * @throws UnknownParameterType
131: * @see ParseResult
132: */
133: public ParseResult parse(String[] candidates, int first,
134: ParameterValidator pv) throws UnknownParameterType {
135: if (candidates == null)
136: throw new IllegalArgumentException(
137: "The string array to parse cannot be null.");
138: if (pv == null)
139: throw new IllegalArgumentException(
140: "Parameter validator cannot be null.");
141: if (first < 0)
142: throw new IllegalArgumentException(
143: "First token index have to be 0 or greater.");
144: List<Object> ParamValues = new LinkedList<Object>();
145: boolean[] tokenGiven = new boolean[this .grammar.size()];
146: int ip = 0; // index for paramValues
147: int it = 0; // index for tokensGiven
148: int ig = 0; // index for the grammar
149: int ic = first; // index for the candidates
150: boolean varargs = false;
151: /*
152: * increment
153: * match opt param ip it ig ic
154: * 0 0 ? - - - - return null
155: * 0 1 0 0 1 1 0
156: * 0 1 1 1 1 1 0
157: * 1 ? 0 0 1 1 1
158: * 1 ? 1 1 1 1 1
159: *
160: */
161: while (ig < grammar.size() && ic < candidates.length) {
162: Token tgrammar = grammar.get(ig);
163: // check if there are varargs
164: varargs = tgrammar.isVarArgs();
165: if (varargs)
166: break;
167: boolean match = tgrammar.matches(candidates[ic], pv);
168: boolean opt = tgrammar.isOptional();
169: boolean param = tgrammar.isParameter();
170: // Validate the token
171: if (!match && !opt)
172: return null;
173: // Increment ip and add value to paramValues
174: if (param) {
175: if (opt && !match)
176: ParamValues.add(null);
177: else
178: ParamValues.add(pv.getParameterType(
179: tgrammar.getParameterTypeName())
180: .convertParameterValue(candidates[ic]));
181: ip++;
182: }
183: // Increment ic if matches
184: if (match)
185: ic++;
186: // Increment it and ig and add value to tokenGiven
187: tokenGiven[it++] = match;
188: ig++;
189: }
190: // Go for the variable arguments
191: if (varargs) {
192: tokenGiven[it] = true;
193: Token token = grammar.get(ig - 1);
194: ig++;
195: // Validate
196: if (token.isOptional())
197: throw new RuntimeException(
198: "Internal error: Parameter for variable arguments cannot be optional.");
199: // Go for values
200: while (ic < candidates.length) {
201: if (!token.matches(candidates[ic], pv))
202: return null;
203: ParamValues.add(pv.getParameterType(
204: token.getParameterTypeName())
205: .convertParameterValue(candidates[ic]));
206: ic++;
207: }
208: }
209: // Validate ParamValuesAux
210: if (!varargs && ParamValues.size() > paramCount)
211: throw new RuntimeException(
212: "Internal error: Invalid parameter count.");
213: // Determine if the bucle is finished ok
214: if (ic == candidates.length && ig == grammar.size())
215: return new ParseResult(ParamValues.toArray(), tokenGiven);
216: if (ic == candidates.length && ig == grammar.size() - 1
217: && grammar.get(grammar.size() - 1).isVarArgs())
218: return new ParseResult(ParamValues.toArray(), tokenGiven);
219: // Not enough values o matching error
220: return null;
221: }
222:
223: }
|