finished parts of the test
This commit is contained in:
@@ -3,106 +3,367 @@ package ssw.mj.impl;
|
|||||||
import ssw.mj.Errors;
|
import ssw.mj.Errors;
|
||||||
import ssw.mj.scanner.Token;
|
import ssw.mj.scanner.Token;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import static ssw.mj.scanner.Token.Kind.none;
|
import static ssw.mj.scanner.Token.Kind.*;
|
||||||
|
|
||||||
public class Scanner {
|
public class Scanner {
|
||||||
|
|
||||||
// Scanner Skeleton - do not rename fields / methods !
|
// Scanner Skeleton - do not rename fields / methods !
|
||||||
private static final char EOF = (char) -1;
|
private static final char EOF = (char) -1;
|
||||||
private static final char LF = '\n';
|
private static final char LF = '\n';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input data to read from.
|
* Input data to read from.
|
||||||
*/
|
*/
|
||||||
private final Reader in;
|
private final Reader in;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lookahead character. (= next (unhandled) character in the input stream)
|
* Lookahead character. (= next (unhandled) character in the input stream)
|
||||||
*/
|
*/
|
||||||
private char ch;
|
private char ch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current line in input stream.
|
* Current line in input stream.
|
||||||
*/
|
*/
|
||||||
private int line;
|
private int line;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current column in input stream.
|
* Current column in input stream.
|
||||||
*/
|
*/
|
||||||
private int col;
|
private int col;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* According errors object.
|
* According errors object.
|
||||||
*/
|
*/
|
||||||
public final Errors errors;
|
public final Errors errors;
|
||||||
|
|
||||||
public Scanner(Reader r) {
|
public Scanner(Reader r) {
|
||||||
// store reader
|
// store reader
|
||||||
in = r;
|
in = r;
|
||||||
|
|
||||||
// initialize error handling support
|
// initialize error handling support
|
||||||
errors = new Errors();
|
errors = new Errors();
|
||||||
|
|
||||||
line = 1;
|
line = 1;
|
||||||
col = 0;
|
col = 0;
|
||||||
nextCh(); // read 1st char into ch, incr col to 1
|
nextCh(); // read 1st char into ch, incr col to 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds error message to the list of errors.
|
* Adds error message to the list of errors.
|
||||||
*/
|
*/
|
||||||
public final void error(Token t, Errors.Message msg, Object... msgParams) {
|
public final void error(Token t, Errors.Message msg, Object... msgParams) {
|
||||||
errors.error(t.line, t.col, msg, msgParams);
|
errors.error(t.line, t.col, msg, msgParams);
|
||||||
|
|
||||||
// reset token content (consistent JUnit tests)
|
// reset token content (consistent JUnit tests)
|
||||||
t.numVal = 0;
|
t.numVal = 0;
|
||||||
t.val = null;
|
t.val = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ================================================
|
// ================================================
|
||||||
// TODO Exercise UE-P-1: Implement Scanner (next() + private helper methods)
|
// TODO Exercise UE-P-1: Implement Scanner (next() + private helper methods)
|
||||||
// ================================================
|
// ================================================
|
||||||
|
|
||||||
// TODO Exercise UE-P-1: Keywords
|
// TODO Exercise UE-P-1: Keywords
|
||||||
/**
|
/**
|
||||||
* Mapping from keyword names to appropriate token codes.
|
* Mapping from keyword names to appropriate token codes.
|
||||||
*/
|
*/
|
||||||
private static final Map<String, Token.Kind> keywords;
|
private static final Map<String, Token.Kind> keywords;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
keywords = new HashMap<>();
|
keywords = Map.ofEntries(
|
||||||
}
|
Map.entry("void", void_),
|
||||||
|
Map.entry("class", class_),
|
||||||
|
Map.entry("program", program),
|
||||||
|
Map.entry("if", if_),
|
||||||
|
Map.entry("else", else_),
|
||||||
|
Map.entry("while", while_),
|
||||||
|
Map.entry("read", read),
|
||||||
|
Map.entry("print", print),
|
||||||
|
Map.entry("return", return_),
|
||||||
|
Map.entry("break", break_),
|
||||||
|
Map.entry("final", final_),
|
||||||
|
Map.entry("new", new_)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns next token. To be used by parser.
|
* Returns next token. To be used by parser.
|
||||||
*/
|
*/
|
||||||
public Token next() {
|
public Token next() {
|
||||||
// TODO Exercise UE-P-1: implementation of next method
|
// TODO Exercise UE-P-1: implementation of next method
|
||||||
Token t = new Token(none, 1, 1);
|
while (Character.isWhitespace(ch)) {
|
||||||
return t;
|
nextCh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void nextCh() {
|
final var ogLine = line;
|
||||||
// TODO Exercise UE-P-1: implementation of nextCh method and other private helper methods
|
final var ogCol = col;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Exercise UE-P-1: private helper methods used by next(), as discussed in the exercise
|
if (isLetter(ch)) {
|
||||||
|
final var name = readName();
|
||||||
|
|
||||||
// -----------------------------------------------
|
return getTokenByName(name, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isLetter(char c) {
|
if (isDigit(ch)) {
|
||||||
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z';
|
final var number = readNumber();
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isDigit(char c) {
|
var token = new Token(Token.Kind.number, ogLine, ogCol);
|
||||||
return '0' <= c && c <= '9';
|
token.numVal = number;
|
||||||
}
|
token.val = number + "";
|
||||||
|
|
||||||
// ================================================
|
return token;
|
||||||
// ================================================
|
}
|
||||||
|
|
||||||
|
if (ch == '\'') {
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
if (ch == '\\') {
|
||||||
|
nextCh();
|
||||||
|
final var charContent = switch (ch) {
|
||||||
|
case 'n' -> '\n';
|
||||||
|
case 'r' -> '\r';
|
||||||
|
case 't' -> '\t';
|
||||||
|
case '\\' -> '\\';
|
||||||
|
case '\'' -> '\'';
|
||||||
|
default -> ' ';
|
||||||
|
};
|
||||||
|
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
token.val = charContent + "";
|
||||||
|
token.numVal = charContent;
|
||||||
|
nextCh();
|
||||||
|
nextCh();
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var charContent = ch;
|
||||||
|
|
||||||
|
if (charContent == LF || charContent == EOF || charContent == '\'') {
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
final var message = switch (charContent) {
|
||||||
|
case LF -> Errors.Message.ILLEGAL_LINE_END;
|
||||||
|
case EOF -> Errors.Message.EOF_IN_CHAR;
|
||||||
|
case '\'' -> Errors.Message.EMPTY_CHARCONST;
|
||||||
|
default -> null;
|
||||||
|
};
|
||||||
|
|
||||||
|
error(token, message);
|
||||||
|
token.val = '\0' + "";
|
||||||
|
token.numVal = 0;
|
||||||
|
|
||||||
|
if (charContent == '\'') {
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
if (ch != '\'') {
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
error(token, Errors.Message.MISSING_QUOTE);
|
||||||
|
token.val = '\0' + "";
|
||||||
|
token.numVal = 0;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCh();
|
||||||
|
|
||||||
|
final var token = new Token(charConst, ogLine, ogCol);
|
||||||
|
token.val = charContent + "";
|
||||||
|
token.numVal = charContent;
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var ogChar = ch;
|
||||||
|
nextCh();
|
||||||
|
return switch(ogChar) {
|
||||||
|
case ';' -> new Token(Token.Kind.semicolon, ogLine, ogCol);
|
||||||
|
case ',' -> new Token(Token.Kind.comma, ogLine, ogCol);
|
||||||
|
case '.' -> new Token(Token.Kind.period, ogLine, ogCol);
|
||||||
|
case '{' -> new Token(Token.Kind.lbrace, ogLine, ogCol);
|
||||||
|
case '}' -> new Token(Token.Kind.rbrace, ogLine, ogCol);
|
||||||
|
case (char) -1 -> new Token(Token.Kind.eof, ogLine, ogCol);
|
||||||
|
case '=' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.eql, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.assign, line, col);
|
||||||
|
}
|
||||||
|
case '+' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.plusas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '+') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.pplus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.plus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '-' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.minusas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == '-') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.mminus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.minus, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '*' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.timesas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.times, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '/' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.slashas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.slash, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '%' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.remas, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.rem, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '!' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.neq, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(new Token(none, line, col), Errors.Message.INVALID_CHAR);
|
||||||
|
yield next();
|
||||||
|
}
|
||||||
|
case '>' -> {
|
||||||
|
if (ch == '=') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.geq, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.gtr, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '<' -> {
|
||||||
|
if (ch == '<') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.leq, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
yield new Token(Token.Kind.lss, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
case '&' -> {
|
||||||
|
if (ch == '&') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.and, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(new Token(none, line, col), Errors.Message.INVALID_CHAR);
|
||||||
|
yield next();
|
||||||
|
}
|
||||||
|
case '|' -> {
|
||||||
|
if (ch == '|') {
|
||||||
|
nextCh();
|
||||||
|
yield new Token(Token.Kind.and, ogLine, ogCol);
|
||||||
|
}
|
||||||
|
|
||||||
|
error(new Token(none, line, col), Errors.Message.INVALID_CHAR);
|
||||||
|
yield next();
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
error(new Token(none, line, col), Errors.Message.INVALID_CHAR);
|
||||||
|
yield next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextCh() {
|
||||||
|
// TODO Exercise UE-P-1: implementation of nextCh method and other private helper methods
|
||||||
|
try {
|
||||||
|
final var intChar = in.read();
|
||||||
|
final var nextChar = (char) intChar;
|
||||||
|
|
||||||
|
if (ch == '\n') {
|
||||||
|
line++;
|
||||||
|
col = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
col++;
|
||||||
|
ch = nextChar;
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Could not read Stream");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Exercise UE-P-1: private helper methods used by next(), as discussed in the exercise
|
||||||
|
|
||||||
|
// -----------------------------------------------
|
||||||
|
|
||||||
|
private boolean isLetter(char c) {
|
||||||
|
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z';
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isDigit(char c) {
|
||||||
|
return '0' <= c && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readName() {
|
||||||
|
final var builder = new StringBuilder();
|
||||||
|
|
||||||
|
while (isLetter(ch)) {
|
||||||
|
builder.append(ch);
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readNumber() {
|
||||||
|
final var builder = new StringBuilder();
|
||||||
|
|
||||||
|
while (isDigit(ch)) {
|
||||||
|
builder.append(ch);
|
||||||
|
nextCh();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Integer.parseInt(builder.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token getTokenByName(String name, int line, int col) {
|
||||||
|
final var kind = keywords.getOrDefault(name, Token.Kind.ident);
|
||||||
|
|
||||||
|
var token = new Token(kind, line, col);
|
||||||
|
|
||||||
|
if (kind == Token.Kind.ident) {
|
||||||
|
token.val = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================================
|
||||||
|
// ================================================
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user