/* Copyright (C) 2006 Christian Schneider
*
* This file is part of Nomad.
*
* Nomad is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Nomad is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Nomad; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/*
* Created on Dec 19, 2006
*/
package net.sf.nmedit.jpatch.clavia.nordmodular.parser;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
public class PParser implements ErrorHandler
{
public static final String SHEADER = "header";
public static final String SMODULEDUMP = "moduledump";
public static final String SCURRENTNOTEDUMP = "currentnotedump";
public static final String SCABLEDUMP = "cabledump";
public static final String SPARAMETERDUMP = "parameterdump";
public static final String SKNOBMAPDUMP = "knobmapdump";
public static final String SCUSTOMDUMP = "customdump";
public static final String SNAMEDUMP = "namedump";
public static final String SNOTES = "notes";
public static final String SKEYBOARDASSIGNMENT = "keyboardassignment";
public static final String SMORPHMAPDUMP = "morphmapdump";
public static final String SCTRLMAPDUMP = "ctrlmapdump";
public static final int IHEADER = 0;
public static final int IMODULEDUMP = 1;
public static final int ICURRENTNOTEDUMP = 2;
public static final int ICABLEDUMP = 3;
public static final int IPARAMETERDUMP = 4;
public static final int IKNOBMAPDUMP = 5;
public static final int ICUSTOMDUMP = 6;
public static final int INAMEDUMP = 7;
public static final int INOTES = 8;
public static final int IKEYBOARDASSIGNMENT = 9;
public static final int IMORPHMAPDUMP = 10;
public static final int ICTRLMAPDUMP = 11;
private PScanner scanner;
private int token = -1;
private static final Map<String,Integer> sectionMap = new HashMap<String,Integer>();
static
{
sectionMap.put(SHEADER, IHEADER);
sectionMap.put(SMODULEDUMP, IMODULEDUMP);
sectionMap.put(SCURRENTNOTEDUMP, ICURRENTNOTEDUMP);
sectionMap.put(SCABLEDUMP, ICABLEDUMP);
sectionMap.put(SPARAMETERDUMP, IPARAMETERDUMP);
sectionMap.put(SKNOBMAPDUMP, IKNOBMAPDUMP);
sectionMap.put(SCUSTOMDUMP, ICUSTOMDUMP);
sectionMap.put(SNAMEDUMP, INAMEDUMP);
sectionMap.put(SNOTES, INOTES);
sectionMap.put(SKEYBOARDASSIGNMENT, IKEYBOARDASSIGNMENT);
sectionMap.put(SMORPHMAPDUMP, IMORPHMAPDUMP);
sectionMap.put(SCTRLMAPDUMP, ICTRLMAPDUMP);
}
private int[] allnumbers = new int[10];
private int numbers = 0;
private PContentHandler handler = null;
private ErrorHandler errorHandler;
public PParser(ErrorHandler errorHandler)
{
this.errorHandler = errorHandler;
scanner = new PScanner();
}
public void setContentHandler(PContentHandler handler)
{
this.handler = handler;
}
public PContentHandler getContentHandler()
{
return handler;
}
public PParser(InputStream stream, ErrorHandler errorHandler)
{
this(errorHandler);
setSource(stream);
}
public PParser(Reader reader,ErrorHandler errorHandler)
{
this(errorHandler);
setSource(reader);
}
public void setSource(InputStream source)
{
scanner.setSource(source);
reset();
}
public void setSource(Reader source)
{
scanner.setSource(source);
reset();
}
private void reset()
{
numbers = 0;
token = -1;
}
public void warning( ParseException e ) throws ParseException
{
emitwarning(e.getMessage());
}
public void error( ParseException e ) throws ParseException
{
emiterror(e.getMessage());
}
public void fatal( ParseException e ) throws ParseException
{
emitfatal(e.getMessage());
}
private String errormsg(int line, int column, String weight, String message)
{
return weight+"@"+line+":"+column + (message != null ? ": "+message : "");
}
private void emitwarning(int line, int column, String message) throws ParseException
{
errorHandler.warning(new ParseException(errormsg(line, column, "warning",message)));
}
private void emiterror(int line, int column, String message) throws ParseException
{
ParseException e = new ParseException(errormsg(line, column, "error",message));
errorHandler.error(e);
}
private void emitfatal(int line, int column, String message) throws ParseException
{
ParseException e = new ParseException(errormsg(line, column, "fatal",message));
errorHandler.fatal(e);
throw e; // fallback
}
public void emitwarning(String message) throws ParseException
{
emitwarning(getLineNumber(), getColumn(), message);
}
public void emiterror(String message) throws ParseException
{
emiterror(getLineNumber(), getColumn(), message);
}
public void emitfatal(String message) throws ParseException
{
emitfatal(getLineNumber(), getColumn(), message);
}
public int getPosition()
{
return scanner.getPosition();
}
public int getColumn()
{
return scanner.getColumn();
}
public int getLineNumber()
{
return scanner.getLineNumber();
}
public void parse() throws ParseException
{
try
{
handler.beginDocument();
sections();
handler.endDocument();
}
catch (ParseException pe)
{
throw pe;
}
catch (Throwable t)
{
throw new ParseException(errormsg(getLineNumber(), getColumn(), "error", null), t);
}
}
private void sections() throws IOException, ParseException
{
int sectionid;
int position;
boolean firstsection = true;
do
{
position = getPosition();
if (firstsection)
{
firstsection = false;
skipws();
if (PScanner.BROPEN == next())
{
take();
skipws();
sectionid = sectionname(-1);
}
else
{
emiterror("expected '[' (<begin-section Header >), found "+found()) ;
emitwarning("assuming Header section");
sectionid = IHEADER;
}
}
else
{
if (PScanner.BROPEN != inclusive(PScanner.BROPEN))
{
if (next()==PScanner.EOF)
break;
emiterror("expected '[' (<begin-section>), found "+found()) ;
}
skipws();
sectionid = sectionname(-1);
}
firstsection = false;
if (sectionid>=0)
{
// <NAME>
if (PScanner.BRCLOSE != wsinclusive(PScanner.BRCLOSE))
emiterror("expected ']' (<begin-section>), found "+found()) ;
;
if (section(sectionid))
{
if (PScanner.BROPEN != inclusive(PScanner.BROPEN))
emiterror("expected '[' (<end-section>), found "+found()) ;
;
if (PScanner.SLASH != inclusive(PScanner.SLASH))
emiterror("expected '/' (<end-section>), found "+found()) ;
;
skipws();
sectionname(sectionid);
if (PScanner.BRCLOSE != wsinclusive(PScanner.BRCLOSE))
emiterror("expected ']' (<end-section>), found "+found()) ;
;
}
}
if (position == getPosition())
throw new ParseException("invalid state, found "+found());
} while (next()!=-1);
}
private int sectionname(int sectionid) throws IOException, ParseException
{
int tclass = next();
if (tclass != PScanner.ANY)
{
String sname = sectionNameForId(sectionid);
if (sname!=null)
emiterror("expected <section-name> '"+sname+"', found "+found());
else
emiterror("expected <section-name>, found "+found());
return -1;
}
take();
Integer sid = sectionMap.get(scanner.getString().toLowerCase());
if (sid == null || (sectionid>= 0 && sid.intValue()!=sectionid))
{
String sname = sectionNameForId(sectionid);
if (sname!=null)
emiterror("invalid <section-name> '"+scanner.getString()+"' expected '"+sname+"'");
else
emiterror("invalid <section-name> '"+scanner.getString()+"'");
return -1;
}
else
{
return sid.intValue();
}
}
private String sectionNameForId(int id)
{
for (String key : sectionMap.keySet())
if (sectionMap.get(key)==id)
return key;
return null;
}
private boolean section(int sectionid) throws IOException, ParseException
{
switch (sectionid)
{
case IHEADER:
header();
break;
case IMODULEDUMP:
if (!isEmptySection(sectionid))
moduleDump();
break;
case ICURRENTNOTEDUMP:
if (!isEmptySection(sectionid))
currentNoteDump();
break;
case ICABLEDUMP:
if (!isEmptySection(sectionid))
cableDump();
break;
case IPARAMETERDUMP:
if (!isEmptySection(sectionid))
parameterDump();
break;
case IKNOBMAPDUMP:
if (!isEmptySection(sectionid))
knobMapDump();
break;
case ICUSTOMDUMP:
if (!isEmptySection(sectionid))
customDump();
break;
case INAMEDUMP:
if (!isEmptySection(sectionid))
nameDump();
break;
case IKEYBOARDASSIGNMENT:
if (!isEmptySection(sectionid))
kbAssignment();
break;
case IMORPHMAPDUMP:
if (!isEmptySection(sectionid))
morphMapDump();
break;
case ICTRLMAPDUMP:
if (!isEmptySection(sectionid))
ctrlMapDump();
break;
case INOTES:
notesSection();
return false;
}
return true;
}
private boolean isEmptySection(int sectionid) throws IOException, ParseException
{
skipws();
if (next() == PScanner.BROPEN)
{
emitwarning("section contains no data "+sectionNameForId(sectionid));
return true;
}
return false;
}
private void header() throws IOException, ParseException
{
handler.beginSection(IHEADER, -1);
loop:while (true)
{
skipws();
switch (next())
{
case PScanner.NUMBER:
parseNumbers(PContentHandler.HEADER_RSIZE, -1);
if (numbers != PContentHandler.HEADER_RSIZE)
emiterror("erroneous numbers("+numbers+") in header");
else
handler.header(allnumbers);
break;
case PScanner.EOF:
emitfatal("unexpected end of file");
break loop;
case PScanner.BROPEN:
break loop;
default:
try
{
testVersionKey = true;
keyValuePair();
}
finally
{
testVersionKey = false;
}
break;
}
}
handler.endSection(IHEADER);
}
private boolean testVersionKey = false;
private void keyValuePair() throws IOException, ParseException
{
String key = scanner.getString();
take();
if (wsinclusive(PScanner.EQ)!=PScanner.EQ)
{
emiterror("expected '=', found "+found());
throw new RuntimeException();
}
else
{
StringBuilder value = getCachedBuilder();
loop:while(true)
{
switch (next())
{
case PScanner.NEWLINEWS:
break loop;
case PScanner.EOF:
break loop;
case PScanner.NUMBER:
value.append(Integer.toString(scanner.getNumber()));
break;
default:
value.append(scanner.getString());
break;
}
take();
}
String valueString = value.toString();
if (key != null && "version".contains(key.toLowerCase()))
{
if (valueString.contains("2.10"))
emiterror("unsupported patch format:\""+valueString+"\"");
}
handler.header(key, valueString);
}
}
private void incompleteRecord(int section, int expectedSize) throws ParseException
{
StringBuilder sb = getCachedBuilder();
sb.append("incomplete record in ");
sb.append(sectionNameForId(section));
sb.append(" [");
for (int i=0;i<expectedSize;i++)
{
if (i<numbers)
sb.append(allnumbers[i]);
else
sb.append("?");
sb.append(",");
}
if (expectedSize>0)
sb.replace(sb.length()-1, sb.length(),"]");
else
sb.append("]");
emitwarning(sb.toString());
}
private void ctrlMapDump() throws IOException, ParseException
{
boolean content = false;
while (true)
{
parseNumbers(PContentHandler.CTRLMAPDUMP_RSIZE, -1);
if (numbers != PContentHandler.CTRLMAPDUMP_RSIZE)
break;
if (!content)
{
content = true;
handler.beginSection(ICTRLMAPDUMP, -1);
}
handler.ctrlMapDump(allnumbers);
}
if (numbers>0)
incompleteRecord(ICTRLMAPDUMP, PContentHandler.CTRLMAPDUMP_RSIZE);
if (content)
handler.endSection(ICTRLMAPDUMP);
}
int xcount=0;
private void moduleDump() throws IOException, ParseException
{
int va = voiceAreaId();
if (va<0)
return;
handler.beginSection(IMODULEDUMP, va);
while (true)
{
parseNumbers(PContentHandler.MODULEDUMP_RSIZE, -1);
if (numbers == PContentHandler.MODULEDUMP_RSIZE)
{
handler.moduleDump(allnumbers);
}
else break;
}
if (numbers>0)
incompleteRecord(IMODULEDUMP, PContentHandler.MODULEDUMP_RSIZE);
handler.endSection(IMODULEDUMP);
}
private void morphMapDump() throws IOException, ParseException
{
handler.beginSection(IMORPHMAPDUMP, -1);
parseNumbers(PContentHandler.MORPHMAPDUMP_PROLOG_RSIZE, -1);
if (numbers == PContentHandler.MORPHMAPDUMP_PROLOG_RSIZE)
{
handler.morphMapDumpProlog(allnumbers);
while (true)
{
parseNumbers(PContentHandler.MORPHMAPDUMP_RSIZE, -1);
if (numbers != PContentHandler.MORPHMAPDUMP_RSIZE)
break;
handler.morphMapDump(allnumbers);
}
if (numbers>0)
incompleteRecord(IMORPHMAPDUMP, PContentHandler.MORPHMAPDUMP_RSIZE);
}
else
{
incompleteRecord(IMORPHMAPDUMP, PContentHandler.MORPHMAPDUMP_PROLOG_RSIZE);
}
handler.endSection(IMORPHMAPDUMP);
}
private void kbAssignment() throws IOException, ParseException
{
handler.beginSection(IKEYBOARDASSIGNMENT, -1);
while (true)
{
parseNumbers(PContentHandler.KEYBOARDASSIGNMENT_RSIZE, -1);
if (numbers != PContentHandler.KEYBOARDASSIGNMENT_RSIZE)
break;
handler.keyboardAssignment(allnumbers);
}
if (numbers>0)
incompleteRecord(IKEYBOARDASSIGNMENT, PContentHandler.KEYBOARDASSIGNMENT_RSIZE);
handler.endSection(IKEYBOARDASSIGNMENT);
}
private void customDump() throws IOException, ParseException
{
int va = voiceAreaId();
if (va<0)
return;
handler.beginSection(ICUSTOMDUMP, va);
while (true)
{
parseNumbers(PContentHandler.CUSTOMDUMP_RSIZE,
PContentHandler.CUSTOMDUMP_RSIZE-1);
if (numbers < PContentHandler.CUSTOMDUMP_RSIZE
|| numbers != (PContentHandler.CUSTOMDUMP_RSIZE
+allnumbers[PContentHandler.CUSTOMDUMP_RSIZE-1]) )
break;
handler.customDump(allnumbers);
}
if (numbers>0)
incompleteRecord(ICUSTOMDUMP, PContentHandler.CUSTOMDUMP_RSIZE);
handler.endSection(ICUSTOMDUMP);
}
private void knobMapDump() throws IOException, ParseException
{
handler.beginSection(IKNOBMAPDUMP, -1);
while (true)
{
parseNumbers(PContentHandler.KNOBMAPDUMP_RSIZE, -1);
if (numbers != PContentHandler.KNOBMAPDUMP_RSIZE)
break;
handler.knobMapDump(allnumbers);
}
if (numbers>0)
incompleteRecord(IKNOBMAPDUMP, PContentHandler.KNOBMAPDUMP_RSIZE);
handler.endSection(IKNOBMAPDUMP);
}
private void parameterDump() throws IOException, ParseException
{
int va = voiceAreaId();
if (va<0)
return;
handler.beginSection(IPARAMETERDUMP, va);
while (true)
{
parseNumbers(PContentHandler.PARAMETERDUMP_RSIZE,
PContentHandler.PARAMETERDUMP_RSIZE-1);
if (numbers < PContentHandler.PARAMETERDUMP_RSIZE
|| numbers != (PContentHandler.PARAMETERDUMP_RSIZE
+allnumbers[PContentHandler.PARAMETERDUMP_RSIZE-1]) )
break;
handler.parameterDump(allnumbers);
}
if (numbers>0)
incompleteRecord(IPARAMETERDUMP, PContentHandler.PARAMETERDUMP_RSIZE);
handler.endSection(IPARAMETERDUMP);
}
private void cableDump() throws IOException, ParseException
{
int va = voiceAreaId();
if (va<0)
return;
handler.beginSection(ICABLEDUMP, va);
while (true)
{
parseNumbers(PContentHandler.CABLEDUMP_RSIZE, -1);
if (numbers != PContentHandler.CABLEDUMP_RSIZE)
break;
handler.cableDump(allnumbers);
}
if (numbers>0)
incompleteRecord(ICABLEDUMP, PContentHandler.CABLEDUMP_RSIZE);
handler.endSection(ICABLEDUMP);
}
private void currentNoteDump() throws IOException, ParseException
{
handler.beginSection(ICURRENTNOTEDUMP, -1);
while (true)
{
parseNumbers(PContentHandler.CURRENTNOTEDUMP_RSIZE, -1);
if (numbers != PContentHandler.CURRENTNOTEDUMP_RSIZE)
break;
handler.currentNoteDump(allnumbers);
}
if (numbers>0)
incompleteRecord(ICURRENTNOTEDUMP, PContentHandler.CURRENTNOTEDUMP_RSIZE);
handler.endSection(ICURRENTNOTEDUMP);
}
private void parseNumbers(int count, int more) throws IOException, ParseException
{
numbers = 0;
for(;;)
{
switch (next())
{
case PScanner.NUMBER:
if (numbers>=allnumbers.length)
{
int[] expanded = new int[(numbers+1)*2];
System.arraycopy(allnumbers, 0, expanded, 0, numbers);
allnumbers = expanded;
}
if (more == numbers)
{
count+=scanner.getNumber();
}
allnumbers[numbers++] = scanner.getNumber();
take();
if (--count<=0)
return;
break;
case PScanner.INLINEWS:
case PScanner.NEWLINEWS:
take();
break;
case PScanner.EOF:
emitfatal("unexpected end of file");
case PScanner.BROPEN:
return;
default:
emiterror("expected <number> or '[', found "+found());
return;
}
}
}
private int voiceAreaId() throws IOException, ParseException
{
skipws();
if (next()!=PScanner.NUMBER || (scanner.getNumber()<0 || scanner.getNumber()>1))
{
emiterror("expected voice area id ('0'|'1'), found "+found());
return -1;
}
take();
return scanner.getNumber();
}
private String found() throws IOException
{
switch (next())
{
case PScanner.NUMBER:
return "number <"+scanner.getNumber()+">";
case PScanner.NEWLINEWS:
return "[\\r\\n]+";
case PScanner.INLINEWS:
return "(<whitespace>)+";
case PScanner.EOF:
return "<EOF>";
}
String s = scanner.getString();
if (s.length()>20)
{
return "'"+s.substring(0,20) + "' ("+(s.length()-20)+" more)";
}
else
{
return "'"+s+"'";
}
}
private void nameDump() throws IOException, ParseException
{
int voicearea = voiceAreaId();
if (voicearea<0)
return;
handler.beginSection(INAMEDUMP, voicearea);
StringBuilder sb = getCachedBuilder();
loop:while (true)
{
skipws();
switch(next())
{
case PScanner.BROPEN:
break loop;
case PScanner.NUMBER:
break;
default:
emiterror("expected <module index>, found "+found());
break loop;
}
int modindex = scanner.getNumber();
take();
sb.setLength(0);
loop2:while (true)
{
switch (next())
{
case PScanner.NEWLINEWS:
break loop2;
case PScanner.NUMBER:
sb.append(Integer.toString(scanner.getNumber()));
break;
case PScanner.EOF:
break loop2;
default:
sb.append(scanner.getString());
break;
}
take();
}
String modname;
if (sb.length()>0 && Character.isWhitespace(sb.charAt(0)))
modname = sb.substring(1);
else
modname = sb.toString();
handler.moduleNameDump(modindex, modname);
}
handler.endSection(INAMEDUMP);
}
private StringBuilder cachedBuilder = null;
private StringBuilder getCachedBuilder()
{
if (cachedBuilder == null)
cachedBuilder = new StringBuilder();
else
cachedBuilder.setLength(0);
return cachedBuilder;
}
private void notesSection() throws IOException, ParseException
{
handler.beginSection(INOTES,-1);
StringBuilder notes = getCachedBuilder();
boolean onlywhitespace = true;
int truncstart = -1;
boolean firstnewline = true;
final int ST_NONE = 0;
final int ST_BROPEN = 1;
final int ST_SLASH = 2;
final int ST_NOTES = 3;
final int ST_BRCLOSE = 4;
int state = ST_NONE;
loop:while (true)
{
switch (next())
{
case PScanner.NEWLINEWS:
if (state!=ST_BRCLOSE)
{
state = ST_NONE;
if (state == ST_NONE)
truncstart = notes.length();
}
if ((!firstnewline))
appendNewlines(notes, scanner.getNumber());
onlywhitespace = true;
break;
case PScanner.INLINEWS:
if (!firstnewline) notes.append(scanner.getString());
onlywhitespace = true;
break;
case PScanner.NUMBER:
notes.append(Integer.toString(scanner.getNumber()));
state = ST_NONE;
onlywhitespace = false;
break;
case PScanner.EOF:
if (state != ST_BRCLOSE)
{
truncstart = notes.length();
emiterror("unexpected end of file");
}
break loop;
case PScanner.BROPEN:
if (state == ST_NONE)
{
if (!onlywhitespace)
truncstart = notes.length();
state = ST_BROPEN;
}
else state = ST_NONE;
notes.append(scanner.getString());
break;
case PScanner.SLASH:
state = state == ST_BROPEN ? ST_SLASH : ST_NONE;
notes.append(scanner.getString());
onlywhitespace = false;
break;
case PScanner.BRCLOSE:
if (state == ST_NOTES)
{
// finished ???
state = ST_BRCLOSE;
}
else
{
state = ST_NONE;
}
onlywhitespace = false;
notes.append(scanner.getString());
break;
default:
String s = scanner.getString();
notes.append(s);
if (state == ST_SLASH)
{
Integer sid = sectionMap.get(s.toLowerCase());
if (sid != null && sid.intValue() == INOTES)
state = ST_NOTES;
else
state = ST_NONE;
}
onlywhitespace = false;
break;
}
take();
firstnewline = false;
}
if (state == ST_BRCLOSE && truncstart>=0 && truncstart<notes.length())
notes.replace(truncstart, notes.length(), "");
handler.notes(notes.toString());
handler.endSection(INOTES);
}
private void appendNewlines(StringBuilder sb, int count)
{
while (count-->0)
sb.append("\n");
}
private final int next() throws IOException
{
return token != -1 ? token : (token=scanner.nextToken());
}
private final void take()
{
token = -1;
}
private int wsinclusive(int tclass) throws IOException
{
int rtoken;
loop: while (true)
{
rtoken = next();
if (tclass == rtoken || rtoken == -1)
{
take();
break loop;
}
switch (rtoken)
{
case PScanner.NEWLINEWS:
case PScanner.INLINEWS:
break;
default:
break loop;
}
take();
}
return rtoken;
}
private int inclusive(int tclass) throws IOException, ParseException
{
int rtoken;
int position = getPosition();
int line = getLineNumber();
int column = getColumn();
boolean takeerroneous = true;
StringBuilder erroneous = null;
final int emax = 20;
loop: while (true)
{
rtoken = next();
if (rtoken == tclass || rtoken == -1)
{
take();
break loop;
}
switch (rtoken)
{
case PScanner.ANY:
case PScanner.BRCLOSE:
case PScanner.BROPEN:
case PScanner.EOF:
case PScanner.EQ:
case PScanner.SLASH:
// set skipped flag
if (erroneous == null)
erroneous = new StringBuilder();
if (takeerroneous && erroneous != null && erroneous.length()<emax)
{
String s = scanner.getString();
erroneous.append(s.substring(0, Math.min(s.length(), 1+emax-erroneous.length())));
}
break;
case PScanner.NUMBER:
// set skipped flag
if (erroneous == null)
erroneous = new StringBuilder();
if (takeerroneous && erroneous != null && erroneous.length()<emax)
{
erroneous.append(Integer.toString(scanner.getNumber()));
}
break;
case PScanner.INLINEWS:
// ignore
if (takeerroneous && erroneous != null && erroneous.length()<emax)
{
String s = scanner.getString();
erroneous.append(s.substring(0, Math.min(s.length(), 1+emax-erroneous.length())));
}
break;
case PScanner.NEWLINEWS:
if (erroneous != null)
takeerroneous = false;
// ignore
break;
}
take();
}
if (erroneous!=null)
{
String msg = "erroneous characters";
if (erroneous.length()>0)
{
msg += ": '"+erroneous+"'";
int more = scanner.getPosition()-position-erroneous.length();
if (more>0)
msg+=" ("+more+" more)";
}
emitwarning(line, column, msg);
}
return rtoken;
}
public void skipws() throws IOException
{
while (true)
{
switch (next())
{
case PScanner.INLINEWS:
case PScanner.NEWLINEWS:
take();
break;
default:
return;
}
}
}
}