Skip to content

Commit 35971e8

Browse files
committed
Add semantic linter as a required stage
1 parent 1535e86 commit 35971e8

File tree

16 files changed

+258
-92
lines changed

16 files changed

+258
-92
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.annimon.ownlang.util.input;
2+
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
6+
public record InputSourceDetector() {
7+
public static final String RESOURCE_PREFIX = "resource:";
8+
9+
public boolean isReadable(String programPath) {
10+
if (programPath.startsWith(RESOURCE_PREFIX)) {
11+
String path = programPath.substring(RESOURCE_PREFIX.length());
12+
return getClass().getResource(path) != null;
13+
} else {
14+
Path path = Path.of(programPath);
15+
return Files.isReadable(path) && Files.isRegularFile(path);
16+
}
17+
}
18+
19+
public InputSource toInputSource(String programPath) {
20+
if (programPath.startsWith(RESOURCE_PREFIX)) {
21+
String path = programPath.substring(RESOURCE_PREFIX.length());
22+
return new InputSourceResource(path);
23+
} else {
24+
return new InputSourceFile(programPath);
25+
}
26+
}
27+
}

ownlang-desktop/src/main/java/com/annimon/ownlang/Main.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.annimon.ownlang.utils.TimeMeasurement;
1616
import java.io.IOException;
1717
import java.util.List;
18+
import java.util.Locale;
1819
import java.util.concurrent.TimeUnit;
1920

2021
/**
@@ -72,8 +73,13 @@ public static void main(String[] args) throws IOException {
7273

7374
case "-l":
7475
case "--lint":
75-
options.lintMode = true;
76-
return;
76+
final String lintMode = i + 1 < args.length ? args[++i] : LinterStage.Mode.SEMANTIC.name();
77+
options.lintMode = switch (lintMode.toLowerCase(Locale.ROOT)) {
78+
case "none" -> LinterStage.Mode.NONE;
79+
case "full" -> LinterStage.Mode.FULL;
80+
default -> LinterStage.Mode.SEMANTIC;
81+
};
82+
break;
7783

7884
case "-f":
7985
case "--file":
@@ -112,8 +118,8 @@ private static void printUsage() {
112118
options:
113119
-f, --file [input] Run program file. Required.
114120
-r, --repl Enter to a REPL mode
115-
-l, --lint Find bugs in code
116-
-o N, --optimize N Perform optimization with N (0...9) passes
121+
-l, --lint <mode> Find bugs in code. Mode: none, semantic, full
122+
-o, --optimize N Perform optimization with N (0...9) passes
117123
-b, --beautify Beautify source code
118124
-a, --showast Show AST of program
119125
-t, --showtokens Show lexical tokens
@@ -145,8 +151,8 @@ private static void run(RunOptions options) {
145151
.thenConditional(options.optimizationLevel > 0,
146152
scopedStages.create("Optimization",
147153
new OptimizationStage(options.optimizationLevel, options.showAst)))
148-
.thenConditional(options.lintMode,
149-
scopedStages.create("Linter", new LinterStage()))
154+
.thenConditional(options.linterEnabled(),
155+
scopedStages.create("Linter", new LinterStage(options.lintMode)))
150156
.then(scopedStages.create("Function adding", new FunctionAddingStage()))
151157
.then(scopedStages.create("Execution", new ExecutionStage()))
152158
.perform(stagesData, options.toInputSource());
Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,36 @@
11
package com.annimon.ownlang;
22

3-
import com.annimon.ownlang.util.input.InputSource;
4-
import com.annimon.ownlang.util.input.InputSourceFile;
5-
import com.annimon.ownlang.util.input.InputSourceProgram;
6-
import com.annimon.ownlang.util.input.InputSourceResource;
7-
import java.nio.file.Files;
8-
import java.nio.file.Path;
3+
import com.annimon.ownlang.parser.linters.LinterStage;
4+
import com.annimon.ownlang.util.input.*;
5+
import static com.annimon.ownlang.util.input.InputSourceDetector.RESOURCE_PREFIX;
96

107
public class RunOptions {
11-
private static final String RESOURCE_PREFIX = "resource:";
128
private static final String DEFAULT_PROGRAM = "program.own";
139

1410
// input
1511
String programPath;
1612
String programSource;
1713
// modes
18-
boolean lintMode;
14+
LinterStage.Mode lintMode = LinterStage.Mode.SEMANTIC;
1915
boolean beautifyMode;
2016
int optimizationLevel;
2117
// flags
2218
boolean showTokens;
2319
boolean showAst;
2420
boolean showMeasurements;
2521

22+
private final InputSourceDetector inputSourceDetector = new InputSourceDetector();
23+
24+
boolean linterEnabled() {
25+
return lintMode != null && lintMode != LinterStage.Mode.NONE;
26+
}
27+
2628
String detectDefaultProgramPath() {
27-
if (getClass().getResource("/" + DEFAULT_PROGRAM) != null) {
28-
return RESOURCE_PREFIX + "/" + DEFAULT_PROGRAM;
29+
final String resourcePath = RESOURCE_PREFIX + "/" + DEFAULT_PROGRAM;
30+
if (inputSourceDetector.isReadable(resourcePath)) {
31+
return resourcePath;
2932
}
30-
if (Files.isReadable(Path.of(DEFAULT_PROGRAM))) {
33+
if (inputSourceDetector.isReadable(DEFAULT_PROGRAM)) {
3134
return DEFAULT_PROGRAM;
3235
}
3336
return null;
@@ -44,12 +47,6 @@ InputSource toInputSource() {
4447
throw new IllegalArgumentException("Empty input");
4548
}
4649
}
47-
48-
if (programPath.startsWith(RESOURCE_PREFIX)) {
49-
String path = programPath.substring(RESOURCE_PREFIX.length());
50-
return new InputSourceResource(path);
51-
} else {
52-
return new InputSourceFile(programPath);
53-
}
50+
return inputSourceDetector.toInputSource(programPath);
5451
}
5552
}

ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/BaseParserException.java

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,27 @@
11
package com.annimon.ownlang.exceptions;
22

3-
import com.annimon.ownlang.parser.error.ParseError;
4-
import com.annimon.ownlang.parser.error.ParseErrors;
3+
import com.annimon.ownlang.util.SourceLocatedError;
4+
import java.util.Collection;
5+
import java.util.List;
56

67
/**
7-
* Single Exception for Lexer and Parser errors
8+
* Single Exception for Lexer, Parser and Linter errors
89
*/
910
public class OwnLangParserException extends RuntimeException {
1011

11-
private final ParseErrors parseErrors;
12+
private final Collection<? extends SourceLocatedError> errors;
1213

13-
public OwnLangParserException(ParseError parseError) {
14-
super(parseError.toString());
15-
this.parseErrors = new ParseErrors();
16-
parseErrors.add(parseError);;
14+
public OwnLangParserException(SourceLocatedError error) {
15+
super(error.toString());
16+
errors = List.of(error);;
1717
}
1818

19-
public OwnLangParserException(ParseErrors parseErrors) {
20-
super(parseErrors.toString());
21-
this.parseErrors = parseErrors;
19+
public OwnLangParserException(Collection<? extends SourceLocatedError> errors) {
20+
super(errors.toString());
21+
this.errors = errors;
2222
}
2323

24-
public ParseErrors getParseErrors() {
25-
return parseErrors;
24+
public Collection<? extends SourceLocatedError> getParseErrors() {
25+
return errors;
2626
}
2727
}

ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/ParseException.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
*
77
* @author aNNiMON
88
*/
9-
public final class ParseException extends BaseParserException {
9+
public final class ParseException extends RuntimeException {
1010

11-
public ParseException(String message) {
12-
super(message, Range.ZERO);
13-
}
11+
private final Range range;
1412

1513
public ParseException(String message, Range range) {
16-
super(message, range);
14+
super(message);
15+
this.range = range;
16+
}
17+
18+
public Range getRange() {
19+
return range;
1720
}
1821
}

ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseErrors.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.annimon.ownlang.parser.error;
22

33
import com.annimon.ownlang.Console;
4+
import java.util.AbstractList;
45
import java.util.ArrayList;
56
import java.util.Iterator;
67
import java.util.List;
78

8-
public final class ParseErrors implements Iterable<ParseError> {
9+
public final class ParseErrors extends AbstractList<ParseError> {
910

1011
private final List<ParseError> errors;
1112

@@ -16,11 +17,17 @@ public ParseErrors() {
1617
public void clear() {
1718
errors.clear();
1819
}
19-
20-
public void add(ParseError parseError) {
21-
errors.add(parseError);
20+
21+
@Override
22+
public boolean add(ParseError parseError) {
23+
return errors.add(parseError);
2224
}
23-
25+
26+
@Override
27+
public ParseError get(int index) {
28+
return errors.get(index);
29+
}
30+
2431
public boolean hasErrors() {
2532
return !errors.isEmpty();
2633
}
@@ -30,6 +37,11 @@ public Iterator<ParseError> iterator() {
3037
return errors.iterator();
3138
}
3239

40+
@Override
41+
public int size() {
42+
return errors.size();
43+
}
44+
3345
@Override
3446
public String toString() {
3547
final StringBuilder result = new StringBuilder();

ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseErrorsFormatterStage.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
import com.annimon.ownlang.stages.StagesData;
55
import com.annimon.ownlang.util.ErrorsLocationFormatterStage;
66
import com.annimon.ownlang.util.ErrorsStackTraceFormatterStage;
7+
import com.annimon.ownlang.util.SourceLocatedError;
8+
import java.util.Collection;
79

8-
public class ParseErrorsFormatterStage implements Stage<ParseErrors, String> {
10+
public class ParseErrorsFormatterStage implements Stage<Collection<? extends SourceLocatedError>, String> {
911

1012
@Override
11-
public String perform(StagesData stagesData, ParseErrors input) {
13+
public String perform(StagesData stagesData, Collection<? extends SourceLocatedError> input) {
1214
String error = new ErrorsLocationFormatterStage()
1315
.perform(stagesData, input);
1416
String stackTrace = new ErrorsStackTraceFormatterStage()

ownlang-parser/src/main/java/com/annimon/ownlang/parser/linters/AssignValidator.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.annimon.ownlang.parser.linters;
22

3-
import com.annimon.ownlang.Console;
43
import com.annimon.ownlang.parser.ast.*;
5-
import java.util.Collection;
64
import java.util.HashSet;
75
import java.util.Set;
86

@@ -14,7 +12,7 @@ final class AssignValidator extends LintVisitor {
1412

1513
private final Set<String> moduleConstants = new HashSet<>();
1614

17-
AssignValidator(Collection<LinterResult> results) {
15+
AssignValidator(LinterResults results) {
1816
super(results);
1917
}
2018

@@ -24,8 +22,8 @@ public void visit(AssignmentExpression s) {
2422
if (s.target instanceof VariableExpression varExpr) {
2523
final String variable = varExpr.name;
2624
if (moduleConstants.contains(variable)) {
27-
results.add(new LinterResult(LinterResult.Severity.WARNING,
28-
String.format("Variable \"%s\" overrides constant", variable)));
25+
results.add(LinterResult.warning(
26+
"Variable \"%s\" overrides constant".formatted(variable)));
2927
}
3028
}
3129
}

ownlang-parser/src/main/java/com/annimon/ownlang/parser/linters/DefaultFunctionsOverrideValidator.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
package com.annimon.ownlang.parser.linters;
22

33
import com.annimon.ownlang.parser.ast.*;
4-
import java.util.Collection;
54
import java.util.HashSet;
65
import java.util.Set;
76

87
final class DefaultFunctionsOverrideValidator extends LintVisitor {
98

109
private final Set<String> moduleFunctions = new HashSet<>();
1110

12-
DefaultFunctionsOverrideValidator(Collection<LinterResult> results) {
11+
DefaultFunctionsOverrideValidator(LinterResults results) {
1312
super(results);
1413
}
1514

1615
@Override
1716
public void visit(FunctionDefineStatement s) {
1817
super.visit(s);
1918
if (moduleFunctions.contains(s.name)) {
20-
results.add(new LinterResult(LinterResult.Severity.WARNING,
21-
String.format("Function \"%s\" overrides default module function", s.name)));
19+
results.add(LinterResult.warning(
20+
"Function \"%s\" overrides default module function".formatted(s.name)));
2221
}
2322
}
2423

0 commit comments

Comments
 (0)