Sindbad~EG File Manager
/**
* CS 240
* Author: Nhan Vu (and some parts by Thang Dao)
* 12.19.06 M. Beeson made constructor and parse() public
* 12.20.06 Beeson eliminated private copy of isPrimitiveCommand and made it handle final semicolon.
* 12.22.06 Beeson changed SymbolTable.IsPrimitiveCommand to Scripts.IsPrimitiveCommand
* 1.10.07 Beeson removed hard-coded list of scripts in favor of Euclid--then put them back
*/
package org.dynamicgeometry.diagrammer;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.ArrayList;
import org.dynamicgeometry.scripts.*;
public class Parser extends Object {
private Hashtable<String, ArrayList<String>> commandNameToArgumentList;
private String errorMsg;
private int errorColumn;
public Parser() {
//commandNameToArgumentList = new Hashtable<String, ArrayList<String>>();
initHashTable();
}
//For testing
Parser(Hashtable<String, ArrayList<String>> h) {
commandNameToArgumentList = h;
}
/**************************************************************************
This shall parse the line in the forms of " Label = CommandName ( Arg ,
MoreArg ) " or " CommandName ( Arg , MoreArg ) ", where Label shall begin
with exactly one upper/lowercase letter followed by 0 or more digits
CommandName shall be
Arg shall be 1 or more comma separated argument list
' ' shall be 0 or more tabs or spaces
and return null if the parse was unsuccessful, or if the argument list does
not match the CommandName looked up from the hash table; otherwise return a
Command object. If Label is missing, create Command object with null Label.
Also accepts one semicolon at the end.
**************************************************************************/
public Command parse( String line ) {
Command cmd = null;
if (line == null || line.length() == 0) {
setErrorMsg( "Parsed string is null or empty" );
return null;
}
int i = line.indexOf("=");
if (i == 0 || i == (line.length() - 1)) {
setErrorMsg( "Incorrect location of equal sign" );
return null;
}
if (i != -1) {//this command has label
String label = line.substring(0, i).trim();
if (!isValidLabel(label)) {
{ setErrorMsg("Parser encountered invalid label");
return null;
}
}
cmd = parseNoLabel(line.substring(i + 1));
if (cmd != null) {
cmd.setLabel(label);
}
}
else
cmd = parseNoLabel(line);
return cmd;
}
Command parseNoLabel(String line) {
Command cmd = null;
String parsedLine;
String x = line.trim();
if(x.charAt(x.length()-1) == ';')
parsedLine = x.substring(0,x.length()-1);
else
parsedLine = x;
int i, j, k;
int balance;
if (parsedLine.length() == 0) {
setErrorMsg( "Parsed string is null or empty" );
return null;
}
if(parsedLine.startsWith("return")) { // added by Beeson
// it's OK to have no parenthesis, but only a variable or an and-delimited list of variables can follow "return".
// No parentheses can come after "return".
int count = 0;
String[] parts = parsedLine.split(" +");
for(i=1;i<parts.length;i++)
{ if(parts[i].equals("and"))
continue;
if(!isValidLabel(parts[i]))
{ setErrorMsg("Only variables can follow return, not " + parts[i]);
return null;
}
++count;
}
String[] args = new String[count];
count = 0;
for(i=1;i<parts.length;i++)
{ if(parts[i].equals("and"))
continue;
args[count] = parts[i];
++count;
}
return new Command(null, "return", args);
}
//Step 1: Check parentheses in general
balance = 0;
for (i = 0; i < parsedLine.length(); i++) {
if (parsedLine.charAt(i) == '(')
balance++;
if (parsedLine.charAt(i) == ')')
balance--;
if (balance < 0) {
setErrorMsg ("Parentheses are not properly nested");
return null;
}
}
if (balance != 0) {
setErrorMsg ("Parentheses are not properly nested");
return null;
}
//Step 2: Find the argument list and command name
i = parsedLine.indexOf("(");
j = parsedLine.lastIndexOf(")");
if (i == -1 || j != (parsedLine.length() - 1)) {//no '(' or ')' is not at the end
setErrorMsg ("Parsed string has incorrect format");
return null;
}
String commandName = parsedLine.substring(0, i).trim();
if (!isValidCommandName(commandName)) {
setErrorMsg ("Command name has incorrect format");
return null;
}
//Parse argument list
String argumentList = parsedLine.substring(i + 1, j).trim();
i = 0;//i is starting point
j = 0;//j is ending point
k = 0;//k is number of arguments
balance = 0;//balance = number of '(' - number of ')' so far
//# of arguments cannot exceed argumentList.length()
String arguments[] = new String[argumentList.length()];
while (j < argumentList.length()) {
if (argumentList.charAt(j) == ',') {
if (balance == 0) {
arguments[k] = argumentList.substring(i, j).trim();
k++;
i = j + 1;
j = i;
}
else
j++;
}
else {
if (argumentList.charAt(j) == '(')
balance++;
else if (argumentList.charAt(j) == ')')
balance--;
j++;
}
}
//The last token
arguments[k] = argumentList.substring(i, j).trim();
k++;
String[] correctArgs = new String[k];
//Check if arguments are valid
for (i = 0; i < k; i++) {
if (!isValidArgument(arguments[i])) {
setErrorMsg( "One argument has incorrect format" );
errorColumn = i + 1;
return null;
}
else correctArgs[i] = arguments[i];
}
//* After commandNameToArgumentList is properly hard-coded, un-comment
// this block
if( !Scripts.isPrimitiveCommand( commandName ) ) {
if (commandNameToArgumentList.get(commandName) == null) {
setErrorMsg("Command name not found in Hashtable");
return null;
}
if (k != commandNameToArgumentList.get(commandName).size()) {
setErrorMsg("Number of arguments in argument list is incorrect");
return null;
}
//*/
}
//At this point, everything is OK
//for( int z=0; z<correctArgs.length; z++ ) System.out.println( " " + correctArgs[z] );
cmd = new Command ( null, commandName, correctArgs );
return cmd;
}
private boolean isValidCommandName( String cmdName ) {
return true;
}
/**
* Added by Thang Dao (with Nhan Vu's permission)
* Check validity of label. label is valid if it starts with letter followed by 0 or more digits
* Return true if format of label is valid, otherwise return false.
* @param label check validity of this label
* @return true label is in valid format
* false label is in invalid format
*/
private boolean isValidLabel( String label ) {
/* first character is not letter */
if( !Character.isLetter( label.charAt(0) )) {
setErrorMsg( "First character of Label is not an alphabet" );
return false;
}
/* if any character from position 1+ is not digit, return false */
for( int i = 1; i < label.length(); i++ )
if( !Character.isDigit( label.charAt(i) ) ) {
setErrorMsg( "Label contains character different than digits after the 1st alphabet" );
return false;
}
return true;
}
/**
* Check the validity of argument
* The format of argument is the same as that of label, so this method simply
* returns what is returned by isValidLabel
*/
private boolean isValidArgument( String arg ) {
return isValidLabel(arg);
}
/**
* This method initializes the Hashtable commandNameToArgumentList
* It hard-codes the Hashtable using commands defined in class Scripts
*/
private void initHashTable() {
commandNameToArgumentList = new Hashtable<String, ArrayList<String>>();
//for(String u : Scripts.Euclid)
// addOneCommand(u);
addOneCommand("EquilateralTriangle");
addOneCommand("CopySegment");
addOneCommand("CopySegmentOntoLine");
addOneCommand("CopySegmentOntoLine2");
addOneCommand("BisectAngle");
addOneCommand("BisectAngle2");
addOneCommand("BisectSegment");
addOneCommand("BisectSegment2");
addOneCommand("ErectPerpendicular");
addOneCommand("DropPerpendicular");
addOneCommand("TriangleFromSegments");
addOneCommand("CircleFromCenterAndRadius");
//addOneCommand("CopyTriangle");
addOneCommand("CopyAngleToSegment");
addOneCommand("CopyAngleToSegment2");
addOneCommand("ParallelThroughPoint");
addOneCommand("ParallelogramEqualToTriangle");
//addOneCommand("ParallelogramOfGivenWidthEqualToTriangle");
//addOneCommand("Parallelogram2OfGivenWidthEqualToTriangle");
//addOneCommand("ParallelogramEqualToConvexPolygon");
//addOneCommand("SquareOnGivenSide");
//addOneCommand("SquareOnGivenSide2");
//addOneCommand("CutSegmentInGoldenSection");
//addOneCommand("SquareEqualToConvexPolygon");
//addOneCommand("FindCenterOfCircle");
//addOneCommand("TangentToCircle");
//addOneCommand("CircleFromArc");
//addOneCommand("BisectCircumference");
//addOneCommand("GivenSegmentAndAngleConstructCircle");
//addOneCommand("GivenCircleAndAngleConstructArc");
//addOneCommand("FitSegmentInCircle");
//addOneCommand("TriangleInCircle");
//addOneCommand("TriangleAroundCircle");
//addOneCommand("CircleInTriangle");
//addOneCommand("CircleAroundTriangle");
//addOneCommand("SquareInCircle");
//addOneCommand("SquareAroundCircle");
//addOneCommand("CircleInSquare");
//addOneCommand("CircleAroundSquare");
//addOneCommand("ConstructGoldenTriangle");
//addOneCommand("PentagonInCircle");
//addOneCommand("PentagonAroundCircle");
//addOneCommand("CircleInPentagon");
//addOneCommand("CircleAroundPentagon");
//addOneCommand("HexagonInCircle");
//addOneCommand("FifteenGonInCircle");
//addOneCommand("DivideSegmentIntoParts");
//addOneCommand("SegmentOnLine");
//addOneCommand("ThirdProportional");
//addOneCommand("FourthProportional");
//addOneCommand("MeanProportional");
//addOneCommand("SimilarConvexPolygonWithGivenSide");
}
private void addOneCommand (String commandName) {
try {
ArrayList<String> list = new ArrayList<String>();
String[] parameterNames = Scripts.getParameterNames( "org.dynamicgeometry.scripts." + commandName);
for (int i = 0; i < parameterNames.length; i++)
list.add(parameterNames[i]);
commandNameToArgumentList.put(commandName, list);
}
catch (Exception e) {
e.printStackTrace();
}
}
private void setErrorMsg( String _errorMsg ) { errorMsg = _errorMsg; }
private void setErrorColumn( int _errorColumn ) { errorColumn = _errorColumn; }
public String getErrorMsg() { return errorMsg; }
public int getErrorColumn() { return errorColumn; }
}
Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists