From bf69d3590cc6a074a27a7ab9627ed46dac6adcc2 Mon Sep 17 00:00:00 2001 From: Petr Pytelka Date: Mon, 30 Dec 2019 05:22:01 +0100 Subject: [PATCH] Improved select case syntax parser. Check if there are only 'case' statements after 'select case ' --- .../scriptbasic/context/ContextBuilder.java | 3 +- .../NestedStructureHouseKeeper.java | 10 +++ .../AbstractNestedStructureHouseKeeper.java | 7 +++ .../syntax/BasicSyntaxAnalyzer.java | 12 ++-- .../commands/CommandAnalyzerSelect.java | 63 ++++++++++++------- .../testprograms/TestPrograms.java | 3 +- .../scriptbasic/testprograms/TestSelect1.bas | 11 ++++ .../testprograms/TestSelectBadSyntax1.bas | 1 + .../testprograms/TestSelectBadSyntax4.bas | 10 +++ 9 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax4.bas diff --git a/src/main/java/com/scriptbasic/context/ContextBuilder.java b/src/main/java/com/scriptbasic/context/ContextBuilder.java index cfb80685..31ff90bf 100644 --- a/src/main/java/com/scriptbasic/context/ContextBuilder.java +++ b/src/main/java/com/scriptbasic/context/ContextBuilder.java @@ -98,7 +98,8 @@ private static void createReaderDependentComponents(final SourceReader reader, f ctx.lexicalAnalyzer = new ScriptBasicLexicalAnalyzer(reader); ctx.nestedStructureHouseKeeper = new GenericNestedStructureHouseKeeper(ctx.lexicalAnalyzer); final var commandFactory = new BasicCommandFactory(ctx); - ctx.syntaxAnalyzer = new BasicSyntaxAnalyzer(ctx.lexicalAnalyzer, commandFactory); + ctx.syntaxAnalyzer = new BasicSyntaxAnalyzer(ctx.lexicalAnalyzer, commandFactory, + ctx.nestedStructureHouseKeeper); } private static void createReusableComponents(final Context ctx) { diff --git a/src/main/java/com/scriptbasic/interfaces/NestedStructureHouseKeeper.java b/src/main/java/com/scriptbasic/interfaces/NestedStructureHouseKeeper.java index 4b6b1a3a..537db80d 100644 --- a/src/main/java/com/scriptbasic/interfaces/NestedStructureHouseKeeper.java +++ b/src/main/java/com/scriptbasic/interfaces/NestedStructureHouseKeeper.java @@ -51,4 +51,14 @@ public interface NestedStructureHouseKeeper { */ T pop(Class expectedClass) throws AnalysisException; + + /** + * Check final state of nested structures. + * + * Check if there are no opened nested structures + * or any other pending blocks. + * + * @throws AnalysisException when there are some elements on the stack + */ + void checkFinalState() throws AnalysisException; } diff --git a/src/main/java/com/scriptbasic/syntax/AbstractNestedStructureHouseKeeper.java b/src/main/java/com/scriptbasic/syntax/AbstractNestedStructureHouseKeeper.java index f025af62..0357b75d 100644 --- a/src/main/java/com/scriptbasic/syntax/AbstractNestedStructureHouseKeeper.java +++ b/src/main/java/com/scriptbasic/syntax/AbstractNestedStructureHouseKeeper.java @@ -83,4 +83,11 @@ public boolean match(final Class expectedClass) { return expectedClass.isAssignableFrom(getElementType()); } } + + @Override + public void checkFinalState() throws AnalysisException { + if (stack.size() > 0) { + throw new BasicSyntaxException("There is at least one opened block on the stack. Block is not properly closed."); + } + } } diff --git a/src/main/java/com/scriptbasic/syntax/BasicSyntaxAnalyzer.java b/src/main/java/com/scriptbasic/syntax/BasicSyntaxAnalyzer.java index ee3c1edb..7cdcd7fb 100644 --- a/src/main/java/com/scriptbasic/syntax/BasicSyntaxAnalyzer.java +++ b/src/main/java/com/scriptbasic/syntax/BasicSyntaxAnalyzer.java @@ -5,14 +5,17 @@ public final class BasicSyntaxAnalyzer implements SyntaxAnalyzer { private final LexicalAnalyzer lexicalAnalyzer; private final CommandFactory commandFactory; - private LexicalElement lexicalElement; + private final NestedStructureHouseKeeper nestedStructureHouseKeeper; + private LexicalElement lexicalElement; - public BasicSyntaxAnalyzer(final LexicalAnalyzer lexicalAnalyzer, final CommandFactory commandFactory) { + public BasicSyntaxAnalyzer(final LexicalAnalyzer lexicalAnalyzer, final CommandFactory commandFactory, + final NestedStructureHouseKeeper nestedStructureHouseKeeper) { this.lexicalAnalyzer = lexicalAnalyzer; this.commandFactory = commandFactory; + this.nestedStructureHouseKeeper = nestedStructureHouseKeeper; } - private static boolean lineToIgnore(final String lexString) { + public static boolean lineToIgnore(final String lexString) { return lexString.equals("\n") || lexString.equals("'") || lexString.equalsIgnoreCase(ScriptBasicKeyWords.KEYWORD_REM); } @@ -49,11 +52,12 @@ public BuildableProgram analyze() throws AnalysisException { } this.lexicalElement = lexicalAnalyzer.peek(); } + nestedStructureHouseKeeper.checkFinalState(); buildableProgram.postprocess(); return buildableProgram; } - private void consumeIgnoredLine(final LexicalAnalyzer lexicalAnalyzer, String lexString) throws AnalysisException { + public static void consumeIgnoredLine(final LexicalAnalyzer lexicalAnalyzer, String lexString) throws AnalysisException { while (!lexString.equals("\n")) { final var le = lexicalAnalyzer.get(); if (le == null) { diff --git a/src/main/java/com/scriptbasic/syntax/commands/CommandAnalyzerSelect.java b/src/main/java/com/scriptbasic/syntax/commands/CommandAnalyzerSelect.java index 4412a2ed..9fc02c2a 100644 --- a/src/main/java/com/scriptbasic/syntax/commands/CommandAnalyzerSelect.java +++ b/src/main/java/com/scriptbasic/syntax/commands/CommandAnalyzerSelect.java @@ -3,33 +3,48 @@ import com.scriptbasic.context.Context; import com.scriptbasic.executors.commands.CommandSelect; import com.scriptbasic.interfaces.AnalysisException; +import com.scriptbasic.interfaces.BasicSyntaxException; import com.scriptbasic.interfaces.ScriptBasicKeyWords; import com.scriptbasic.spi.Command; +import com.scriptbasic.syntax.BasicSyntaxAnalyzer; public class CommandAnalyzerSelect - extends AbstractCommandAnalyzer -{ - - public CommandAnalyzerSelect(Context ctx) - { - super(ctx); - } - - @Override - public Command analyze() throws AnalysisException { - final var lexicalElement = ctx.lexicalAnalyzer.peek(); - // consume optional case statement - if(lexicalElement.isSymbol(ScriptBasicKeyWords.KEYWORD_CASE)) - ctx.lexicalAnalyzer.get(); - // read expression till end of line - final var expression = analyzeExpression(); - consumeEndOfLine(); - - final var node = new CommandSelect(); - node.setExpression(expression); - pushNode(node); - - return node; - } + extends AbstractCommandAnalyzer { + + public CommandAnalyzerSelect(Context ctx) { + super(ctx); + } + + @Override + public Command analyze() throws AnalysisException { + var lexicalElement = ctx.lexicalAnalyzer.peek(); + // consume optional case statement + if (lexicalElement.isSymbol(ScriptBasicKeyWords.KEYWORD_CASE)) + ctx.lexicalAnalyzer.get(); + // read expression till end of line + final var expression = analyzeExpression(); + consumeEndOfLine(); + + final var node = new CommandSelect(); + node.setExpression(expression); + pushNode(node); + + // next command has to be 'case' + // first skip any comments + lexicalElement = ctx.lexicalAnalyzer.peek(); + while (lexicalElement != null && BasicSyntaxAnalyzer.lineToIgnore(lexicalElement.getLexeme())) { + ctx.lexicalAnalyzer.get(); + BasicSyntaxAnalyzer.consumeIgnoredLine(ctx.lexicalAnalyzer, lexicalElement.getLexeme()); + lexicalElement = ctx.lexicalAnalyzer.peek(); + } + if (lexicalElement == null) { + throw new BasicSyntaxException("Preliminary end of file"); + } + if (!lexicalElement.isSymbol(ScriptBasicKeyWords.KEYWORD_CASE)) { + throw new BasicSyntaxException("Expected case statement, but found: " + lexicalElement.getLexeme()); + } + + return node; + } } diff --git a/src/test/java/com/scriptbasic/testprograms/TestPrograms.java b/src/test/java/com/scriptbasic/testprograms/TestPrograms.java index 733b1f5b..fddd53b5 100644 --- a/src/test/java/com/scriptbasic/testprograms/TestPrograms.java +++ b/src/test/java/com/scriptbasic/testprograms/TestPrograms.java @@ -61,12 +61,13 @@ public void testPrograms() throws Exception { codeTest("TestEmpty.bas", ""); codeTest("TestPrintHello.bas", "hello"); codeTest("TestIf.bas", "111"); - codeTest("TestSelect1.bas", "111111"); + codeTest("TestSelect1.bas", "1111111"); codeTest("TestSelect2.bas", "2468"); codeTest("TestSelect3.bas", "0 1 1 2 3 5 8"); testSyntaxFail("TestSelectBadSyntax1.bas"); testSyntaxFail("TestSelectBadSyntax2.bas"); testSyntaxFail("TestSelectBadSyntax3.bas"); + testSyntaxFail("TestSelectBadSyntax4.bas"); codeTest("TestBooleanConversions.bas", "111111"); codeTest("TestArrays.bas", "OK"); try { diff --git a/src/test/resources/com/scriptbasic/testprograms/TestSelect1.bas b/src/test/resources/com/scriptbasic/testprograms/TestSelect1.bas index 2692f00d..b6c40918 100644 --- a/src/test/resources/com/scriptbasic/testprograms/TestSelect1.bas +++ b/src/test/resources/com/scriptbasic/testprograms/TestSelect1.bas @@ -45,4 +45,15 @@ case is "1": print "1" case is "2": print "2" end select +' Test comments inside select case +select case v +rem comment before first case +case "0": print "0" +rem comment between case +case "1": print "1" +case "2": print "2" +rem comment before end select +end select +rem comment after end select + print "1" diff --git a/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax1.bas b/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax1.bas index 6ea11281..e904e9e0 100644 --- a/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax1.bas +++ b/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax1.bas @@ -5,3 +5,4 @@ select case v if true then print "1" +end select diff --git a/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax4.bas b/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax4.bas new file mode 100644 index 00000000..b74a1820 --- /dev/null +++ b/src/test/resources/com/scriptbasic/testprograms/TestSelectBadSyntax4.bas @@ -0,0 +1,10 @@ +' +' This program is used to test the bad syntax of +' command select case ... end select +' when the 'end select' is missing +' + +select case "1" +case "0": print "0" +case "1": print "1" +case "2": print "2"