Skip to content

Commit 31a0c40

Browse files
authored
Merge pull request #12 from PetrPytelka/single_line_if_statement
Add support for single line statement (if .... then ... [else ...])
2 parents 7f35fa6 + 103d740 commit 31a0c40

21 files changed

Lines changed: 266 additions & 33 deletions

src/main/java/com/scriptbasic/interfaces/LexicalElement.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ public interface LexicalElement extends SourceLocationBound {
8080
Boolean isSymbol(String lexeme);
8181

8282
/**
83-
* Return true if the lexical element is colon
84-
*
8583
* @return true if the lexical element is colon
8684
*/
8785
Boolean isStatementSeparator();

src/main/java/com/scriptbasic/interfaces/NestedStructureHouseKeeper.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,24 @@
1010
* date June 8, 2012
1111
*/
1212
public interface NestedStructureHouseKeeper {
13+
14+
static public enum EndOfStatementResult {
15+
/**
16+
* End of statement was consumed. Other processors should not be called.
17+
*/
18+
CONSUMED,
19+
20+
/**
21+
* End of statement was processed. Other processors should be notified.
22+
*/
23+
PROCESSED
24+
}
25+
26+
static public interface EndOfStatementProcessor {
27+
28+
EndOfStatementResult consumeEndOfStatement() throws AnalysisException;
29+
30+
}
1331

1432
/**
1533
* Push a nested structure object on the housekeeping stack.
@@ -61,4 +79,18 @@ <T extends NestedStructure> T pop(Class<T> expectedClass)
6179
* @throws AnalysisException when there are some elements on the stack
6280
*/
6381
void checkFinalState() throws AnalysisException;
82+
83+
/**
84+
* Checks that there are no extra characters when the line analyzer
85+
* expects it has finished analyzing the statement. If there are
86+
* some extra characters on the line then throws syntax error exception.
87+
* Otherwise it simply steps the lexical analyzer iterator over the symbol.
88+
*
89+
* @throws AnalysisException when there are extra character on the actual line
90+
*/
91+
void consumeEndOfStatement() throws AnalysisException;
92+
93+
void pushEndOfStatementProcessor(EndOfStatementProcessor endOfStatementProcessor);
94+
95+
EndOfStatementProcessor popEndOfStatementProcessor();
6496
}

src/main/java/com/scriptbasic/interfaces/SyntaxAnalyzer.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.scriptbasic.interfaces;
22

3+
import com.scriptbasic.spi.Command;
34

45
/**
56
* A syntax analyzer analyzes a program source using the result of the lexical
@@ -17,4 +18,11 @@ public interface SyntaxAnalyzer {
1718
*/
1819
BuildableProgram analyze() throws AnalysisException;
1920

21+
/**
22+
* Add command to the currently build program
23+
*
24+
* @param command Command to be added to the current program
25+
*/
26+
void addCommand(Command command);
27+
2028
}

src/main/java/com/scriptbasic/syntax/AbstractNestedStructureHouseKeeper.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
import com.scriptbasic.interfaces.*;
55
import com.scriptbasic.log.Logger;
66
import com.scriptbasic.log.LoggerFactory;
7+
import com.scriptbasic.utility.SyntaxExceptionUtility;
78

9+
import java.util.ArrayList;
10+
import java.util.List;
811
import java.util.Stack;
912

1013
public abstract class AbstractNestedStructureHouseKeeper implements NestedStructureHouseKeeper {
@@ -17,6 +20,7 @@ public <T> boolean match(final Class<T> expectedClass) {
1720
private final Stack<Structure> stack = new Stack<>();
1821
private final LexicalAnalyzer analyzer;
1922
private boolean stackIsHealthy = true;
23+
private final Stack<EndOfStatementProcessor> endOfStatementProcessors = new Stack<>();
2024

2125
protected AbstractNestedStructureHouseKeeper(final LexicalAnalyzer analyzer) {
2226
this.analyzer = analyzer;
@@ -89,5 +93,47 @@ public void checkFinalState() throws AnalysisException {
8993
if (stack.size() > 0) {
9094
throw new BasicSyntaxException("There is at least one opened block on the stack. Block is not properly closed.");
9195
}
96+
if (endOfStatementProcessors.size() > 0) {
97+
throw new BasicSyntaxException("There is at least one unfinished statement.");
98+
}
99+
}
100+
101+
@Override
102+
public void consumeEndOfStatement() throws AnalysisException {
103+
104+
final var numOfProcessors = endOfStatementProcessors.size();
105+
if(numOfProcessors>0) {
106+
// Copy processors and revert order
107+
// Note: Processors might be unregistered while iterating them
108+
List<EndOfStatementProcessor> processors = new ArrayList<>(numOfProcessors);
109+
final var iter = endOfStatementProcessors.listIterator(endOfStatementProcessors.size());
110+
while(iter.hasPrevious()) {
111+
processors.add(iter.previous());
112+
}
113+
// Run processors
114+
for(final var processor: processors) {
115+
final var result = processor.consumeEndOfStatement();
116+
if(result==EndOfStatementResult.CONSUMED) {
117+
return;
118+
}
119+
}
120+
}
121+
122+
final var le = analyzer.get();
123+
if (le != null && !(le.isLineTerminator() || le.isStatementSeparator())) {
124+
SyntaxExceptionUtility.throwSyntaxException(
125+
"There are extra characters following the expression.", le);
126+
}
127+
}
128+
129+
@Override
130+
public void pushEndOfStatementProcessor(EndOfStatementProcessor endOfStatementProcessor)
131+
{
132+
endOfStatementProcessors.push(endOfStatementProcessor);
133+
}
134+
135+
@Override
136+
public EndOfStatementProcessor popEndOfStatementProcessor() {
137+
return endOfStatementProcessors.pop();
92138
}
93139
}

src/main/java/com/scriptbasic/syntax/BasicSyntaxAnalyzer.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.scriptbasic.syntax;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
5+
36
import com.scriptbasic.interfaces.*;
7+
import com.scriptbasic.spi.Command;
48

59
public final class BasicSyntaxAnalyzer implements SyntaxAnalyzer {
610
private final LexicalAnalyzer lexicalAnalyzer;
711
private final CommandFactory commandFactory;
812
private final NestedStructureHouseKeeper nestedStructureHouseKeeper;
9-
private LexicalElement lexicalElement;
13+
private LexicalElement lexicalElement;
14+
private List<Command> additionalCommands;
1015

1116
public BasicSyntaxAnalyzer(final LexicalAnalyzer lexicalAnalyzer, final CommandFactory commandFactory,
1217
final NestedStructureHouseKeeper nestedStructureHouseKeeper) {
@@ -50,12 +55,24 @@ public BuildableProgram analyze() throws AnalysisException {
5055
buildableProgram.addCommand(newCommand);
5156
}
5257
}
58+
if(additionalCommands!=null) {
59+
additionalCommands.forEach(buildableProgram::addCommand);
60+
additionalCommands = null;
61+
}
5362
this.lexicalElement = lexicalAnalyzer.peek();
5463
}
5564
nestedStructureHouseKeeper.checkFinalState();
5665
buildableProgram.postprocess();
5766
return buildableProgram;
5867
}
68+
69+
@Override
70+
public void addCommand(final Command command) {
71+
if(additionalCommands==null) {
72+
additionalCommands = new ArrayList<>();
73+
}
74+
additionalCommands.add(command);
75+
}
5976

6077
public static void consumeIgnoredLine(final LexicalAnalyzer lexicalAnalyzer, String lexString) throws AnalysisException {
6178
while (!lexString.equals("\n")) {

src/main/java/com/scriptbasic/syntax/commands/AbstractCommandAnalyzer.java

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import com.scriptbasic.spi.LeftValue;
88
import com.scriptbasic.spi.LeftValueList;
99
import com.scriptbasic.syntax.AbstractAnalyzer;
10-
import com.scriptbasic.utility.SyntaxExceptionUtility;
1110

1211
public abstract class AbstractCommandAnalyzer extends AbstractAnalyzer<Command>
1312
implements CommandAnalyzer {
@@ -101,11 +100,6 @@ protected boolean isKeyWord(final String keyword) throws AnalysisException {
101100
* @throws AnalysisException when there are extra character on the actual line
102101
*/
103102
protected void consumeEndOfStatement() throws AnalysisException {
104-
final var le = ctx.lexicalAnalyzer.get();
105-
if (le != null && !(le.isLineTerminator() || le.isStatementSeparator())) {
106-
SyntaxExceptionUtility.throwSyntaxException(
107-
"There are extra characters following the expression after the '"
108-
+ getName() + "' keyword", le);
109-
}
103+
ctx.nestedStructureHouseKeeper.consumeEndOfStatement();
110104
}
111105
}

src/main/java/com/scriptbasic/syntax/commands/AbstractCommandAnalyzerIfElseKind.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ protected void registerAndSwapNode(final AbstractCommandIfElseKind node)
2121
pushNode(node);
2222
}
2323

24-
protected void registerAndPopNode(final AbstractCommandIfElseKind node)
24+
protected AbstractCommandIfElseKind registerAndPopNode(final AbstractCommandIfElseKind node)
2525
throws AnalysisException {
26-
ctx.nestedStructureHouseKeeper.pop(AbstractCommandIfElseKind.class).setNext(node);
26+
var command = ctx.nestedStructureHouseKeeper.pop(AbstractCommandIfElseKind.class);
27+
command.setNext(node);
28+
return command;
2729
}
2830

2931
}

src/main/java/com/scriptbasic/syntax/commands/AbstractCommandAnalyzerIfKind.java

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.scriptbasic.interfaces.AnalysisException;
55
import com.scriptbasic.interfaces.Expression;
66
import com.scriptbasic.interfaces.ScriptBasicKeyWords;
7-
import com.scriptbasic.spi.Command;
87

98
/**
109
* @author Peter Verhas
@@ -16,22 +15,14 @@ public AbstractCommandAnalyzerIfKind(final Context ctx) {
1615
super(ctx);
1716
}
1817

19-
protected abstract Command createNode(Expression condition) throws AnalysisException;
20-
21-
/*
22-
* (non-Javadoc)
23-
*
24-
* @see com.scriptbasic.interfaces.Analyzer#analyze()
18+
/**
19+
* Analyse expression and THEN keyword
20+
* @return expression for IF statement
21+
* @throws AnalysisException error when missing then keyword or failed to parse expression
2522
*/
26-
@Override
27-
public Command analyze() throws AnalysisException {
28-
return createNode(analizeLine());
29-
}
30-
31-
protected Expression analizeLine() throws AnalysisException {
23+
protected Expression analyzeCondition() throws AnalysisException {
3224
final var condition = analyzeExpression();
3325
assertKeyWord(ScriptBasicKeyWords.KEYWORD_THEN);
34-
consumeEndOfStatement();
3526
return condition;
3627
}
3728

src/main/java/com/scriptbasic/syntax/commands/CommandAnalyzerElse.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.scriptbasic.syntax.commands;
22

33
import com.scriptbasic.context.Context;
4+
import com.scriptbasic.executors.commands.AbstractCommandIfElseKind;
45
import com.scriptbasic.executors.commands.CommandElse;
56
import com.scriptbasic.interfaces.AnalysisException;
7+
import com.scriptbasic.interfaces.BasicSyntaxException;
68
import com.scriptbasic.spi.Command;
79

810
public class CommandAnalyzerElse extends AbstractCommandAnalyzerIfElseKind {
@@ -14,8 +16,12 @@ public CommandAnalyzerElse(final Context ctx) {
1416
@Override
1517
public Command analyze() throws AnalysisException {
1618
final var node = new CommandElse();
19+
AbstractCommandIfElseKind prevNode = registerAndPopNode(node);
20+
if(prevNode instanceof CommandElse) {
21+
throw new BasicSyntaxException("Multiple 'else' statements", ctx.lexicalAnalyzer.peek());
22+
}
23+
pushNode(node);
1724
consumeEndOfStatement();
18-
registerAndSwapNode(node);
1925
return node;
2026
}
2127

src/main/java/com/scriptbasic/syntax/commands/CommandAnalyzerElseIf.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.scriptbasic.context.Context;
44
import com.scriptbasic.executors.commands.CommandElseIf;
55
import com.scriptbasic.interfaces.AnalysisException;
6-
import com.scriptbasic.interfaces.Expression;
76
import com.scriptbasic.spi.Command;
87

98
/**
@@ -16,10 +15,15 @@ public CommandAnalyzerElseIf(final Context ctx) {
1615
super(ctx);
1716
}
1817

19-
protected Command createNode(final Expression condition) throws AnalysisException {
18+
@Override
19+
public Command analyze() throws AnalysisException {
20+
21+
final var condition = analyzeCondition();
22+
2023
final var node = new CommandElseIf();
2124
node.setCondition(condition);
2225
registerAndSwapNode(node);
26+
consumeEndOfStatement();
2327
return node;
2428
}
2529

0 commit comments

Comments
 (0)