423 lines
8.4 KiB
Java
423 lines
8.4 KiB
Java
/* :folding=explicit:collapseFolds=1: */
|
|
|
|
/*
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2004 Slava Pestov.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
* this list of conditions and the following disclaimer in the documentation
|
|
* and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
|
|
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
|
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package factor;
|
|
|
|
import factor.math.NumberParser;
|
|
import java.io.*;
|
|
|
|
/**
|
|
* Splits an input stream into words.
|
|
*/
|
|
public class FactorScanner
|
|
{
|
|
/**
|
|
* Special object returned on EOF.
|
|
*/
|
|
public static final Object EOF = new Object();
|
|
|
|
/**
|
|
* Special object returned on EOL.
|
|
*/
|
|
public static final Object EOL = new Object();
|
|
|
|
private String filename;
|
|
private BufferedReader in;
|
|
|
|
/**
|
|
* Line number being parsed, for error reporting.
|
|
*/
|
|
private int lineNo = 0;
|
|
|
|
/**
|
|
* The line currently being parsed.
|
|
*/
|
|
private String line;
|
|
|
|
/**
|
|
* Position within line being parsed.
|
|
*/
|
|
private int position = 0;
|
|
|
|
/**
|
|
* Position of last word read.
|
|
*/
|
|
private int lastPosition = 0;
|
|
|
|
private ReadTable readtable;
|
|
|
|
/**
|
|
* The current word.
|
|
*/
|
|
private StringBuffer buf;
|
|
|
|
//{{{ FactorScanner constructor
|
|
public FactorScanner(String filename, BufferedReader in)
|
|
{
|
|
this.filename = filename;
|
|
this.in = in;
|
|
buf = new StringBuffer();
|
|
setReadTable(ReadTable.DEFAULT_READTABLE);
|
|
} //}}}
|
|
|
|
//{{{ getReadTable() method
|
|
public ReadTable getReadTable()
|
|
{
|
|
return readtable;
|
|
} //}}}
|
|
|
|
//{{{ setReadTable() method
|
|
public void setReadTable(ReadTable readtable)
|
|
{
|
|
this.readtable = readtable;
|
|
} //}}}
|
|
|
|
//{{{ getLine() method
|
|
public String getLine()
|
|
{
|
|
return line;
|
|
} //}}}
|
|
|
|
//{{{ getLineNumber() method
|
|
public int getLineNumber()
|
|
{
|
|
return lineNo;
|
|
} //}}}
|
|
|
|
//{{{ getColumnNumber() method
|
|
public int getColumnNumber()
|
|
{
|
|
return position;
|
|
} //}}}
|
|
|
|
//{{{ getLastColumnNumber() method
|
|
public int getLastColumnNumber()
|
|
{
|
|
return lastPosition;
|
|
} //}}}
|
|
|
|
//{{{ getFileName() method
|
|
public String getFileName()
|
|
{
|
|
return filename;
|
|
} //}}}
|
|
|
|
//{{{ nextLine() method
|
|
public void nextLine() throws IOException
|
|
{
|
|
lineNo++;
|
|
line = in.readLine();
|
|
position = 0;
|
|
lastPosition = 0;
|
|
if(line != null && line.length() == 0)
|
|
nextLine();
|
|
} //}}}
|
|
|
|
//{{{ isEOL() method
|
|
private boolean isEOL()
|
|
{
|
|
return position >= line.length();
|
|
} //}}}
|
|
|
|
//{{{ skipWhitespace() method
|
|
/**
|
|
* The Factor parser is so much nicer in Factor than Java!
|
|
*/
|
|
public void skipWhitespace() throws FactorParseException
|
|
{
|
|
for(;;)
|
|
{
|
|
if(isEOL())
|
|
return;
|
|
|
|
char ch = line.charAt(position++);
|
|
|
|
int type = readtable.getCharacterType(ch);
|
|
|
|
switch(type)
|
|
{
|
|
case ReadTable.INVALID:
|
|
error("Invalid character in input: " + ch);
|
|
break;
|
|
case ReadTable.WHITESPACE:
|
|
break;
|
|
default:
|
|
position--;
|
|
return;
|
|
}
|
|
}
|
|
} //}}}
|
|
|
|
//{{{ next() method
|
|
/**
|
|
* Read a word name. Note that no escaping of characters is done.
|
|
*
|
|
* @param readNumbers If true, will return either a Number or a
|
|
* String. Otherwise, only Strings are returned.
|
|
* @param start If true, dispatches will be handled by their parsing
|
|
* word, otherwise dispatches are ignored.
|
|
* @param base The number base -- not that if this is not equal to
|
|
* 10, floats cannot be read
|
|
*/
|
|
public Object next(
|
|
boolean readNumbers,
|
|
boolean start,
|
|
int base)
|
|
throws IOException, FactorParseException
|
|
{
|
|
if(line == null)
|
|
return EOF;
|
|
if(position == line.length())
|
|
return EOL;
|
|
|
|
lastPosition = position;
|
|
|
|
for(;;)
|
|
{
|
|
if(position >= line.length())
|
|
{
|
|
// EOL
|
|
if(buf.length() != 0)
|
|
return word(readNumbers,base);
|
|
else
|
|
return EOL;
|
|
}
|
|
|
|
char ch = line.charAt(position++);
|
|
|
|
int type = readtable.getCharacterType(ch);
|
|
|
|
switch(type)
|
|
{
|
|
case ReadTable.INVALID:
|
|
error("Invalid character in input: " + ch);
|
|
break;
|
|
case ReadTable.WHITESPACE:
|
|
if(buf.length() != 0)
|
|
return word(readNumbers,base);
|
|
lastPosition = position;
|
|
break;
|
|
case ReadTable.DISPATCH:
|
|
// note that s" is read as the word s", no
|
|
// dispatch on "
|
|
if(buf.length() == 0 && start)
|
|
{
|
|
buf.append(ch);
|
|
return word(readNumbers,base);
|
|
}
|
|
case ReadTable.CONSTITUENT:
|
|
case ReadTable.SINGLE_ESCAPE:
|
|
buf.append(ch);
|
|
break;
|
|
}
|
|
}
|
|
} //}}}
|
|
|
|
//{{{ nextNonEOL() method
|
|
public Object nextNonEOL(
|
|
boolean readNumbers,
|
|
boolean start,
|
|
int base)
|
|
throws IOException, FactorParseException
|
|
{
|
|
Object next = next(readNumbers,start,base);
|
|
if(next == EOL)
|
|
error("Unexpected EOL");
|
|
if(next == EOF)
|
|
error("Unexpected EOF");
|
|
return next;
|
|
} //}}}
|
|
|
|
//{{{ readUntil() method
|
|
/**
|
|
* Characters are escaped.
|
|
*/
|
|
public String readUntil(char start, char end, boolean escapesAllowed)
|
|
throws IOException, FactorParseException
|
|
{
|
|
buf.setLength(0);
|
|
|
|
for(;;)
|
|
{
|
|
if(isEOL())
|
|
{
|
|
error("Expected " + end + " before EOL");
|
|
break;
|
|
}
|
|
|
|
if(line == null)
|
|
{
|
|
error("Expected " + end + " before EOF");
|
|
break;
|
|
}
|
|
|
|
char ch = line.charAt(position++);
|
|
|
|
if(ch == end)
|
|
break;
|
|
|
|
int type = readtable.getCharacterType(ch);
|
|
|
|
if(escapesAllowed && type == ReadTable.SINGLE_ESCAPE)
|
|
buf.append(escape());
|
|
else
|
|
buf.append(ch);
|
|
}
|
|
|
|
String returnValue = buf.toString();
|
|
buf.setLength(0);
|
|
return returnValue;
|
|
} //}}}
|
|
|
|
//{{{ readUntilEOL() method
|
|
public String readUntilEOL() throws IOException
|
|
{
|
|
buf.setLength(0);
|
|
|
|
while(position < line.length())
|
|
buf.append(line.charAt(position++));
|
|
|
|
String returnValue = buf.toString();
|
|
buf.setLength(0);
|
|
return returnValue;
|
|
} //}}}
|
|
|
|
//{{{ readNonEOF() method
|
|
public char readNonEOF() throws FactorParseException, IOException
|
|
{
|
|
if(isEOL())
|
|
{
|
|
error("Unexpected EOL");
|
|
return '\0';
|
|
}
|
|
if(line == null)
|
|
{
|
|
error("Unexpected EOF");
|
|
return '\0';
|
|
}
|
|
|
|
return line.charAt(position++);
|
|
} //}}}
|
|
|
|
//{{{ readNonEOFEscaped() method
|
|
public char readNonEOFEscaped() throws FactorParseException, IOException
|
|
{
|
|
char next = readNonEOF();
|
|
if(readtable.getCharacterType(next) == ReadTable.SINGLE_ESCAPE)
|
|
return escape();
|
|
else
|
|
return next;
|
|
} //}}}
|
|
|
|
//{{{ atEndOfWord() method
|
|
public boolean atEndOfWord() throws IOException
|
|
{
|
|
if(isEOL())
|
|
return true;
|
|
if(line == null)
|
|
return true;
|
|
char next = line.charAt(position);
|
|
int type = readtable.getCharacterType(next);
|
|
return type == ReadTable.WHITESPACE;
|
|
} //}}}
|
|
|
|
//{{{ escape() method
|
|
private char escape() throws FactorParseException
|
|
{
|
|
char ch = line.charAt(position++);
|
|
|
|
switch(ch)
|
|
{
|
|
case 'e':
|
|
// ASCII ESC
|
|
return (char)27;
|
|
case 'n':
|
|
return '\n';
|
|
case 'r':
|
|
return '\r';
|
|
case 't':
|
|
return '\t';
|
|
case '\\':
|
|
return '\\';
|
|
case '"':
|
|
return '"';
|
|
case 's':
|
|
case ' ':
|
|
return ' ';
|
|
case '0':
|
|
return '\0';
|
|
case 'u':
|
|
if(line.length() - position < 4)
|
|
{
|
|
error("Unexpected EOL");
|
|
return '\0';
|
|
}
|
|
|
|
String hex = line.substring(position,position + 4);
|
|
|
|
position += 4;
|
|
|
|
try
|
|
{
|
|
return (char)Integer.parseInt(hex,16);
|
|
}
|
|
catch(NumberFormatException e)
|
|
{
|
|
error("Invalid \\u escape: " + hex);
|
|
}
|
|
return '\0';
|
|
default:
|
|
error("Unknown escape: " + ch);
|
|
return '\0';
|
|
}
|
|
} //}}}
|
|
|
|
//{{{ word() method
|
|
private Object word(boolean readNumbers, int base)
|
|
{
|
|
String name = buf.toString();
|
|
buf.setLength(0);
|
|
if(readNumbers)
|
|
{
|
|
Number n = NumberParser.parseNumber(name, base);
|
|
if(n != null)
|
|
return n;
|
|
}
|
|
|
|
return name;
|
|
} //}}}
|
|
|
|
//{{{ error() method
|
|
public void error(String msg) throws FactorParseException
|
|
{
|
|
throw new FactorParseException(
|
|
filename,lineNo,line,position - 1,msg);
|
|
} //}}}
|
|
}
|