Add lint validation for break/continue statement outside the loop body

This commit is contained in:
aNNiMON 2023-10-24 19:35:40 +03:00 committed by Victor Melnik
parent ec04e5faca
commit cef845f0f8
10 changed files with 125 additions and 21 deletions

View File

@ -17,8 +17,11 @@ public class ErrorsLocationFormatterStage implements Stage<Iterable<? extends So
for (SourceLocatedError error : input) { for (SourceLocatedError error : input) {
sb.append(Console.newline()); sb.append(Console.newline());
sb.append(error); sb.append(error);
sb.append(Console.newline());
final Range range = error.getRange(); final Range range = error.getRange();
if (range != null) {
sb.append(' ').append(range.format());
}
sb.append(Console.newline());
if (range != null && lines.length > 0) { if (range != null && lines.length > 0) {
var positions = stagesData.getOrDefault(TAG_POSITIONS, HashSet::new); var positions = stagesData.getOrDefault(TAG_POSITIONS, HashSet::new);
positions.add(range); positions.add(range);

View File

@ -135,10 +135,10 @@ public final class Parser {
return doWhileStatement(); return doWhileStatement();
} }
if (match(TokenType.BREAK)) { if (match(TokenType.BREAK)) {
return new BreakStatement(); return new BreakStatement(getRange(index - 1, index - 1));
} }
if (match(TokenType.CONTINUE)) { if (match(TokenType.CONTINUE)) {
return new ContinueStatement(); return new ContinueStatement(getRange(index - 1, index - 1));
} }
if (match(TokenType.RETURN)) { if (match(TokenType.RETURN)) {
return new ReturnStatement(expression()); return new ReturnStatement(expression());

View File

@ -1,12 +1,24 @@
package com.annimon.ownlang.parser.ast; package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.lib.Value; import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.util.Range;
import com.annimon.ownlang.util.SourceLocation;
/** /**
* *
* @author aNNiMON * @author aNNiMON
*/ */
public final class BreakStatement extends RuntimeException implements Statement { public final class BreakStatement extends RuntimeException implements Statement, SourceLocation {
private final Range range;
public BreakStatement(Range range) {
this.range = range;
}
@Override
public Range getRange() {
return range;
}
@Override @Override
public Value eval() { public Value eval() {

View File

@ -1,12 +1,24 @@
package com.annimon.ownlang.parser.ast; package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.lib.Value; import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.util.Range;
import com.annimon.ownlang.util.SourceLocation;
/** /**
* *
* @author aNNiMON * @author aNNiMON
*/ */
public final class ContinueStatement extends RuntimeException implements Statement { public final class ContinueStatement extends RuntimeException implements Statement, SourceLocation {
private final Range range;
public ContinueStatement(Range range) {
this.range = range;
}
@Override
public Range getRange() {
return range;
}
@Override @Override
public Value eval() { public Value eval() {

View File

@ -35,8 +35,8 @@ final class AssignValidator extends LintVisitor {
} }
@Override @Override
public void visit(UseStatement st) { public void visit(UseStatement s) {
super.visit(st); super.visit(s);
moduleConstants.addAll(st.loadConstants().keySet()); moduleConstants.addAll(s.loadConstants().keySet());
} }
} }

View File

@ -22,14 +22,14 @@ final class DefaultFunctionsOverrideValidator extends LintVisitor {
} }
@Override @Override
public void visit(IncludeStatement st) { public void visit(IncludeStatement s) {
super.visit(st); super.visit(s);
applyVisitor(st, this); applyVisitor(s, this);
} }
@Override @Override
public void visit(UseStatement st) { public void visit(UseStatement s) {
super.visit(st); super.visit(s);
moduleFunctions.addAll(st.loadFunctions().keySet()); moduleFunctions.addAll(s.loadFunctions().keySet());
} }
} }

View File

@ -27,6 +27,7 @@ public class LinterStage implements Stage<Node, Node> {
final LinterResults results = new LinterResults(); final LinterResults results = new LinterResults();
final List<Visitor> validators = new ArrayList<>(); final List<Visitor> validators = new ArrayList<>();
validators.add(new IncludeSourceValidator(results)); validators.add(new IncludeSourceValidator(results));
validators.add(new LoopStatementsValidator(results));
if (mode == Mode.SEMANTIC) { if (mode == Mode.SEMANTIC) {
validators.forEach(input::accept); validators.forEach(input::accept);

View File

@ -0,0 +1,76 @@
package com.annimon.ownlang.parser.linters;
import com.annimon.ownlang.parser.ast.*;
import java.util.ArrayDeque;
import java.util.Deque;
final class LoopStatementsValidator extends LintVisitor {
private enum LoopScope { FOR, WHILE, DO_WHILE, FOREACH_ARR, FOREACH_MAP };
private final Deque<LoopScope> loopScope;
LoopStatementsValidator(LinterResults results) {
super(results);
loopScope = new ArrayDeque<>(10);
}
@Override
public void visit(ForStatement s) {
s.initialization.accept(this);
s.termination.accept(this);
s.increment.accept(this);
loopScope.push(LoopScope.FOR);
s.statement.accept(this);
loopScope.remove();
}
@Override
public void visit(DoWhileStatement s) {
s.condition.accept(this);
loopScope.push(LoopScope.DO_WHILE);
s.statement.accept(this);
loopScope.remove();
}
@Override
public void visit(ForeachArrayStatement s) {
s.container.accept(this);
loopScope.push(LoopScope.FOREACH_ARR);
s.body.accept(this);
loopScope.remove();
}
@Override
public void visit(ForeachMapStatement s) {
s.container.accept(this);
loopScope.push(LoopScope.FOREACH_MAP);
s.body.accept(this);
loopScope.remove();
}
@Override
public void visit(WhileStatement s) {
s.condition.accept(this);
loopScope.push(LoopScope.WHILE);
s.statement.accept(this);
loopScope.remove();
}
@Override
public void visit(BreakStatement s) {
if (loopScope.isEmpty()) {
results.add(LinterResult.error(
"break statement shouldn't be placed outside the loop body",
s.getRange()));
}
}
@Override
public void visit(ContinueStatement s) {
if (loopScope.isEmpty()) {
results.add(LinterResult.error(
"continue statement shouldn't be placed outside the loop body",
s.getRange()));
}
}
}

View File

@ -185,13 +185,13 @@ public abstract class AbstractVisitor implements Visitor {
} }
@Override @Override
public void visit(WhileStatement st) { public void visit(WhileStatement s) {
st.condition.accept(this); s.condition.accept(this);
st.statement.accept(this); s.statement.accept(this);
} }
@Override @Override
public void visit(UseStatement st) { public void visit(UseStatement s) {
} }
} }

View File

@ -19,8 +19,8 @@ public class ModuleDetector extends AbstractVisitor {
} }
@Override @Override
public void visit(UseStatement st) { public void visit(UseStatement s) {
modules.addAll(st.modules); modules.addAll(s.modules);
super.visit(st); super.visit(s);
} }
} }