mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Add semantic linter as a required stage
This commit is contained in:
parent
1535e86472
commit
35971e874b
@ -0,0 +1,27 @@
|
|||||||
|
package com.annimon.ownlang.util.input;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public record InputSourceDetector() {
|
||||||
|
public static final String RESOURCE_PREFIX = "resource:";
|
||||||
|
|
||||||
|
public boolean isReadable(String programPath) {
|
||||||
|
if (programPath.startsWith(RESOURCE_PREFIX)) {
|
||||||
|
String path = programPath.substring(RESOURCE_PREFIX.length());
|
||||||
|
return getClass().getResource(path) != null;
|
||||||
|
} else {
|
||||||
|
Path path = Path.of(programPath);
|
||||||
|
return Files.isReadable(path) && Files.isRegularFile(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputSource toInputSource(String programPath) {
|
||||||
|
if (programPath.startsWith(RESOURCE_PREFIX)) {
|
||||||
|
String path = programPath.substring(RESOURCE_PREFIX.length());
|
||||||
|
return new InputSourceResource(path);
|
||||||
|
} else {
|
||||||
|
return new InputSourceFile(programPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import com.annimon.ownlang.utils.Sandbox;
|
|||||||
import com.annimon.ownlang.utils.TimeMeasurement;
|
import com.annimon.ownlang.utils.TimeMeasurement;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,8 +73,13 @@ public final class Main {
|
|||||||
|
|
||||||
case "-l":
|
case "-l":
|
||||||
case "--lint":
|
case "--lint":
|
||||||
options.lintMode = true;
|
final String lintMode = i + 1 < args.length ? args[++i] : LinterStage.Mode.SEMANTIC.name();
|
||||||
return;
|
options.lintMode = switch (lintMode.toLowerCase(Locale.ROOT)) {
|
||||||
|
case "none" -> LinterStage.Mode.NONE;
|
||||||
|
case "full" -> LinterStage.Mode.FULL;
|
||||||
|
default -> LinterStage.Mode.SEMANTIC;
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
case "-f":
|
case "-f":
|
||||||
case "--file":
|
case "--file":
|
||||||
@ -112,8 +118,8 @@ public final class Main {
|
|||||||
options:
|
options:
|
||||||
-f, --file [input] Run program file. Required.
|
-f, --file [input] Run program file. Required.
|
||||||
-r, --repl Enter to a REPL mode
|
-r, --repl Enter to a REPL mode
|
||||||
-l, --lint Find bugs in code
|
-l, --lint <mode> Find bugs in code. Mode: none, semantic, full
|
||||||
-o N, --optimize N Perform optimization with N (0...9) passes
|
-o, --optimize N Perform optimization with N (0...9) passes
|
||||||
-b, --beautify Beautify source code
|
-b, --beautify Beautify source code
|
||||||
-a, --showast Show AST of program
|
-a, --showast Show AST of program
|
||||||
-t, --showtokens Show lexical tokens
|
-t, --showtokens Show lexical tokens
|
||||||
@ -145,8 +151,8 @@ public final class Main {
|
|||||||
.thenConditional(options.optimizationLevel > 0,
|
.thenConditional(options.optimizationLevel > 0,
|
||||||
scopedStages.create("Optimization",
|
scopedStages.create("Optimization",
|
||||||
new OptimizationStage(options.optimizationLevel, options.showAst)))
|
new OptimizationStage(options.optimizationLevel, options.showAst)))
|
||||||
.thenConditional(options.lintMode,
|
.thenConditional(options.linterEnabled(),
|
||||||
scopedStages.create("Linter", new LinterStage()))
|
scopedStages.create("Linter", new LinterStage(options.lintMode)))
|
||||||
.then(scopedStages.create("Function adding", new FunctionAddingStage()))
|
.then(scopedStages.create("Function adding", new FunctionAddingStage()))
|
||||||
.then(scopedStages.create("Execution", new ExecutionStage()))
|
.then(scopedStages.create("Execution", new ExecutionStage()))
|
||||||
.perform(stagesData, options.toInputSource());
|
.perform(stagesData, options.toInputSource());
|
||||||
|
@ -1,21 +1,17 @@
|
|||||||
package com.annimon.ownlang;
|
package com.annimon.ownlang;
|
||||||
|
|
||||||
import com.annimon.ownlang.util.input.InputSource;
|
import com.annimon.ownlang.parser.linters.LinterStage;
|
||||||
import com.annimon.ownlang.util.input.InputSourceFile;
|
import com.annimon.ownlang.util.input.*;
|
||||||
import com.annimon.ownlang.util.input.InputSourceProgram;
|
import static com.annimon.ownlang.util.input.InputSourceDetector.RESOURCE_PREFIX;
|
||||||
import com.annimon.ownlang.util.input.InputSourceResource;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public class RunOptions {
|
public class RunOptions {
|
||||||
private static final String RESOURCE_PREFIX = "resource:";
|
|
||||||
private static final String DEFAULT_PROGRAM = "program.own";
|
private static final String DEFAULT_PROGRAM = "program.own";
|
||||||
|
|
||||||
// input
|
// input
|
||||||
String programPath;
|
String programPath;
|
||||||
String programSource;
|
String programSource;
|
||||||
// modes
|
// modes
|
||||||
boolean lintMode;
|
LinterStage.Mode lintMode = LinterStage.Mode.SEMANTIC;
|
||||||
boolean beautifyMode;
|
boolean beautifyMode;
|
||||||
int optimizationLevel;
|
int optimizationLevel;
|
||||||
// flags
|
// flags
|
||||||
@ -23,11 +19,18 @@ public class RunOptions {
|
|||||||
boolean showAst;
|
boolean showAst;
|
||||||
boolean showMeasurements;
|
boolean showMeasurements;
|
||||||
|
|
||||||
|
private final InputSourceDetector inputSourceDetector = new InputSourceDetector();
|
||||||
|
|
||||||
|
boolean linterEnabled() {
|
||||||
|
return lintMode != null && lintMode != LinterStage.Mode.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
String detectDefaultProgramPath() {
|
String detectDefaultProgramPath() {
|
||||||
if (getClass().getResource("/" + DEFAULT_PROGRAM) != null) {
|
final String resourcePath = RESOURCE_PREFIX + "/" + DEFAULT_PROGRAM;
|
||||||
return RESOURCE_PREFIX + "/" + DEFAULT_PROGRAM;
|
if (inputSourceDetector.isReadable(resourcePath)) {
|
||||||
|
return resourcePath;
|
||||||
}
|
}
|
||||||
if (Files.isReadable(Path.of(DEFAULT_PROGRAM))) {
|
if (inputSourceDetector.isReadable(DEFAULT_PROGRAM)) {
|
||||||
return DEFAULT_PROGRAM;
|
return DEFAULT_PROGRAM;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -44,12 +47,6 @@ public class RunOptions {
|
|||||||
throw new IllegalArgumentException("Empty input");
|
throw new IllegalArgumentException("Empty input");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return inputSourceDetector.toInputSource(programPath);
|
||||||
if (programPath.startsWith(RESOURCE_PREFIX)) {
|
|
||||||
String path = programPath.substring(RESOURCE_PREFIX.length());
|
|
||||||
return new InputSourceResource(path);
|
|
||||||
} else {
|
|
||||||
return new InputSourceFile(programPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
package com.annimon.ownlang.exceptions;
|
|
||||||
|
|
||||||
import com.annimon.ownlang.util.Range;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base type for all lexer and parser exceptions
|
|
||||||
*/
|
|
||||||
public abstract class BaseParserException extends RuntimeException {
|
|
||||||
|
|
||||||
private final Range range;
|
|
||||||
|
|
||||||
public BaseParserException(String message, Range range) {
|
|
||||||
super(message);
|
|
||||||
this.range = range;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Range getRange() {
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +1,27 @@
|
|||||||
package com.annimon.ownlang.exceptions;
|
package com.annimon.ownlang.exceptions;
|
||||||
|
|
||||||
import com.annimon.ownlang.parser.error.ParseError;
|
import com.annimon.ownlang.util.SourceLocatedError;
|
||||||
import com.annimon.ownlang.parser.error.ParseErrors;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single Exception for Lexer and Parser errors
|
* Single Exception for Lexer, Parser and Linter errors
|
||||||
*/
|
*/
|
||||||
public class OwnLangParserException extends RuntimeException {
|
public class OwnLangParserException extends RuntimeException {
|
||||||
|
|
||||||
private final ParseErrors parseErrors;
|
private final Collection<? extends SourceLocatedError> errors;
|
||||||
|
|
||||||
public OwnLangParserException(ParseError parseError) {
|
public OwnLangParserException(SourceLocatedError error) {
|
||||||
super(parseError.toString());
|
super(error.toString());
|
||||||
this.parseErrors = new ParseErrors();
|
errors = List.of(error);;
|
||||||
parseErrors.add(parseError);;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public OwnLangParserException(ParseErrors parseErrors) {
|
public OwnLangParserException(Collection<? extends SourceLocatedError> errors) {
|
||||||
super(parseErrors.toString());
|
super(errors.toString());
|
||||||
this.parseErrors = parseErrors;
|
this.errors = errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParseErrors getParseErrors() {
|
public Collection<? extends SourceLocatedError> getParseErrors() {
|
||||||
return parseErrors;
|
return errors;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,13 +6,16 @@ import com.annimon.ownlang.util.Range;
|
|||||||
*
|
*
|
||||||
* @author aNNiMON
|
* @author aNNiMON
|
||||||
*/
|
*/
|
||||||
public final class ParseException extends BaseParserException {
|
public final class ParseException extends RuntimeException {
|
||||||
|
|
||||||
public ParseException(String message) {
|
private final Range range;
|
||||||
super(message, Range.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ParseException(String message, Range range) {
|
public ParseException(String message, Range range) {
|
||||||
super(message, range);
|
super(message);
|
||||||
|
this.range = range;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Range getRange() {
|
||||||
|
return range;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,12 @@
|
|||||||
package com.annimon.ownlang.parser.error;
|
package com.annimon.ownlang.parser.error;
|
||||||
|
|
||||||
import com.annimon.ownlang.Console;
|
import com.annimon.ownlang.Console;
|
||||||
|
import java.util.AbstractList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public final class ParseErrors implements Iterable<ParseError> {
|
public final class ParseErrors extends AbstractList<ParseError> {
|
||||||
|
|
||||||
private final List<ParseError> errors;
|
private final List<ParseError> errors;
|
||||||
|
|
||||||
@ -16,11 +17,17 @@ public final class ParseErrors implements Iterable<ParseError> {
|
|||||||
public void clear() {
|
public void clear() {
|
||||||
errors.clear();
|
errors.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(ParseError parseError) {
|
@Override
|
||||||
errors.add(parseError);
|
public boolean add(ParseError parseError) {
|
||||||
|
return errors.add(parseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParseError get(int index) {
|
||||||
|
return errors.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasErrors() {
|
public boolean hasErrors() {
|
||||||
return !errors.isEmpty();
|
return !errors.isEmpty();
|
||||||
}
|
}
|
||||||
@ -30,6 +37,11 @@ public final class ParseErrors implements Iterable<ParseError> {
|
|||||||
return errors.iterator();
|
return errors.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return errors.size();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder result = new StringBuilder();
|
final StringBuilder result = new StringBuilder();
|
||||||
|
@ -4,11 +4,13 @@ import com.annimon.ownlang.stages.Stage;
|
|||||||
import com.annimon.ownlang.stages.StagesData;
|
import com.annimon.ownlang.stages.StagesData;
|
||||||
import com.annimon.ownlang.util.ErrorsLocationFormatterStage;
|
import com.annimon.ownlang.util.ErrorsLocationFormatterStage;
|
||||||
import com.annimon.ownlang.util.ErrorsStackTraceFormatterStage;
|
import com.annimon.ownlang.util.ErrorsStackTraceFormatterStage;
|
||||||
|
import com.annimon.ownlang.util.SourceLocatedError;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
public class ParseErrorsFormatterStage implements Stage<ParseErrors, String> {
|
public class ParseErrorsFormatterStage implements Stage<Collection<? extends SourceLocatedError>, String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String perform(StagesData stagesData, ParseErrors input) {
|
public String perform(StagesData stagesData, Collection<? extends SourceLocatedError> input) {
|
||||||
String error = new ErrorsLocationFormatterStage()
|
String error = new ErrorsLocationFormatterStage()
|
||||||
.perform(stagesData, input);
|
.perform(stagesData, input);
|
||||||
String stackTrace = new ErrorsStackTraceFormatterStage()
|
String stackTrace = new ErrorsStackTraceFormatterStage()
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package com.annimon.ownlang.parser.linters;
|
package com.annimon.ownlang.parser.linters;
|
||||||
|
|
||||||
import com.annimon.ownlang.Console;
|
|
||||||
import com.annimon.ownlang.parser.ast.*;
|
import com.annimon.ownlang.parser.ast.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -14,7 +12,7 @@ final class AssignValidator extends LintVisitor {
|
|||||||
|
|
||||||
private final Set<String> moduleConstants = new HashSet<>();
|
private final Set<String> moduleConstants = new HashSet<>();
|
||||||
|
|
||||||
AssignValidator(Collection<LinterResult> results) {
|
AssignValidator(LinterResults results) {
|
||||||
super(results);
|
super(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,8 +22,8 @@ final class AssignValidator extends LintVisitor {
|
|||||||
if (s.target instanceof VariableExpression varExpr) {
|
if (s.target instanceof VariableExpression varExpr) {
|
||||||
final String variable = varExpr.name;
|
final String variable = varExpr.name;
|
||||||
if (moduleConstants.contains(variable)) {
|
if (moduleConstants.contains(variable)) {
|
||||||
results.add(new LinterResult(LinterResult.Severity.WARNING,
|
results.add(LinterResult.warning(
|
||||||
String.format("Variable \"%s\" overrides constant", variable)));
|
"Variable \"%s\" overrides constant".formatted(variable)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.annimon.ownlang.parser.linters;
|
package com.annimon.ownlang.parser.linters;
|
||||||
|
|
||||||
import com.annimon.ownlang.parser.ast.*;
|
import com.annimon.ownlang.parser.ast.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -9,7 +8,7 @@ final class DefaultFunctionsOverrideValidator extends LintVisitor {
|
|||||||
|
|
||||||
private final Set<String> moduleFunctions = new HashSet<>();
|
private final Set<String> moduleFunctions = new HashSet<>();
|
||||||
|
|
||||||
DefaultFunctionsOverrideValidator(Collection<LinterResult> results) {
|
DefaultFunctionsOverrideValidator(LinterResults results) {
|
||||||
super(results);
|
super(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,8 +16,8 @@ final class DefaultFunctionsOverrideValidator extends LintVisitor {
|
|||||||
public void visit(FunctionDefineStatement s) {
|
public void visit(FunctionDefineStatement s) {
|
||||||
super.visit(s);
|
super.visit(s);
|
||||||
if (moduleFunctions.contains(s.name)) {
|
if (moduleFunctions.contains(s.name)) {
|
||||||
results.add(new LinterResult(LinterResult.Severity.WARNING,
|
results.add(LinterResult.warning(
|
||||||
String.format("Function \"%s\" overrides default module function", s.name)));
|
"Function \"%s\" overrides default module function".formatted(s.name)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.annimon.ownlang.parser.linters;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.parser.ast.IncludeStatement;
|
||||||
|
import com.annimon.ownlang.parser.ast.ValueExpression;
|
||||||
|
import com.annimon.ownlang.util.input.InputSourceDetector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
final class IncludeSourceValidator extends LintVisitor {
|
||||||
|
private final InputSourceDetector detector;
|
||||||
|
|
||||||
|
IncludeSourceValidator(LinterResults results) {
|
||||||
|
super(results);
|
||||||
|
detector = new InputSourceDetector();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(IncludeStatement s) {
|
||||||
|
super.visit(s);
|
||||||
|
if (s.expression instanceof ValueExpression expr) {
|
||||||
|
final String path = expr.eval().asString();
|
||||||
|
if (!detector.isReadable(path)) {
|
||||||
|
results.add(LinterResult.error(
|
||||||
|
"Include statement path \"%s\" is not readable".formatted(path)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
results.add(LinterResult.warning(
|
||||||
|
"Include statement path \"%s\" is not a constant string".formatted(s.expression)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,12 +5,11 @@ import com.annimon.ownlang.parser.ast.Node;
|
|||||||
import com.annimon.ownlang.parser.ast.Visitor;
|
import com.annimon.ownlang.parser.ast.Visitor;
|
||||||
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
||||||
import com.annimon.ownlang.parser.visitors.VisitorUtils;
|
import com.annimon.ownlang.parser.visitors.VisitorUtils;
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
abstract class LintVisitor extends AbstractVisitor {
|
abstract class LintVisitor extends AbstractVisitor {
|
||||||
protected final Collection<LinterResult> results;
|
protected final LinterResults results;
|
||||||
|
|
||||||
LintVisitor(Collection<LinterResult> results) {
|
LintVisitor(LinterResults results) {
|
||||||
this.results = results;
|
this.results = results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,42 @@
|
|||||||
package com.annimon.ownlang.parser.linters;
|
package com.annimon.ownlang.parser.linters;
|
||||||
|
|
||||||
record LinterResult(Severity severity, String message) {
|
import com.annimon.ownlang.util.Range;
|
||||||
|
import com.annimon.ownlang.util.SourceLocatedError;
|
||||||
|
|
||||||
|
record LinterResult(Severity severity, String message, Range range) implements SourceLocatedError {
|
||||||
|
|
||||||
enum Severity { ERROR, WARNING }
|
enum Severity { ERROR, WARNING }
|
||||||
|
|
||||||
|
static LinterResult warning(String message) {
|
||||||
|
return new LinterResult(Severity.WARNING, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LinterResult error(String message) {
|
||||||
|
return new LinterResult(Severity.ERROR, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
LinterResult(Severity severity, String message) {
|
||||||
|
this(severity, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isError() {
|
||||||
|
return severity == Severity.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWarning() {
|
||||||
|
return severity == Severity.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Range getRange() {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return severity.name() + ": " + message;
|
return severity.name() + ": " + message;
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.annimon.ownlang.parser.linters;
|
||||||
|
|
||||||
|
import java.util.AbstractList;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
class LinterResults extends AbstractList<LinterResult> {
|
||||||
|
private final List<LinterResult> results;
|
||||||
|
|
||||||
|
LinterResults() {
|
||||||
|
this(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
LinterResults(List<LinterResult> results) {
|
||||||
|
this.results = results;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(LinterResult result) {
|
||||||
|
return results.add(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LinterResult get(int index) {
|
||||||
|
return results.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<LinterResult> iterator() {
|
||||||
|
return results.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return results.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasErrors() {
|
||||||
|
return results.stream().anyMatch(LinterResult::isError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<LinterResult> errors() {
|
||||||
|
return results.stream().filter(LinterResult::isError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasWarnings() {
|
||||||
|
return results.stream().anyMatch(LinterResult::isWarning);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream<LinterResult> warnings() {
|
||||||
|
return results.stream().filter(LinterResult::isWarning);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.annimon.ownlang.parser.linters;
|
package com.annimon.ownlang.parser.linters;
|
||||||
|
|
||||||
import com.annimon.ownlang.Console;
|
import com.annimon.ownlang.Console;
|
||||||
|
import com.annimon.ownlang.exceptions.OwnLangParserException;
|
||||||
import com.annimon.ownlang.lib.ScopeHandler;
|
import com.annimon.ownlang.lib.ScopeHandler;
|
||||||
import com.annimon.ownlang.parser.ast.Node;
|
import com.annimon.ownlang.parser.ast.Node;
|
||||||
import com.annimon.ownlang.parser.ast.Visitor;
|
import com.annimon.ownlang.parser.ast.Visitor;
|
||||||
@ -11,23 +12,42 @@ import java.util.Comparator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class LinterStage implements Stage<Node, Node> {
|
public class LinterStage implements Stage<Node, Node> {
|
||||||
|
public enum Mode { NONE, SEMANTIC, FULL }
|
||||||
|
|
||||||
|
private final Mode mode;
|
||||||
|
|
||||||
|
public LinterStage(Mode mode) {
|
||||||
|
this.mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Node perform(StagesData stagesData, Node input) {
|
public Node perform(StagesData stagesData, Node input) {
|
||||||
final List<LinterResult> results = new ArrayList<>();
|
if (mode == Mode.NONE) return input;
|
||||||
final Visitor[] validators = new Visitor[] {
|
|
||||||
new AssignValidator(results),
|
|
||||||
new DefaultFunctionsOverrideValidator(results)
|
|
||||||
};
|
|
||||||
|
|
||||||
ScopeHandler.resetScope();
|
final LinterResults results = new LinterResults();
|
||||||
|
final List<Visitor> validators = new ArrayList<>();
|
||||||
|
validators.add(new IncludeSourceValidator(results));
|
||||||
|
|
||||||
|
if (mode == Mode.SEMANTIC) {
|
||||||
|
validators.forEach(input::accept);
|
||||||
|
if (results.hasErrors()) {
|
||||||
|
throw new OwnLangParserException(results.errors().toList());
|
||||||
|
}
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full lint validation with Console output
|
||||||
|
validators.add(new AssignValidator(results));
|
||||||
|
validators.add(new DefaultFunctionsOverrideValidator(results));
|
||||||
|
|
||||||
|
ScopeHandler.resetScope(); // TODO special linter scope?
|
||||||
for (Visitor validator : validators) {
|
for (Visitor validator : validators) {
|
||||||
input.accept(validator);
|
input.accept(validator);
|
||||||
ScopeHandler.resetScope();
|
ScopeHandler.resetScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
results.sort(Comparator.comparing(LinterResult::severity));
|
results.sort(Comparator.comparing(LinterResult::severity));
|
||||||
Console.println(String.format("Lint validation completed. %d results found!", results.size()));
|
Console.println("Lint validation completed. %d results found!".formatted(results.size()));
|
||||||
for (LinterResult r : results) {
|
for (LinterResult r : results) {
|
||||||
switch (r.severity()) {
|
switch (r.severity()) {
|
||||||
case ERROR -> Console.error(r.toString());
|
case ERROR -> Console.error(r.toString());
|
||||||
|
@ -9,6 +9,7 @@ import com.annimon.ownlang.parser.ast.FunctionDefineStatement;
|
|||||||
import com.annimon.ownlang.parser.ast.Node;
|
import com.annimon.ownlang.parser.ast.Node;
|
||||||
import com.annimon.ownlang.parser.ast.Visitor;
|
import com.annimon.ownlang.parser.ast.Visitor;
|
||||||
import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
|
import com.annimon.ownlang.parser.error.ParseErrorsFormatterStage;
|
||||||
|
import com.annimon.ownlang.parser.linters.LinterStage;
|
||||||
import com.annimon.ownlang.parser.optimization.OptimizationStage;
|
import com.annimon.ownlang.parser.optimization.OptimizationStage;
|
||||||
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
import com.annimon.ownlang.parser.visitors.AbstractVisitor;
|
||||||
import com.annimon.ownlang.stages.*;
|
import com.annimon.ownlang.stages.*;
|
||||||
@ -16,7 +17,6 @@ import com.annimon.ownlang.util.input.InputSource;
|
|||||||
import com.annimon.ownlang.util.input.InputSourceFile;
|
import com.annimon.ownlang.util.input.InputSourceFile;
|
||||||
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
import com.annimon.ownlang.util.input.SourceLoaderStage;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -39,7 +39,9 @@ public class ProgramsTest {
|
|||||||
testPipeline = new SourceLoaderStage()
|
testPipeline = new SourceLoaderStage()
|
||||||
.then(new LexerStage())
|
.then(new LexerStage())
|
||||||
.then(new ParserStage())
|
.then(new ParserStage())
|
||||||
|
.then(new LinterStage(LinterStage.Mode.SEMANTIC))
|
||||||
.thenConditional(true, new OptimizationStage(9))
|
.thenConditional(true, new OptimizationStage(9))
|
||||||
|
.then(ProgramsTest::mockOUnit)
|
||||||
.then(new ExecutionStage())
|
.then(new ExecutionStage())
|
||||||
.then((stagesData, input) -> {
|
.then((stagesData, input) -> {
|
||||||
input.accept(testFunctionsExecutor);
|
input.accept(testFunctionsExecutor);
|
||||||
@ -47,8 +49,7 @@ public class ProgramsTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
private static Node mockOUnit(StagesData stagesData, Node input) {
|
||||||
public void initialize() {
|
|
||||||
ScopeHandler.resetScope();
|
ScopeHandler.resetScope();
|
||||||
// Let's mock junit methods as ounit functions
|
// Let's mock junit methods as ounit functions
|
||||||
ScopeHandler.setFunction("assertEquals", (args) -> {
|
ScopeHandler.setFunction("assertEquals", (args) -> {
|
||||||
@ -84,6 +85,7 @@ public class ProgramsTest {
|
|||||||
}
|
}
|
||||||
return NumberValue.ONE;
|
return NumberValue.ONE;
|
||||||
});
|
});
|
||||||
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
|
Loading…
Reference in New Issue
Block a user