From 0ae082e7d0ee636e07b031f0bc53dbfde1fa8951 Mon Sep 17 00:00:00 2001 From: JFronny Date: Fri, 15 Sep 2023 19:32:01 +0200 Subject: [PATCH] feat(muscript): V3: enforce new line or semicolon between expressions --- .../io/gitlab/jfronny/muscript/compiler/Lexer.java | 14 ++++++++++++-- .../jfronny/muscript/compiler/MuScriptVersion.java | 4 ++-- .../gitlab/jfronny/muscript/compiler/Parser.java | 11 +++++++++-- .../gitlab/jfronny/muscript/test/ClosureTest.java | 5 +++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java index daca0e0..1cc41db 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Lexer.java @@ -22,6 +22,8 @@ public class Lexer extends VersionedComponent { public final String source; public int start, current; + public boolean passedNewline = false; + @Deprecated public Lexer(String source) { this(MuScriptVersion.DEFAULT, source); @@ -51,6 +53,7 @@ public class Lexer extends VersionedComponent { */ public void next() { start = current; + passedNewline = false; if (isAtEnd()) { createToken(Token.EOF); @@ -111,7 +114,10 @@ public class Lexer extends VersionedComponent { case '{' -> createToken(Token.LeftBrace); case '}' -> createToken(Token.RightBrace); - case ';' -> createToken(Token.Semicolon); + case ';' -> { + createToken(Token.Semicolon); + passedNewline = true; + } default -> unexpected(); } @@ -177,7 +183,11 @@ public class Lexer extends VersionedComponent { if (isAtEnd()) return; switch (peek()) { - case ' ', '\r', '\t', '\n' -> advance(); + case ' ', '\t' -> advance(); + case '\r', '\n' -> { + advance(); + passedNewline = true; + } case '/' -> { switch (peekNext()) { case '/' -> { diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/MuScriptVersion.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/MuScriptVersion.java index 1b0655d..8c54bce 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/MuScriptVersion.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/MuScriptVersion.java @@ -1,9 +1,9 @@ package io.gitlab.jfronny.muscript.compiler; public enum MuScriptVersion { - V1, V2; + V1, V2, V3; - public static final MuScriptVersion DEFAULT = V2; + public static final MuScriptVersion DEFAULT = V3; public boolean contains(MuScriptVersion version) { return compareTo(version) >= 0; diff --git a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java index 1beadfb..c779074 100644 --- a/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java +++ b/muscript/src/main/java/io/gitlab/jfronny/muscript/compiler/Parser.java @@ -131,7 +131,10 @@ public class Parser extends VersionedComponent { List> expressions = new LinkedList<>(); while (!isAtEnd()) { expressions.add(expression()); - match(Token.Semicolon); // Consume semicolon if present + // Consume semicolon if present + if (!lexer.passedNewline & !match(Token.Semicolon) & !isAtEnd() & version.contains(MuScriptVersion.V3)) { + throw error("Either a semicolon or a new line must separate expressions in scripts"); + } } if (expressions.isEmpty()) throw new ParseException(PrettyPrintError.builder(lexer.location()).setMessage("Missing any elements in closure").build()); return new Script(expressions); @@ -401,7 +404,11 @@ public class Parser extends VersionedComponent { List> expressions = new LinkedList<>(); while (!match(Token.RightBrace)) { expressions.add(expression()); - match(Token.Semicolon); // Consume semicolon if present + // Consume semicolon if present + if (!lexer.passedNewline & !match(Token.Semicolon) & version.contains(MuScriptVersion.V3)) { + if (match(Token.RightBrace)) break; + throw error("Either a semicolon or a new line must separate expressions in closures"); + } } int end = previous.start; return new Closure(location(start, end), boundArgs, expressions, variadic); diff --git a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java index 52a64b5..c100484 100644 --- a/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java +++ b/muscript/src/test/java/io/gitlab/jfronny/muscript/test/ClosureTest.java @@ -8,11 +8,12 @@ import org.junit.jupiter.api.Test; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs; import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.number; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class ClosureTest { @Test void testScript() { - assertEquals(8, Parser.parseScript("function(2, 1) function(2, 2) function(2, 3)") + assertEquals(8, Parser.parseScript("function(2, 1); function(2, 2); function(2, 3)") .run(makeArgs()) .asNumber() .getValue()); @@ -23,7 +24,7 @@ class ClosureTest { assertEquals(2, number("{->2}()")); assertEquals(2, number("{n->n}(2)")); assertEquals(2, number("{n->n()}({->2})")); - assertEquals(2, number("{->num = 2 num = num * 2 num = num - 2}()")); + assertThrows(Parser.ParseException.class, () -> number("{->num = 2 num = num * 2 num = num - 2}()")); assertEquals(2, number("{->num = 2; num = num * 2; num = num - 2}()")); }