package freeboogie.ast.gen;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import freeboogie.util.Err;
/**
* Used for flow control in {@code AgParser}.
*
* @author rgrig
* @author reviewed by TODO
*/
@SuppressWarnings("serial")
class EofReached extends Exception {
// only the type is important
}
/**
* A hand-crafted parser for abstract grammar (AG) textual descriptions.
* See packages {@link freeboogie.ast} and {@link freeboogie.ast.gen}
* for examples. A reason why this is handcrafted is that I want to
* compare the experience of writing it with the experience of writing
* the BoogiePL parser using ANTLR. It should also be not too hard since
* the input language is <i>really</i> simple. On the other hand I can
* explore the design space of a parse more, for example by giving really
* custom error messages and hints of what might be wrong.
*
* @author rgrig
* @author reviewed by TODO
*/
public class AgParser {
private static final Logger log = Logger.getLogger("freeboogie.ast.gen");
private AgLexer lexer;
private Grammar grammar;
private boolean okToFinish;
/**
* Creates an AG parser.
*/
public AgParser() {
grammar = new Grammar();
okToFinish = false;
}
/**
* Sets the input stream for the next parsing operation.
* @param stream the input stream
* @throws IOException
*/
public void setInputStream(InputStream stream) throws IOException {
lexer = new AgLexer(new CharStream(stream));
}
/**
* This will read in an AG from the input stream.
*
* @throws IOException if thrown by the underlying stream
*/
public void parseAg() throws IOException {
try {
while (true) parseStatement();
} catch (EofReached e) {
if (!okToFinish) {
Err.error("The end of file took me by surprise.");
Err.help("Did you forget a semicolon?");
}
}
}
/**
* Gets the grammar. Initially it is empty.
* @return the grammar
*/
public Grammar getGrammar() {
return grammar;
}
private void parseStatement() throws IOException, EofReached {
okToFinish = true;
AgToken t = nextToken();
if (t.type != AgToken.Type.ID) {
skipStatementBecauseOf(t);
return;
}
okToFinish = false;
String className = t.rep;
t = nextToken();
if (t.type == AgToken.Type.EQ) {
// parse "class = members"
parseMembers(className);
} else if (t.type == AgToken.Type.COLON) {
// parse "class : spec\n"
parseSpec(className);
} else if (t.type == AgToken.Type.SUPERTYPE) {
// parse "class :> derived"
parseSubclasses(className);
} else {
skipStatementBecauseOf(t);
return;
}
lexer.eat();
log.fine("Parsed statement from AG (" + className + ").");
}
private void parseMembers(String className)
throws IOException, EofReached {
AgClass cls = grammar.getAgClass(className);
int memCnt = 0;
// The user should not see this sentinel token.
AgToken t = new AgToken(AgToken.Type.COMMA, "INTERNAL ERROR");
while (t.type == AgToken.Type.COMMA) {
t = nextToken();
AgMember mem = new AgMember();
if (t.type == AgToken.Type.ENUM) {
mem.type = parseEnum(cls);
if (mem.type == null) return;
} else if (t.type == AgToken.Type.ID)
mem.type = t.rep;
else {
if (memCnt == 0)
Err.help("You should specify at least one member in a '=' statement.");
skipStatementBecauseOf(t);
return;
}
t = nextToken();
if (t.type == AgToken.Type.BANG) {
mem.nonNull = true;
t = nextToken();
}
if (t.type != AgToken.Type.ID) {
err("I was expecting a name for this member.");
skipStatementBecauseOf(t);
return;
}
mem.name = t.rep;
cls.members.add(mem);
++memCnt;
t = nextToken();
}
if (t.type != AgToken.Type.SEMICOLON)
skipStatementBecauseOf(t);
}
/* Reads all the text up to the first newline */
private void parseSpec(String className) throws IOException {
AgClass cls = grammar.getAgClass(className);
StringBuilder sb = new StringBuilder();
AgToken tok = lexer.next();
while (tok != null && tok.type != AgToken.Type.NL) {
sb.append(tok.rep);
tok = lexer.next();
}
if (tok == null) {
Err.error("The spec for '" + className + "' ends abruptly.");
Err.help("No newline at the end of the grammar file?");
}
cls.invariants.add(sb.toString());
}
private void parseSubclasses(String className)
throws IOException, EofReached {
grammar.getAgClass(className);
AgToken id, sep;
id = nextToken();
if (id.type == AgToken.Type.SEMICOLON) {
err("This :> statement is meaningless.");
return;
}
while (true) {
if (id.type != AgToken.Type.ID) {
skipStatementBecauseOf(id);
return;
}
AgClass derived = grammar.getAgClass(id.rep);
if (derived.base != null) {
err("You specify multiple base classes for '" + derived.name +"'");
Err.help("I'll use '" + derived.base + "'");
} else
derived.base = className;
sep = nextToken();
if (sep.type == AgToken.Type.SEMICOLON) break;
if (sep.type != AgToken.Type.COMMA) {
skipStatementBecauseOf(sep);
return;
}
id = nextToken();
}
}
/*
* Parses (enum_name; val1, ..., valn), adds this to the class cls,
* and returns enum_name. Returns null if parsing fails.
*/
private String parseEnum(AgClass cls) throws IOException, EofReached {
AgToken t = nextToken();
if (t.type != AgToken.Type.LP) {
err("I was expecting '(' after 'enum'.");
Err.help("Are you trying to use 'enum' as a type name?");
skipStatementBecauseOf(t);
return null;
}
t = nextToken();
if (t.type != AgToken.Type.ID) {
err("There should be an enum name here.");
skipStatementBecauseOf(t);
return null;
}
AgEnum agEnum = cls.getEnum(t.rep);
t = nextToken();
if (t.type != AgToken.Type.COLON) {
err("I was expecting ':' after the enum type name.");
skipStatementBecauseOf(t);
return null;
}
t = nextToken();
if (t.type == AgToken.Type.RP) return agEnum.name;
while (true) {
if (t.type != AgToken.Type.ID) {
err("I was expecting an enum value.");
skipStatementBecauseOf(t);
return null;
}
agEnum.values.add(t.rep);
t = nextToken();
if (t.type == AgToken.Type.RP) break;
if (t.type != AgToken.Type.COMMA) {
err("I was expecting a comma between enum values.");
skipStatementBecauseOf(t);
return null;
}
t = nextToken();
}
return agEnum.name;
}
private void skipStatementBecauseOf(AgToken tok)
throws IOException, EofReached {
StringBuilder sb = new StringBuilder();
err("I'm confused by '" + tok.rep + "'");
do {
tok = nextToken();
if (tok.type == AgToken.Type.ID) sb.append(' ');
sb.append(tok.rep);
} while (tok.type != AgToken.Type.SEMICOLON);
Err.error("I skipped: " + sb);
lexer.eat();
}
private AgToken nextToken() throws IOException, EofReached {
AgToken token = lexer.nextGood();
if (token == null) throw new EofReached();
log.finer("read token: " + token.rep);
return token;
}
private void err(String e) {
lexer.eat();
Err.error("AG" + lexer.getLoc() + ": " + e);
}
/**
* For testing purposes. (TODO)
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}