package scotch.compiler.parser;
import static org.antlr.v4.runtime.Recognizer.EOF;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static scotch.compiler.parser.ScotchLexer.CLOSE_CURLY;
import static scotch.compiler.parser.ScotchLexer.ID_VAR;
import static scotch.compiler.parser.ScotchLexer.IN;
import static scotch.compiler.parser.ScotchLexer.OPEN_CURLY;
import static scotch.compiler.parser.ScotchLexer.OPERATOR;
import static scotch.compiler.parser.ScotchLexer.SEMICOLON;
import static scotch.compiler.parser.ScotchLexer.VOCABULARY;
import java.util.ArrayList;
import java.util.List;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.Token;
import org.junit.Test;
public class ScotchLayoutLexerTest {
@Test
public void shouldRemoveNewlineBeforeIndent() {
List<Token> tokens = lexAll(
"fn a = a",
" + b"
);
shouldHaveToken(tokens, 4, OPERATOR, "+");
}
@Test
public void sequentialLet_shouldCollapseIntoSingleLet() {
List<Token> tokens = lexAll(
"let one = 1",
"let two = 2",
"three one two"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 5, SEMICOLON, ";");
shouldHaveToken(tokens, 6, ID_VAR, "two");
shouldHaveToken(tokens, 9, SEMICOLON, ";");
shouldHaveToken(tokens, 10, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 11, IN, "in");
shouldHaveToken(tokens, 12, ID_VAR, "three");
}
@Test
public void shouldAcceptLetWithoutIn_whenScopeOnSeparateLine() {
List<Token> tokens = lexAll(
"let x = 1",
" y = 2",
"x + y"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 5, SEMICOLON, ";");
shouldHaveToken(tokens, 6, ID_VAR, "y");
shouldHaveToken(tokens, 9, SEMICOLON, ";");
shouldHaveToken(tokens, 10, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 11, IN, "in");
}
@Test
public void shouldGetTokensOnSameLine() {
List<Token> tokens = lexAll("one two three");
shouldHaveToken(tokens, 0, ID_VAR, "one");
shouldHaveToken(tokens, 1, ID_VAR, "two");
shouldHaveToken(tokens, 2, ID_VAR, "three");
}
@Test
public void shouldIndentAccordingToFirstTokenAfterLet() {
List<Token> tokens = lexAll(
"let",
" x = 1",
" y = 2",
"x + y"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 5, SEMICOLON, ";");
shouldHaveToken(tokens, 6, ID_VAR, "y");
shouldHaveToken(tokens, 9, SEMICOLON, ";");
shouldHaveToken(tokens, 10, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 11, IN, "in");
}
@Test
public void shouldInsertCurliesAroundLet() {
List<Token> tokens = lexAll(
"let x = y",
" z = a",
"in x + z"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 5, SEMICOLON, ";");
shouldHaveToken(tokens, 6, ID_VAR, "z");
shouldHaveToken(tokens, 9, SEMICOLON, ";");
shouldHaveToken(tokens, 10, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 11, IN, "in");
}
@Test
public void shouldInsertCurliesAroundLetOnOneLine() {
List<Token> tokens = lexAll("let z = \\x -> y in z");
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 8, SEMICOLON, ";");
shouldHaveToken(tokens, 9, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 10, IN, "in");
}
@Test
public void shouldRemoveNewLinesBetweenAllIndentedLines() {
List<Token> tokens = lexAll(
"these are",
" all",
" together",
"this is not"
);
shouldHaveToken(tokens, 2, ID_VAR, "all");
shouldHaveToken(tokens, 3, ID_VAR, "together");
shouldHaveToken(tokens, 4, SEMICOLON, ";");
}
@Test
public void shouldInsertSemicolonBetweenLinesWithSameIndent() {
List<Token> tokens = lexAll(
"these are",
"separate"
);
shouldHaveToken(tokens, 2, SEMICOLON, ";");
}
@Test
public void shouldLayoutMatch() {
List<Token> tokens = lexAll(
"fib n = match n on",
" 0 = 0",
" 1 = 1",
" n = fib (n - 1) + fib (n - 2)",
"main = ..."
);
shouldHaveToken(tokens, 6, OPEN_CURLY, "{");
shouldHaveToken(tokens, 10, SEMICOLON, ";");
shouldHaveToken(tokens, 14, SEMICOLON, ";");
shouldHaveToken(tokens, 30, SEMICOLON, ";");
shouldHaveToken(tokens, 31, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 32, SEMICOLON, ";");
shouldHaveToken(tokens, 33, ID_VAR, "main");
}
@Test
public void shouldLayoutWhere_whenEndingInEof() {
List<Token> tokens = lexAll(
"where",
" (+) :: a",
" (-) :: a",
" abs :: a"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 7, SEMICOLON, ";");
shouldHaveToken(tokens, 13, SEMICOLON, ";");
shouldHaveToken(tokens, 17, SEMICOLON, ";");
shouldHaveToken(tokens, 19, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 20, SEMICOLON, ";");
}
@Test
public void shouldLayoutWhere_whenEndingWithDedent() {
List<Token> tokens = lexAll(
"where",
" (+) :: a",
" (-) :: b",
" abs :: c",
"sub :: d"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 7, SEMICOLON, ";");
shouldHaveToken(tokens, 13, SEMICOLON, ";");
shouldHaveToken(tokens, 17, SEMICOLON, ";");
shouldHaveToken(tokens, 18, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 19, SEMICOLON, ";");
shouldHaveToken(tokens, 20, ID_VAR, "sub");
}
@Test
public void shouldLayoutWhere_withMemberOnSameLine() {
List<Token> tokens = lexAll(
"where (+) :: a",
" (-) :: b",
" abs :: c",
"sub :: d"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 7, SEMICOLON, ";");
shouldHaveToken(tokens, 13, SEMICOLON, ";");
shouldHaveToken(tokens, 17, SEMICOLON, ";");
shouldHaveToken(tokens, 18, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 19, SEMICOLON, ";");
shouldHaveToken(tokens, 20, ID_VAR, "sub");
}
@Test
public void shouldNotLayoutWhenCurliesProvided() {
List<Token> tokens = lexAll(
" let { x = 1;",
"y = 2;",
" } in x + y"
);
shouldHaveToken(tokens, 1, OPEN_CURLY, "{");
shouldHaveToken(tokens, 5, SEMICOLON, ";");
shouldHaveToken(tokens, 6, ID_VAR, "y");
shouldHaveToken(tokens, 9, SEMICOLON, ";");
shouldHaveToken(tokens, 10, CLOSE_CURLY, "}");
shouldHaveToken(tokens, 11, IN, "in");
}
private ScotchLayoutLexer lex(String... input) {
return new ScotchLayoutLexer(new ScotchLexer(new ANTLRInputStream(String.join("\n", input))));
}
private List<Token> lexAll(String... input) {
ScotchLayoutLexer lexer = lex(input);
List<Token> tokens = new ArrayList<>();
while (true) {
tokens.add(lexer.nextToken());
if (tokens.get(tokens.size() - 1).getType() == EOF) {
break;
}
}
return tokens;
}
private void shouldHaveToken(List<Token> tokens, int offset, int type, String text) {
shouldHaveToken(tokens.get(offset), type, text);
}
private void shouldHaveToken(Token token, int type, String text) {
assertThat(VOCABULARY.getSymbolicName(token.getType()), is(VOCABULARY.getSymbolicName(type)));
assertThat(token.getText(), is(text));
}
}