package scotch.compiler.parser;
import static java.util.Arrays.asList;
import static org.antlr.v4.runtime.Token.EOF;
import static scotch.compiler.parser.ScotchLayoutLexer.State.ACCEPT;
import static scotch.compiler.parser.ScotchLayoutLexer.State.SCAN_DEFAULT;
import static scotch.compiler.parser.ScotchLayoutLexer.State.SCAN_DISABLED;
import static scotch.compiler.parser.ScotchLayoutLexer.State.SCAN_LAYOUT;
import static scotch.compiler.parser.ScotchLayoutLexer.State.SCAN_LET;
import static scotch.compiler.parser.ScotchLexer.CLOSE_CURLY;
import static scotch.compiler.parser.ScotchLexer.CLOSE_SQUARE;
import static scotch.compiler.parser.ScotchLexer.DO;
import static scotch.compiler.parser.ScotchLexer.IN;
import static scotch.compiler.parser.ScotchLexer.LET;
import static scotch.compiler.parser.ScotchLexer.NEWLINE;
import static scotch.compiler.parser.ScotchLexer.ON;
import static scotch.compiler.parser.ScotchLexer.OPEN_CURLY;
import static scotch.compiler.parser.ScotchLexer.OPEN_SQUARE;
import static scotch.compiler.parser.ScotchLexer.SEMICOLON;
import static scotch.compiler.parser.ScotchLexer.WHERE;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenFactory;
import org.antlr.v4.runtime.TokenSource;
import org.antlr.v4.runtime.misc.Pair;
public class ScotchLayoutLexer implements TokenSource {
private static final int LOOK_AHEAD = 5;
private final ScotchLexer delegate;
private final Deque<State> states;
private final Deque<Integer> braces;
private final Deque<Integer> indents;
private final Deque<Integer> letIndents;
private final List<Token> tokens;
public ScotchLayoutLexer(ScotchLexer delegate) {
this.delegate = delegate;
states = new ArrayDeque<>(asList(SCAN_DEFAULT));
braces = new ArrayDeque<>();
indents = new ArrayDeque<>();
letIndents = new ArrayDeque<>();
tokens = new ArrayList<>();
}
@Override
public int getCharPositionInLine() {
return firstToken().getCharPositionInLine();
}
@Override
public CharStream getInputStream() {
return firstToken().getInputStream();
}
@Override
public int getLine() {
return firstToken().getLine();
}
@Override
public String getSourceName() {
return firstToken().getTokenSource().getSourceName();
}
@Override
public TokenFactory<?> getTokenFactory() {
return delegate.getTokenFactory();
}
@Override
public Token nextToken() {
if (tokens.isEmpty()) {
buffer();
} else {
buffer();
advance();
}
layout();
return firstToken();
}
@Override
public void setTokenFactory(TokenFactory<?> factory) {
delegate.setTokenFactory(factory);
}
private void accept() {
enterState(ACCEPT);
}
private void advance() {
tokens.remove(0);
}
private void bracesDown() {
braces.push(braces.pop() - 1);
}
private void bracesUp() {
braces.push(braces.pop() + 1);
}
private void buffer() {
if (tokens.isEmpty() || lastToken().getType() != EOF && tokens.size() < LOOK_AHEAD) {
buffer_();
}
}
private void buffer_() {
Token token = delegate.nextToken();
if (token.getType() == EOF && lastToken().getType() != SEMICOLON) {
tokens.add(token(SEMICOLON, ";", token));
}
tokens.add(token);
buffer();
}
private int currentColumn() {
return firstToken().getCharPositionInLine();
}
private int currentIndent() {
return indents.peek();
}
private int currentLetIndent() {
return letIndents.peek();
}
private State currentState() {
return states.peek();
}
private void dedent() {
indents.pop();
}
private void disableScan() {
enterState(SCAN_DISABLED);
braces.push(0);
}
private void enterLayout() {
if (secondToken().getType() == NEWLINE) {
exciseNewLine();
enterLayout();
} else if (secondToken().getType() == OPEN_CURLY) {
disableScan();
} else {
indent(secondToken().getCharPositionInLine());
insertLCurly();
enterState(SCAN_LAYOUT);
accept();
}
}
private void enterLet() {
if (secondToken().getType() == NEWLINE) {
exciseNewLine();
enterLet();
} else if (secondToken().getType() == OPEN_CURLY) {
disableScan();
} else {
letIndent();
insertLCurly();
enterState(SCAN_LET);
accept();
}
}
private void enterState(State state) {
states.push(state);
}
private void exciseNewLine() {
if (secondToken().getType() == NEWLINE) {
tokens.remove(1);
} else {
throw new IllegalStateException();
}
}
private Token firstToken() {
return tokens.get(0);
}
private boolean hasBraces() {
return braces.peek() > 0;
}
private boolean in(State state) {
return currentState() == state;
}
private void indent(int level) {
indents.push(level);
}
private void indent() {
indent(firstToken().getCharPositionInLine());
}
private void insertIn() {
insertToken(token(IN, "in", firstToken()));
}
private void insertLCurly() {
tokens.add(1, token(OPEN_CURLY, "{", tokens.get(2)));
}
private void insertRCurly() {
insertToken(token(CLOSE_CURLY, "}", firstToken()));
}
private void insertSemicolon() {
insertToken(token(SEMICOLON, ";", firstToken()));
}
private void insertToken(Token token) {
tokens.add(0, token);
}
private Token lastToken() {
return tokens.get(tokens.size() - 1);
}
private void layout() {
buffer();
layout_();
}
private void layout_() {
switch (firstToken().getType()) {
case EOF:
if (in(SCAN_LAYOUT)) {
leaveLayout();
}
return;
case SEMICOLON:
case OPEN_SQUARE:
case CLOSE_SQUARE:
case OPEN_CURLY:
case CLOSE_CURLY:
return;
case WHERE:
case ON:
case DO:
enterLayout();
return;
case LET:
if (in(SCAN_LET) && currentColumn() == currentLetIndent()) {
advance();
layout();
} else {
enterLet();
}
return;
case NEWLINE:
advance();
layout();
return;
}
switch (currentState()) {
case ACCEPT:
leaveState();
break;
case SCAN_LAYOUT:
if (currentColumn() == currentIndent()) {
insertSemicolon();
accept();
} else if (currentColumn() < currentIndent()) {
leaveLayout();
}
break;
case SCAN_LET:
if (currentColumn() == currentIndent()) {
insertSemicolon();
accept();
} else if (firstToken().getType() == IN) {
leaveLet();
} else if (currentColumn() < currentIndent()) {
insertIn();
leaveLet();
}
break;
case SCAN_DISABLED:
if (firstToken().getType() == OPEN_CURLY || firstToken().getType() == OPEN_SQUARE) {
bracesUp();
} else if (firstToken().getType() == CLOSE_CURLY || secondToken().getType() == CLOSE_SQUARE) {
if (hasBraces()) {
bracesDown();
} else {
leaveState();
}
}
break;
default:
if (indents.isEmpty()) {
indent();
} else if (currentColumn() <= currentIndent()) {
dedent();
insertSemicolon();
indent(firstToken().getCharPositionInLine());
accept();
}
}
}
private void leaveLayout() {
leaveState();
if (in(SCAN_LET)) {
insertRCurly();
insertSemicolon();
dedent();
} else {
insertSemicolon();
insertRCurly();
insertSemicolon();
dedent();
accept();
}
}
private void leaveLet() {
insertRCurly();
insertSemicolon();
letDedent();
leaveState();
accept();
}
private void leaveState() {
if (states.isEmpty()) {
throw new IllegalStateException("Cannot leave root state");
} else {
states.pop();
}
}
private void letDedent() {
letIndents.pop();
dedent();
}
private void letIndent() {
letIndents.push(firstToken().getCharPositionInLine());
indent(secondToken().getCharPositionInLine());
}
private Token secondToken() {
return tokens.get(1);
}
private Token token(int type, String text, Token token) {
return getTokenFactory().create(
new Pair<>(delegate, delegate.getInputStream()), type, text,
token.getChannel(),
token.getStartIndex(),
token.getStopIndex(),
token.getLine(),
token.getCharPositionInLine()
);
}
enum State {
ACCEPT,
SCAN_DEFAULT,
SCAN_DISABLED,
SCAN_LET,
SCAN_LAYOUT
}
}