package jdk.jshell;
import com.sun.tools.javac.code.Source;
import com.sun.tools.javac.parser.Scanner;
import com.sun.tools.javac.parser.ScannerFactory;
import com.sun.tools.javac.parser.Tokens.Token;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.JCDiagnostic;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.JCDiagnostic.Error;
import com.sun.tools.javac.util.Log;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.EnumMap;
import java.util.Iterator;
import jdk.jshell.SourceCodeAnalysis.Completeness;
import com.sun.source.tree.Tree;
import static jdk.jshell.CompletenessAnalyzer.TK.*;
import jdk.jshell.TaskFactory.ParseTask;
import jdk.jshell.TaskFactory.Worker;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
class CompletenessAnalyzer {
private final ScannerFactory scannerFactory;
private final JShell proc;
private static Completeness error() {
return Completeness.UNKNOWN;
}
static class CaInfo {
CaInfo(Completeness status, int unitEndPos) {
this.status = status;
this.unitEndPos = unitEndPos;
}
final int unitEndPos;
final Completeness status;
}
CompletenessAnalyzer(JShell proc) {
this.proc = proc;
Context context = new Context();
Log log = CaLog.createLog(context);
context.put(Log.class, log);
context.put(Source.class, Source.JDK9);
scannerFactory = ScannerFactory.instance(context);
}
CaInfo scan(String s) {
try {
Parser parser = new Parser(
() -> new Matched(scannerFactory.newScanner(s, false)),
worker -> proc.taskFactory.parse(s, worker));
Completeness stat = parser.parseUnit();
int endPos = stat == Completeness.UNKNOWN
? s.length()
: parser.endPos();
return new CaInfo(stat, endPos);
} catch (SyntaxException ex) {
return new CaInfo(error(), s.length());
}
}
@SuppressWarnings("serial")
private static class SyntaxException extends RuntimeException {
}
private static void die() {
throw new SyntaxException();
}
private static class CaLog extends Log {
private static CaLog createLog(Context context) {
PrintWriter pw = new PrintWriter(new StringWriter());
CaLog log = new CaLog(context, pw);
context.put(logKey, log);
return log;
}
private CaLog(Context context, PrintWriter pw) {
super(context, pw);
this.source = DiagnosticSource.NO_SOURCE;
}
@Override
public void error(String key, Object... args) {
die();
}
@Override
public void error(int pos, Error errorKey) {
die();
}
@Override
public void error(int pos, String key, Object... args) {
die();
}
@Override
public void report(JCDiagnostic diagnostic) {
}
}
private static final int XEXPR = 0b1;
private static final int XDECL = 0b10;
private static final int XSTMT = 0b100;
private static final int XEXPR1o = 0b1000;
private static final int XDECL1o = 0b10000;
private static final int XSTMT1o = 0b100000;
private static final int XEXPR1 = XEXPR1o | XEXPR;
private static final int XDECL1 = XDECL1o | XDECL;
private static final int XSTMT1 = XSTMT1o | XSTMT;
private static final int XANY1 = XEXPR1o | XDECL1o | XSTMT1o;
private static final int XTERM = 0b100000000;
private static final int XSTART = 0b1000000000;
private static final int XERRO = 0b10000000000;
private static final int XBRACESNEEDED = 0b100000000000;
static enum TK {
EOF(TokenKind.EOF, 0),
ERROR(TokenKind.ERROR, XERRO),
IDENTIFIER(TokenKind.IDENTIFIER, XEXPR1|XDECL1|XTERM),
UNDERSCORE(TokenKind.UNDERSCORE, XERRO),
CLASS(TokenKind.CLASS, XEXPR|XDECL1|XBRACESNEEDED),
MONKEYS_AT(TokenKind.MONKEYS_AT, XEXPR|XDECL1),
IMPORT(TokenKind.IMPORT, XDECL1|XSTART),
SEMI(TokenKind.SEMI, XSTMT1|XTERM|XSTART),
PACKAGE(TokenKind.PACKAGE, XERRO),
CONST(TokenKind.CONST, XERRO),
GOTO(TokenKind.GOTO, XERRO),
CUSTOM(TokenKind.CUSTOM, XERRO),
ENUM(TokenKind.ENUM, XDECL1|XBRACESNEEDED),
IMPLEMENTS(TokenKind.IMPLEMENTS, XDECL),
INTERFACE(TokenKind.INTERFACE, XDECL1|XBRACESNEEDED),
THROWS(TokenKind.THROWS, XDECL|XBRACESNEEDED),
BOOLEAN(TokenKind.BOOLEAN, XEXPR1|XDECL1),
BYTE(TokenKind.BYTE, XEXPR1|XDECL1),
CHAR(TokenKind.CHAR, XEXPR1|XDECL1),
DOUBLE(TokenKind.DOUBLE, XEXPR1|XDECL1),
FLOAT(TokenKind.FLOAT, XEXPR1|XDECL1),
INT(TokenKind.INT, XEXPR1|XDECL1),
LONG(TokenKind.LONG, XEXPR1|XDECL1),
SHORT(TokenKind.SHORT, XEXPR1|XDECL1),
VOID(TokenKind.VOID, XEXPR1|XDECL1),
ABSTRACT(TokenKind.ABSTRACT, XDECL1),
FINAL(TokenKind.FINAL, XDECL1),
NATIVE(TokenKind.NATIVE, XDECL1),
STATIC(TokenKind.STATIC, XDECL1),
STRICTFP(TokenKind.STRICTFP, XDECL1),
PRIVATE(TokenKind.PRIVATE, XDECL1),
PROTECTED(TokenKind.PROTECTED, XDECL1),
PUBLIC(TokenKind.PUBLIC, XDECL1),
TRANSIENT(TokenKind.TRANSIENT, XDECL1),
VOLATILE(TokenKind.VOLATILE, XDECL1),
EXTENDS(TokenKind.EXTENDS, XEXPR|XDECL),
COMMA(TokenKind.COMMA, XEXPR|XDECL),
AMP(TokenKind.AMP, XEXPR|XDECL, true),
GT(TokenKind.GT, XEXPR|XDECL, true),
LT(TokenKind.LT, XEXPR|XDECL1, true),
LTLT(TokenKind.LTLT, XEXPR|XDECL1, true),
GTGT(TokenKind.GTGT, XEXPR|XDECL, true),
GTGTGT(TokenKind.GTGTGT, XEXPR|XDECL, true),
QUES(TokenKind.QUES, XEXPR|XDECL, true),
DOT(TokenKind.DOT, XEXPR|XDECL),
STAR(TokenKind.STAR, XEXPR, true),
ASSERT(TokenKind.ASSERT, XSTMT1|XSTART),
BREAK(TokenKind.BREAK, XSTMT1|XTERM|XSTART),
CATCH(TokenKind.CATCH, XSTMT1|XSTART),
CONTINUE(TokenKind.CONTINUE, XSTMT1|XTERM|XSTART),
DO(TokenKind.DO, XSTMT1|XSTART),
ELSE(TokenKind.ELSE, XSTMT1|XTERM|XSTART),
FINALLY(TokenKind.FINALLY, XSTMT1|XSTART),
FOR(TokenKind.FOR, XSTMT1|XSTART),
IF(TokenKind.IF, XSTMT1|XSTART),
RETURN(TokenKind.RETURN, XSTMT1|XTERM|XSTART),
SWITCH(TokenKind.SWITCH, XSTMT1|XEXPR1),
SYNCHRONIZED(TokenKind.SYNCHRONIZED, XSTMT1|XDECL),
THROW(TokenKind.THROW, XSTMT1|XSTART),
TRY(TokenKind.TRY, XSTMT1|XSTART),
WHILE(TokenKind.WHILE, XSTMT1|XSTART),
CASE(TokenKind.CASE, XSTMT|XSTART),
DEFAULT(TokenKind.DEFAULT, XSTMT|XSTART),
INTLITERAL(TokenKind.INTLITERAL, XEXPR1|XTERM),
LONGLITERAL(TokenKind.LONGLITERAL, XEXPR1|XTERM),
FLOATLITERAL(TokenKind.FLOATLITERAL, XEXPR1|XTERM),
DOUBLELITERAL(TokenKind.DOUBLELITERAL, XEXPR1|XTERM),
CHARLITERAL(TokenKind.CHARLITERAL, XEXPR1|XTERM),
STRINGLITERAL(TokenKind.STRINGLITERAL, XEXPR1|XTERM),
TRUE(TokenKind.TRUE, XEXPR1|XTERM),
FALSE(TokenKind.FALSE, XEXPR1|XTERM),
NULL(TokenKind.NULL, XEXPR1|XTERM),
THIS(TokenKind.THIS, XEXPR1|XTERM),
PLUSPLUS(TokenKind.PLUSPLUS, XEXPR1|XTERM),
SUBSUB(TokenKind.SUBSUB, XEXPR1|XTERM),
INSTANCEOF(TokenKind.INSTANCEOF, XEXPR, true),
NEW(TokenKind.NEW, XEXPR1),
SUPER(TokenKind.SUPER, XEXPR1|XDECL),
ARROW(TokenKind.ARROW, XEXPR),
COLCOL(TokenKind.COLCOL, XEXPR),
LPAREN(TokenKind.LPAREN, XEXPR),
RPAREN(TokenKind.RPAREN, XEXPR),
LBRACE(TokenKind.LBRACE, XEXPR),
RBRACE(TokenKind.RBRACE, XEXPR),
LBRACKET(TokenKind.LBRACKET, XEXPR),
RBRACKET(TokenKind.RBRACKET, XEXPR),
ELLIPSIS(TokenKind.ELLIPSIS, XEXPR),
EQ(TokenKind.EQ, XEXPR),
BANG(TokenKind.BANG, XEXPR1),
TILDE(TokenKind.TILDE, XEXPR1),
COLON(TokenKind.COLON, XEXPR|XTERM),
EQEQ(TokenKind.EQEQ, XEXPR, true),
LTEQ(TokenKind.LTEQ, XEXPR, true),
GTEQ(TokenKind.GTEQ, XEXPR, true),
BANGEQ(TokenKind.BANGEQ, XEXPR, true),
AMPAMP(TokenKind.AMPAMP, XEXPR, true),
BARBAR(TokenKind.BARBAR, XEXPR, true),
PLUS(TokenKind.PLUS, XEXPR1, true),
SUB(TokenKind.SUB, XEXPR1, true),
SLASH(TokenKind.SLASH, XEXPR, true),
BAR(TokenKind.BAR, XEXPR, true),
CARET(TokenKind.CARET, XEXPR, true),
PERCENT(TokenKind.PERCENT, XEXPR, true),
PLUSEQ(TokenKind.PLUSEQ, XEXPR),
SUBEQ(TokenKind.SUBEQ, XEXPR),
STAREQ(TokenKind.STAREQ, XEXPR),
SLASHEQ(TokenKind.SLASHEQ, XEXPR),
AMPEQ(TokenKind.AMPEQ, XEXPR),
BAREQ(TokenKind.BAREQ, XEXPR),
CARETEQ(TokenKind.CARETEQ, XEXPR),
PERCENTEQ(TokenKind.PERCENTEQ, XEXPR),
LTLTEQ(TokenKind.LTLTEQ, XEXPR),
GTGTEQ(TokenKind.GTGTEQ, XEXPR),
GTGTGTEQ(TokenKind.GTGTGTEQ, XEXPR),
UNMATCHED(XERRO),
PARENS(XEXPR1|XDECL|XSTMT|XTERM),
BRACKETS(XEXPR|XDECL|XTERM),
BRACES(XSTMT1|XEXPR|XTERM),
DOTSTAR(XDECL|XTERM),
COLCOLNEW(XEXPR|XTERM),
DOTCLASS(XEXPR|XTERM),
;
static final EnumMap<TokenKind,TK> tokenKindToTKMap = new EnumMap<>(TokenKind.class);
final TokenKind tokenKind;
final int belongs;
final boolean valueOp;
Function<TK,TK> mapping;
TK(int b) {
this(null, b);
}
TK(TokenKind tokenKind, int b) {
this(tokenKind, b, false);
}
TK(TokenKind tokenKind, int b, boolean valueOp) {
this.tokenKind = tokenKind;
this.belongs = b;
this.valueOp = valueOp;
this.mapping = null;
}
private static TK tokenKindToTK(TK prev, TokenKind kind) {
TK tk = tokenKindToTKMap.get(kind);
if (tk == null) {
System.err.printf("No corresponding %s for %s: %s\n",
TK.class.getCanonicalName(),
TokenKind.class.getCanonicalName(),
kind);
throw new InternalError("No corresponding TK for TokenKind: " + kind);
}
return tk.mapping != null
? tk.mapping.apply(prev)
: tk;
}
boolean isOkToTerminate() {
return (belongs & XTERM) != 0;
}
boolean isExpression() {
return (belongs & XEXPR) != 0;
}
boolean isDeclaration() {
return (belongs & XDECL) != 0;
}
boolean isError() {
return (belongs & XERRO) != 0;
}
boolean isStart() {
return (belongs & XSTART) != 0;
}
boolean isBracesNeeded() {
return (belongs & XBRACESNEEDED) != 0;
}
static {
for (TK tk : TK.values()) {
if (tk.tokenKind != null) {
tokenKindToTKMap.put(tk.tokenKind, tk);
}
}
for (TokenKind kind : TokenKind.values()) {
tokenKindToTK(null, kind);
}
STAR.mapping = prev -> prev == DOT ? DOTSTAR : STAR;
NEW.mapping = prev -> prev == COLCOL ? COLCOLNEW : NEW;
CLASS.mapping = prev -> prev == DOT ? DOTCLASS : CLASS;
}
}
private static class CT {
public final TK kind;
public final int endPos;
public final String message;
private CT(TK tk, Token tok, String msg) {
this.kind = tk;
this.endPos = tok.endPos;
this.message = msg;
}
private CT(TK tk, Token tok) {
this.kind = tk;
this.endPos = tok.endPos;
this.message = null;
}
private CT(TK tk, int endPos) {
this.kind = tk;
this.endPos = endPos;
this.message = null;
}
}
private static class Matched implements Iterator<CT> {
private final Scanner scanner;
private Token current;
private CT prevCT;
private CT currentCT;
private final Deque<Token> stack = new ArrayDeque<>();
Matched(Scanner scanner) {
this.scanner = scanner;
advance();
prevCT = currentCT = new CT(SEMI, 0);
}
@Override
public boolean hasNext() {
return currentCT.kind != EOF;
}
private Token advance() {
Token prev = current;
scanner.nextToken();
current = scanner.token();
return prev;
}
@Override
public CT next() {
prevCT = currentCT;
currentCT = nextCT();
return currentCT;
}
private CT match(TK tk, TokenKind open) {
Token tok = advance();
db("match desired-tk=%s, open=%s, seen-tok=%s", tk, open, tok.kind);
if (stack.isEmpty()) {
return new CT(ERROR, tok, "Encountered '" + tok + "' with no opening '" + open + "'");
}
Token p = stack.pop();
if (p.kind != open) {
return new CT(ERROR, tok, "No match for '" + p + "' instead encountered '" + tok + "'");
}
return new CT(tk, tok);
}
private void db(String format, Object ... args) {
}
private CT nextCT() {
TK prevTK = currentCT.kind;
while (true) {
db("nextCT");
CT ct;
switch (current.kind) {
case EOF:
db("eof");
if (stack.isEmpty()) {
ct = new CT(EOF, current);
} else {
TokenKind unmatched = stack.pop().kind;
stack.clear();
ct = new CT(UNMATCHED, current, "Unmatched " + unmatched);
}
break;
case LPAREN:
case LBRACE:
case LBRACKET:
stack.push(advance());
prevTK = SEMI;
continue;
case RPAREN:
ct = match(PARENS, TokenKind.LPAREN);
break;
case RBRACE:
ct = match(BRACES, TokenKind.LBRACE);
break;
case RBRACKET:
ct = match(BRACKETS, TokenKind.LBRACKET);
break;
default:
ct = new CT(TK.tokenKindToTK(prevTK, current.kind), advance());
break;
}
if (ct.kind.isStart() && !prevTK.isOkToTerminate() && prevTK != COMMA) {
return new CT(ERROR, current, "No '" + prevTK + "' before '" + ct.kind + "'");
}
if (stack.isEmpty() || ct.kind.isError()) {
return ct;
}
prevTK = ct.kind;
}
}
}
private static class Parser {
private final Supplier<Matched> matchedFactory;
private final Function<Worker<ParseTask, Completeness>, Completeness> parseFactory;
private Matched in;
private CT token;
private Completeness checkResult;
Parser(Supplier<Matched> matchedFactory,
Function<Worker<ParseTask, Completeness>, Completeness> parseFactory) {
this.matchedFactory = matchedFactory;
this.parseFactory = parseFactory;
resetInput();
}
final void resetInput() {
this.in = matchedFactory.get();
nextToken();
}
final void nextToken() {
in.next();
token = in.currentCT;
}
boolean shouldAbort(TK tk) {
if (token.kind == tk) {
nextToken();
return false;
}
switch (token.kind) {
case EOF:
checkResult = ((tk == SEMI) && in.prevCT.kind.isOkToTerminate())
? Completeness.COMPLETE_WITH_SEMI
: Completeness.DEFINITELY_INCOMPLETE;
return true;
case UNMATCHED:
checkResult = Completeness.DEFINITELY_INCOMPLETE;
return true;
default:
checkResult = error();
return true;
}
}
Completeness lastly(TK tk) {
if (shouldAbort(tk)) return checkResult;
return Completeness.COMPLETE;
}
Completeness optionalFinalSemi() {
if (!shouldAbort(SEMI)) return Completeness.COMPLETE;
if (checkResult == Completeness.COMPLETE_WITH_SEMI) return Completeness.COMPLETE;
return checkResult;
}
boolean shouldAbort(Completeness flags) {
checkResult = flags;
return flags != Completeness.COMPLETE;
}
public int endPos() {
return in.prevCT.endPos;
}
public Completeness parseUnit() {
switch (token.kind.belongs & XANY1) {
case XEXPR1o:
return parseExpressionOptionalSemi();
case XSTMT1o: {
Completeness stat = parseSimpleStatement();
return stat==null? error() : stat;
}
case XDECL1o:
return parseDeclaration();
case XSTMT1o | XDECL1o:
case XEXPR1o | XDECL1o:
return disambiguateDeclarationVsExpression();
case 0:
if ((token.kind.belongs & XERRO) != 0) {
return parseExpressionStatement();
}
return error();
case XSTMT1o | XEXPR1o:
return disambiguateStatementVsExpression();
default:
throw new InternalError("Case not covered " + token.kind.belongs + " in " + token.kind);
}
}
public Completeness parseDeclaration() {
boolean isImport = token.kind == IMPORT;
boolean isBracesNeeded = false;
while (token.kind.isDeclaration()) {
isBracesNeeded |= token.kind.isBracesNeeded();
nextToken();
}
switch (token.kind) {
case EQ:
nextToken();
return parseExpressionStatement();
case BRACES:
case SEMI:
nextToken();
return Completeness.COMPLETE;
case UNMATCHED:
nextToken();
return Completeness.DEFINITELY_INCOMPLETE;
case EOF:
switch (in.prevCT.kind) {
case BRACES:
case SEMI:
return Completeness.COMPLETE;
case IDENTIFIER:
return isBracesNeeded
? Completeness.DEFINITELY_INCOMPLETE
: Completeness.COMPLETE_WITH_SEMI;
case BRACKETS:
return Completeness.COMPLETE_WITH_SEMI;
case DOTSTAR:
if (isImport) {
return Completeness.COMPLETE_WITH_SEMI;
} else {
return Completeness.UNKNOWN;
}
default:
return Completeness.DEFINITELY_INCOMPLETE;
}
default:
return error();
}
}
public Completeness disambiguateStatementVsExpression() {
if (token.kind == SWITCH) {
nextToken();
switch (token.kind) {
case PARENS:
nextToken();
break;
case UNMATCHED:
nextToken();
return Completeness.DEFINITELY_INCOMPLETE;
case EOF:
return Completeness.DEFINITELY_INCOMPLETE;
default:
return error();
}
switch (token.kind) {
case BRACES:
nextToken();
break;
case UNMATCHED:
nextToken();
return Completeness.DEFINITELY_INCOMPLETE;
case EOF:
return Completeness.DEFINITELY_INCOMPLETE;
default:
return error();
}
if (token.kind.valueOp) {
return parseExpressionOptionalSemi();
} else {
return Completeness.COMPLETE;
}
} else {
throw new InternalError("Unexpected statement/expression not covered " + token.kind.belongs + " in " + token.kind);
}
}
public Completeness disambiguateDeclarationVsExpression() {
return parseFactory.apply(pt -> {
List<? extends Tree> units = pt.units();
if (units.isEmpty()) {
return error();
}
Tree unitTree = units.get(0);
switch (unitTree.getKind()) {
case EXPRESSION_STATEMENT:
return parseExpressionOptionalSemi();
case LABELED_STATEMENT:
if (shouldAbort(IDENTIFIER)) return checkResult;
if (shouldAbort(COLON)) return checkResult;
return parseStatement();
case VARIABLE:
case IMPORT:
case CLASS:
case ENUM:
case ANNOTATION_TYPE:
case INTERFACE:
case METHOD:
return parseDeclaration();
default:
return error();
}
});
}
public Completeness parseExpressionStatement() {
if (shouldAbort(parseExpression())) return checkResult;
return lastly(SEMI);
}
public Completeness parseExpressionOptionalSemi() {
if (shouldAbort(parseExpression())) return checkResult;
return optionalFinalSemi();
}
public Completeness parseExpression() {
while (token.kind.isExpression())
nextToken();
return Completeness.COMPLETE;
}
public Completeness parseStatement() {
Completeness stat = parseSimpleStatement();
if (stat == null) {
return parseExpressionStatement();
}
return stat;
}
public Completeness parseSimpleStatement() {
switch (token.kind) {
case BRACES:
return lastly(BRACES);
case IF: {
nextToken();
if (shouldAbort(PARENS)) return checkResult;
Completeness thenpart = parseStatement();
if (shouldAbort(thenpart)) return thenpart;
if (token.kind == ELSE) {
nextToken();
return parseStatement();
}
return thenpart;
}
case FOR: {
nextToken();
if (shouldAbort(PARENS)) return checkResult;
if (shouldAbort(parseStatement())) return checkResult;
return Completeness.COMPLETE;
}
case WHILE: {
nextToken();
if (shouldAbort(PARENS)) return error();
return parseStatement();
}
case DO: {
nextToken();
switch (parseStatement()) {
case DEFINITELY_INCOMPLETE:
case CONSIDERED_INCOMPLETE:
case COMPLETE_WITH_SEMI:
return Completeness.DEFINITELY_INCOMPLETE;
case UNKNOWN:
return error();
case COMPLETE:
break;
}
if (shouldAbort(WHILE)) return checkResult;
if (shouldAbort(PARENS)) return checkResult;
return lastly(SEMI);
}
case TRY: {
boolean hasResources = false;
nextToken();
if (token.kind == PARENS) {
nextToken();
hasResources = true;
}
if (shouldAbort(BRACES)) return checkResult;
if (token.kind == CATCH || token.kind == FINALLY) {
while (token.kind == CATCH) {
if (shouldAbort(CATCH)) return checkResult;
if (shouldAbort(PARENS)) return checkResult;
if (shouldAbort(BRACES)) return checkResult;
}
if (token.kind == FINALLY) {
if (shouldAbort(FINALLY)) return checkResult;
if (shouldAbort(BRACES)) return checkResult;
}
} else if (!hasResources) {
if (token.kind == EOF) {
return Completeness.DEFINITELY_INCOMPLETE;
} else {
return error();
}
}
return Completeness.COMPLETE;
}
case SWITCH: {
nextToken();
if (shouldAbort(PARENS)) return checkResult;
return lastly(BRACES);
}
case SYNCHRONIZED: {
nextToken();
if (shouldAbort(PARENS)) return checkResult;
return lastly(BRACES);
}
case THROW: {
nextToken();
if (shouldAbort(parseExpression())) return checkResult;
return lastly(SEMI);
}
case SEMI:
return lastly(SEMI);
case ASSERT:
nextToken();
return parseExpressionStatement();
case RETURN:
case BREAK:
case CONTINUE:
nextToken();
return parseExpressionStatement();
case ELSE:
case FINALLY:
case CATCH:
return error();
case EOF:
return Completeness.CONSIDERED_INCOMPLETE;
default:
return null;
}
}
}
}