diff --git a/docs/implemented-statements.md b/docs/implemented-statements.md index 6bb7301bc..59f09172e 100644 --- a/docs/implemented-statements.md +++ b/docs/implemented-statements.md @@ -50,7 +50,7 @@ partial - partially implemented to prevent false positives (14) | DEFINE WORK FILE | :white_check_mark: | | DELETE | :x: | | DELETE (SQL) | partial | -| DISPLAY | :x: | +| DISPLAY | :white_check_mark: | | DIVIDE | :white_check_mark: | | DOWNLOAD PC FILE | :white_check_mark: | | EJECT | :white_check_mark: | diff --git a/libs/natparse/src/main/java/org/amshove/natparse/lexing/KeywordTable.java b/libs/natparse/src/main/java/org/amshove/natparse/lexing/KeywordTable.java index 5ebc51151..d9d952894 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/lexing/KeywordTable.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/lexing/KeywordTable.java @@ -62,6 +62,7 @@ public static SyntaxKind getKeyword(String possibleKeyword) case "calling" -> SyntaxKind.CALLING; case "callnat" -> SyntaxKind.CALLNAT; case "cap" -> SyntaxKind.CAP; + case "capt" -> SyntaxKind.CAPT; case "captioned" -> SyntaxKind.CAPTIONED; case "case" -> SyntaxKind.CASE; case "cc" -> SyntaxKind.CC; diff --git a/libs/natparse/src/main/java/org/amshove/natparse/lexing/SyntaxKind.java b/libs/natparse/src/main/java/org/amshove/natparse/lexing/SyntaxKind.java index 833d0e039..ab531c4e6 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/lexing/SyntaxKind.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/lexing/SyntaxKind.java @@ -345,6 +345,7 @@ public enum SyntaxKind CABINET(true, false, false), CALLING(true, false, false), CAP(true, false, false), + CAPT(true, false, false), CAPTIONED(true, false, false), CASE(true, false, false), CC(true, false, false), @@ -820,6 +821,7 @@ public boolean isAttribute() this == PM || this == PS || this == SB || + this == SF || this == SG || this == TC || this == TCU || diff --git a/libs/natparse/src/main/java/org/amshove/natparse/natural/IDisplayNode.java b/libs/natparse/src/main/java/org/amshove/natparse/natural/IDisplayNode.java index a83bb55de..0f5eb012c 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/natural/IDisplayNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/natural/IDisplayNode.java @@ -1,4 +1,4 @@ package org.amshove.natparse.natural; -public interface IDisplayNode extends IStatementNode, ICanHaveReportSpecification +public interface IDisplayNode extends IOutputStatementNode, ICanHaveReportSpecification {} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/natural/IInputStatementNode.java b/libs/natparse/src/main/java/org/amshove/natparse/natural/IInputStatementNode.java index 80d3d4d36..5ad0dba6c 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/natural/IInputStatementNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/natural/IInputStatementNode.java @@ -1,11 +1,4 @@ package org.amshove.natparse.natural; -import org.amshove.natparse.ReadOnlyList; -import org.amshove.natparse.natural.output.IOutputElementNode; - -public interface IInputStatementNode extends IStatementNode -{ - ReadOnlyList operands(); - - ReadOnlyList statementAttributes(); -} +public interface IInputStatementNode extends IOutputStatementNode +{} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/natural/IOutputStatementNode.java b/libs/natparse/src/main/java/org/amshove/natparse/natural/IOutputStatementNode.java new file mode 100644 index 000000000..03fb18552 --- /dev/null +++ b/libs/natparse/src/main/java/org/amshove/natparse/natural/IOutputStatementNode.java @@ -0,0 +1,11 @@ +package org.amshove.natparse.natural; + +import org.amshove.natparse.ReadOnlyList; +import org.amshove.natparse.natural.output.IOutputElementNode; + +public interface IOutputStatementNode extends IStatementNode +{ + ReadOnlyList statementAttributes(); + + ReadOnlyList operands(); +} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/natural/IPrintNode.java b/libs/natparse/src/main/java/org/amshove/natparse/natural/IPrintNode.java index 352fa203c..7cbcc7259 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/natural/IPrintNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/natural/IPrintNode.java @@ -1,4 +1,4 @@ package org.amshove.natparse.natural; -public interface IPrintNode extends IStatementNode, ICanHaveReportSpecification +public interface IPrintNode extends IOutputStatementNode, ICanHaveReportSpecification {} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/natural/IWriteNode.java b/libs/natparse/src/main/java/org/amshove/natparse/natural/IWriteNode.java index e4d4d4c1b..8173b1190 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/natural/IWriteNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/natural/IWriteNode.java @@ -1,11 +1,4 @@ package org.amshove.natparse.natural; -import org.amshove.natparse.ReadOnlyList; -import org.amshove.natparse.natural.output.IOutputElementNode; - -public interface IWriteNode extends IStatementNode, ICanHaveReportSpecification -{ - ReadOnlyList statementAttributes(); - - ReadOnlyList operands(); -} +public interface IWriteNode extends IOutputStatementNode, ICanHaveReportSpecification +{} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/DisplayNode.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/DisplayNode.java index 6a6a0a7de..7f5c855c0 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/DisplayNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/DisplayNode.java @@ -1,23 +1,6 @@ package org.amshove.natparse.parsing; -import org.amshove.natparse.lexing.SyntaxToken; import org.amshove.natparse.natural.IDisplayNode; -import java.util.Optional; - -class DisplayNode extends StatementNode implements IDisplayNode, ICanSetReportSpecification -{ - private SyntaxToken reportSpecification; - - @Override - public Optional reportSpecification() - { - return Optional.ofNullable(reportSpecification); - } - - @Override - public void setReportSpecification(SyntaxToken token) - { - reportSpecification = token; - } -} +class DisplayNode extends OutputStatementNode implements IDisplayNode +{} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/OutputStatementNode.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/OutputStatementNode.java new file mode 100644 index 000000000..feefbd187 --- /dev/null +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/OutputStatementNode.java @@ -0,0 +1,58 @@ +package org.amshove.natparse.parsing; + +import org.amshove.natparse.ReadOnlyList; +import org.amshove.natparse.lexing.SyntaxToken; +import org.amshove.natparse.natural.*; +import org.amshove.natparse.natural.output.IOutputElementNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +class OutputStatementNode extends StatementNode implements IOutputStatementNode, ICanSetReportSpecification, ICanHaveReportSpecification +{ + private SyntaxToken reportSpecification; + private final List operands = new ArrayList<>(); + private IAttributeListNode statementAttributes; + + @Override + public Optional reportSpecification() + { + return Optional.ofNullable(reportSpecification); + } + + @Override + public void setReportSpecification(SyntaxToken token) + { + reportSpecification = token; + } + + @Override + public ReadOnlyList operands() + { + return ReadOnlyList.from(operands); + } + + @Override + public ReadOnlyList statementAttributes() + { + return statementAttributes != null ? statementAttributes.attributes() : ReadOnlyList.empty(); + } + + void addOperand(IOutputElementNode operand) + { + if (operand == null) + { + // stuff like tab setting, line skip etc. + return; + } + + addNode((BaseSyntaxNode) operand); + operands.add(operand); + } + + void setAttributes(IAttributeListNode attributes) + { + statementAttributes = attributes; + } +} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/PrintNode.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/PrintNode.java new file mode 100644 index 000000000..21b4358d0 --- /dev/null +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/PrintNode.java @@ -0,0 +1,6 @@ +package org.amshove.natparse.parsing; + +import org.amshove.natparse.natural.IPrintNode; + +public class PrintNode extends OutputStatementNode implements IPrintNode +{} diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/StatementListParser.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/StatementListParser.java index babea0f67..a03adb3cc 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/StatementListParser.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/StatementListParser.java @@ -2134,34 +2134,6 @@ private StatementNode examineTranslate(ExamineNode examine) throws ParseError return examine; } - private StatementNode display() throws ParseError - { - var display = new DisplayNode(); - consumeMandatory(display, SyntaxKind.DISPLAY); - - if (consumeOptionally(display, SyntaxKind.LPAREN)) - { - if (peek().kind().canBeIdentifier() && peekKind(1, SyntaxKind.RPAREN)) - { - var token = consumeMandatoryIdentifier(display); - display.setReportSpecification(token); - } - else - { - // currently consume everything until closing parenthesis to consume things like attribute definition etc. - while (!peekKind(SyntaxKind.RPAREN)) - { - consume(display); - } - } - consumeMandatory(display, SyntaxKind.RPAREN); - } - - // TODO: Parse options - - return display; - } - private StatementNode inputStatement() throws ParseError { var input = new InputStatementNode(); @@ -2299,7 +2271,7 @@ private void checkOutputElementAttributes(IOutputElementNode operand) private StatementNode write(SyntaxKind statementKind) throws ParseError { - var write = new WriteNode(); + OutputStatementNode write = statementKind == SyntaxKind.WRITE ? new WriteNode() : new PrintNode(); consumeMandatory(write, statementKind); if (peekKind(SyntaxKind.LPAREN)) { @@ -2337,8 +2309,56 @@ private StatementNode write(SyntaxKind statementKind) throws ParseError return write; } + private static final Set OPTIONAL_DISPLAY_FLAGS = Set.of(SyntaxKind.NOTITLE, SyntaxKind.NOTIT, SyntaxKind.NOHDR, SyntaxKind.AND, SyntaxKind.GIVE, SyntaxKind.SYSTEM, SyntaxKind.FUNCTIONS); + private static final Set DISPLAY_OUTPUT_FORMATS = Set.of(SyntaxKind.VERT, SyntaxKind.VERTICALLY, SyntaxKind.HORIZ, SyntaxKind.HORIZONTALLY, SyntaxKind.AS, SyntaxKind.CAPT, SyntaxKind.CAPTIONED); + + private StatementNode display() throws ParseError + { + var display = new DisplayNode(); + consumeMandatory(display, SyntaxKind.DISPLAY); + + if (peekKind(SyntaxKind.LPAREN) && !isOutputElementAttribute(peek(1).kind())) + { + consumeMandatory(display, SyntaxKind.LPAREN); + var token = consume(display); + display.setReportSpecification(token); + consumeMandatory(display, SyntaxKind.RPAREN); + } + + if (peekKind(SyntaxKind.LPAREN) && isOutputElementAttribute(peek(1).kind())) + { + var attributeList = consumeAttributeList(display); + display.setAttributes(attributeList); + } + + while (consumeAnyOptionally(display, OPTIONAL_DISPLAY_FLAGS)) + { + // advances automatically + } + + while (!isAtEnd() && !isStatementStart()) + { + while (consumeAnyOptionally(display, DISPLAY_OUTPUT_FORMATS)) + { + // advances automatically + } + + if (!(isOperand() || peekKind(SyntaxKind.TAB_SETTING) || peekKind(SyntaxKind.SLASH) || peekKind(SyntaxKind.OPERAND_SKIP))) + { + break; + } + + var operand = consumeInputOutputOperand(display); + display.addOperand(operand); + checkOutputElementAttributes(operand); + } + + return display; + } + private IOutputElementNode consumeInputOutputOperand(BaseSyntaxNode writeLikeNode) throws ParseError { + if (peekKind(SyntaxKind.TAB_SETTING)) { var tab = new TabulatorElementNode(); @@ -4880,8 +4900,18 @@ private boolean isInputElementAttribute(SyntaxKind kind) { return switch (kind) { - case AD, AL, CD, CV, DF, DL, DY, EM, EMU, FL, HE, IP, NL, PM, SB, SG, ZP -> true; + case AD, AL, CD, CV, DF, DL, DY, EM, EMU, FL, HE, IP, IS, NL, PM, SB, SG, ZP -> true; default -> false; }; } + + private boolean isOutputElementAttribute(SyntaxKind kind) + { + return switch (kind) + { + case AD, AL, CD, CV, DF, DL, DY, EM, EMU, ES, FC, FL, GC, HC, HW, IC, ICU, IS, LC, LCU, LS, MC, MP, NL, PC, PM, PS, SF, SG, TC, TCU, UC, ZP -> true; + default -> false; + }; + } + } diff --git a/libs/natparse/src/main/java/org/amshove/natparse/parsing/WriteNode.java b/libs/natparse/src/main/java/org/amshove/natparse/parsing/WriteNode.java index 84e091ff9..bde60669e 100644 --- a/libs/natparse/src/main/java/org/amshove/natparse/parsing/WriteNode.java +++ b/libs/natparse/src/main/java/org/amshove/natparse/parsing/WriteNode.java @@ -1,58 +1,6 @@ package org.amshove.natparse.parsing; -import org.amshove.natparse.ReadOnlyList; -import org.amshove.natparse.lexing.SyntaxToken; -import org.amshove.natparse.natural.*; -import org.amshove.natparse.natural.output.IOutputElementNode; +import org.amshove.natparse.natural.IWriteNode; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -class WriteNode extends StatementNode implements IWriteNode, IPrintNode, ICanSetReportSpecification -{ - private SyntaxToken reportSpecification; - private final List operands = new ArrayList<>(); - private IAttributeListNode statementAttributes; - - @Override - public Optional reportSpecification() - { - return Optional.ofNullable(reportSpecification); - } - - @Override - public void setReportSpecification(SyntaxToken token) - { - reportSpecification = token; - } - - @Override - public ReadOnlyList operands() - { - return ReadOnlyList.from(operands); - } - - @Override - public ReadOnlyList statementAttributes() - { - return statementAttributes != null ? statementAttributes.attributes() : ReadOnlyList.empty(); - } - - void addOperand(IOutputElementNode operand) - { - if (operand == null) - { - // stuff like tab setting, line skip etc. - return; - } - - addNode((BaseSyntaxNode) operand); - operands.add(operand); - } - - void setAttributes(IAttributeListNode attributes) - { - statementAttributes = attributes; - } -} +class WriteNode extends OutputStatementNode implements IWriteNode +{} diff --git a/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForAttributeControlsShould.java b/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForAttributeControlsShould.java index 59c0092eb..798d295d2 100644 --- a/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForAttributeControlsShould.java +++ b/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForAttributeControlsShould.java @@ -50,6 +50,7 @@ Stream recognizeAttributes() SyntaxKind.PM, SyntaxKind.PS, SyntaxKind.SB, + SyntaxKind.SF, SyntaxKind.SG, SyntaxKind.TC, SyntaxKind.TCU, diff --git a/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForKeywordsShould.java b/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForKeywordsShould.java index 3bf3d42c3..a36a191b3 100644 --- a/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForKeywordsShould.java +++ b/libs/natparse/src/test/java/org/amshove/natparse/lexing/LexerForKeywordsShould.java @@ -75,6 +75,7 @@ Iterable lexKCheckReservedKeywords() keywordTest("CALLING", SyntaxKind.CALLING), keywordTest("CALLNAT", SyntaxKind.CALLNAT), keywordTest("CAP", SyntaxKind.CAP), + keywordTest("CAPT", SyntaxKind.CAPT), keywordTest("CAPTIONED", SyntaxKind.CAPTIONED), keywordTest("CASE", SyntaxKind.CASE), keywordTest("CC", SyntaxKind.CC), diff --git a/libs/natparse/src/test/java/org/amshove/natparse/parsing/StatementListParserShould.java b/libs/natparse/src/test/java/org/amshove/natparse/parsing/StatementListParserShould.java index 6269ad683..c8cf40600 100644 --- a/libs/natparse/src/test/java/org/amshove/natparse/parsing/StatementListParserShould.java +++ b/libs/natparse/src/test/java/org/amshove/natparse/parsing/StatementListParserShould.java @@ -1978,21 +1978,63 @@ void parseDisplay() } @Test - void parseDisplayWithReportSpecification() + void parseDisplayWithIsEqualsOn() { - var display = assertParsesSingleStatement("DISPLAY (PR2)", IDisplayNode.class); + var display = assertParsesSingleStatement("DISPLAY #VAR (IS=ON)", IDisplayNode.class); + assertThat(display.descendants()).hasSize(2); + } + + @ParameterizedTest + @ValueSource(strings = + { + "PR2", + "CC", + "ZD" + }) + void parseDisplayWithReportSpecification(String rep) + { + var display = assertParsesSingleStatement("DISPLAY (%s)".formatted(rep), IDisplayNode.class); assertThat(display.reportSpecification()).isPresent(); - assertThat(display.reportSpecification().get().symbolName()).isEqualTo("PR2"); + assertThat(display.reportSpecification().get().symbolName()).isEqualTo("%s".formatted(rep)); assertThat(display.descendants()).hasSize(4); } + @ParameterizedTest + @ValueSource(strings = + { + "'HDR' #VAR1", + "(01) (AL=L) #VAR1 #VAR2", + "(00) NOTIT NOHDR #VAR1 #VAR2", + "NOTIT NOHDR (ZP=OFF SG=ON) #VAR1 #VAR2", + }) + void parseMoreComplexDisplays(String statement) + { + assertParsesSingleStatement("DISPLAY %s".formatted(statement), IDisplayNode.class); + } + @Test - void parseDisplayWithReportSpecificationAsAttribute() + void parseAVeryComplexDisplay() { - var display = assertParsesSingleStatement("DISPLAY (CC)", IDisplayNode.class); + var display = assertParsesSingleStatement(""" + DISPLAY (10) (ZP=ON) NOTITLE NOHDR AND GIVE FUNCTIONS #VAR1 (2,#IX) + /// T*#VAR1 '='#VAR2 + 10X 'Hey' + '>'(20) '=' #VAR3 + 2/47 'Yes!' + / VERTICALLY AS '=' #VAR4 CAPTIONED + / HORIZ AS #VAR5 CAPT + / P*#VAR 'X' (I) + """, IDisplayNode.class); + + var firstOperand = assertNodeType(display.operands().first(), IOutputOperandNode.class).operand(); + var firstReference = assertIsVariableReference(firstOperand, "#VAR1"); + assertThat(firstReference.dimensions()).hasSize(2); + assertLiteral(firstReference.dimensions().first(), SyntaxKind.NUMBER_LITERAL); + var firstAttr = assertNodeType(display.statementAttributes().first(), IValueAttributeNode.class); + assertThat(firstAttr.kind().name().equals("ZP")); + assertThat(firstAttr.value().equals("ON")); assertThat(display.reportSpecification()).isPresent(); - assertThat(display.reportSpecification().get().symbolName()).isEqualTo("CC"); - assertThat(display.descendants()).hasSize(4); + assertThat(display.reportSpecification().get().symbolName()).isEqualTo("10"); } @Test