Добавлен линтер

This commit is contained in:
Victor 2016-06-20 02:27:27 +03:00
parent 840f049e15
commit 138100bfe0
7 changed files with 171 additions and 7 deletions

View File

@ -4,6 +4,7 @@ import com.annimon.ownlang.utils.TimeMeasurement;
import com.annimon.ownlang.exceptions.LexerException;
import com.annimon.ownlang.parser.Beautifier;
import com.annimon.ownlang.parser.Lexer;
import com.annimon.ownlang.parser.Linter;
import com.annimon.ownlang.parser.Parser;
import com.annimon.ownlang.parser.SourceLoader;
import com.annimon.ownlang.parser.Token;
@ -34,6 +35,7 @@ public final class Main {
options.showAst = true;
options.showTokens = true;
options.showMeasurements = true;
options.lintMode = true;
run(SourceLoader.readSource("program.own"), options);
} catch (IOException ioe) {
System.out.println("OwnLang version " + VERSION + "\n\n" +
@ -41,6 +43,7 @@ public final class Main {
" options:\n" +
" -f, --file [input] Run program file. Required.\n" +
" -r, --repl Enter to a REPL mode\n" +
" -l, --lint Find bugs in code\n" +
" -b, --beautify Beautify source code\n" +
" -a, --showast Show AST of program\n" +
" -t, --showtokens Show lexical tokens\n" +
@ -79,6 +82,11 @@ public final class Main {
repl();
return;
case "-l":
case "--lint":
options.lintMode = true;
return;
case "-f":
case "--file":
if (i + 1 < args.length) {
@ -113,6 +121,7 @@ public final class Main {
}
private static void run(String input, Options options) {
options.validate();
final TimeMeasurement measurement = new TimeMeasurement();
measurement.start("Tokenize time");
final List<Token> tokens = Lexer.tokenize(input);
@ -134,6 +143,10 @@ public final class Main {
System.out.println(parser.getParseErrors());
return;
}
if (options.lintMode) {
Linter.lint(program);
return;
}
program.accept(new FunctionAdder());
try {
measurement.start("Execution time");
@ -189,11 +202,21 @@ public final class Main {
private static class Options {
boolean showTokens, showAst, showMeasurements;
boolean lintMode;
public Options() {
showTokens = false;
showAst = false;
showMeasurements = false;
lintMode = false;
}
public void validate() {
if (lintMode == true) {
showTokens = false;
showAst = false;
showMeasurements = false;
}
}
}
}

View File

@ -0,0 +1,39 @@
package com.annimon.ownlang.parser;
import com.annimon.ownlang.lib.Functions;
import com.annimon.ownlang.lib.Variables;
import com.annimon.ownlang.parser.ast.Statement;
import com.annimon.ownlang.parser.ast.Visitor;
import com.annimon.ownlang.parser.visitors.*;
public final class Linter {
public static void lint(Statement program) {
new Linter(program).execute();
}
private final Statement program;
private Linter(Statement program) {
this.program = program;
}
public void execute() {
final Visitor[] validators = new Visitor[] {
new UseWithNonStringValueValidator(),
new AssignValidator(),
new DefaultFunctionsOverrideValidator()
};
resetState();
for (Visitor validator : validators) {
program.accept(validator);
resetState();
}
System.out.println("Lint validation complete!");
}
private void resetState() {
Variables.clear();
Functions.getFunctions().clear();
}
}

View File

@ -5,6 +5,7 @@ import com.annimon.ownlang.parser.Parser;
import com.annimon.ownlang.parser.SourceLoader;
import com.annimon.ownlang.parser.Token;
import com.annimon.ownlang.parser.visitors.FunctionAdder;
import java.io.IOException;
import java.util.List;
/**
@ -22,11 +23,8 @@ public final class IncludeStatement implements Statement {
@Override
public void execute() {
try {
final String input = SourceLoader.readSource(expression.eval().asString());
final List<Token> tokens = Lexer.tokenize(input);
final Parser parser = new Parser(tokens);
final Statement program = parser.parse();
if (!parser.getParseErrors().hasErrors()) {
final Statement program = loadProgram(expression.eval().asString());
if (program != null) {
program.accept(new FunctionAdder());
program.execute();
}
@ -35,6 +33,17 @@ public final class IncludeStatement implements Statement {
}
}
public Statement loadProgram(String path) throws IOException {
final String input = SourceLoader.readSource(path);
final List<Token> tokens = Lexer.tokenize(input);
final Parser parser = new Parser(tokens);
final Statement program = parser.parse();
if (parser.getParseErrors().hasErrors()) {
return null;
}
return program;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);

View File

@ -1,5 +1,6 @@
package com.annimon.ownlang.parser.visitors;
import com.annimon.ownlang.Console;
import com.annimon.ownlang.lib.Variables;
import com.annimon.ownlang.parser.ast.*;
@ -7,7 +8,7 @@ import com.annimon.ownlang.parser.ast.*;
*
* @author aNNiMON
*/
public final class AssignValidator extends AbstractVisitor {
public final class AssignValidator extends LintVisitor {
@Override
public void visit(AssignmentExpression s) {
@ -15,8 +16,21 @@ public final class AssignValidator extends AbstractVisitor {
if (s.target instanceof VariableExpression) {
final String variable = ((VariableExpression) s.target).name;
if (Variables.isExists(variable)) {
throw new RuntimeException("Cannot assign value to constant");
Console.error(String.format(
"Warning: variable \"%s\" overrides constant", variable));
}
}
}
@Override
public void visit(IncludeStatement st) {
super.visit(st);
applyVisitor(st, this);
}
@Override
public void visit(UseStatement st) {
super.visit(st);
st.execute();
}
}

View File

@ -0,0 +1,29 @@
package com.annimon.ownlang.parser.visitors;
import com.annimon.ownlang.Console;
import com.annimon.ownlang.lib.Functions;
import com.annimon.ownlang.parser.ast.*;
public final class DefaultFunctionsOverrideValidator extends LintVisitor {
@Override
public void visit(FunctionDefineStatement s) {
super.visit(s);
if (Functions.isExists(s.name)) {
Console.error(String.format(
"Warning: function \"%s\" overrides default module function", s.name));
}
}
@Override
public void visit(IncludeStatement st) {
super.visit(st);
applyVisitor(st, this);
}
@Override
public void visit(UseStatement st) {
super.visit(st);
st.execute();
}
}

View File

@ -0,0 +1,19 @@
package com.annimon.ownlang.parser.visitors;
import com.annimon.ownlang.parser.ast.IncludeStatement;
import com.annimon.ownlang.parser.ast.Statement;
import com.annimon.ownlang.parser.ast.ValueExpression;
import com.annimon.ownlang.parser.ast.Visitor;
import java.io.IOException;
public abstract class LintVisitor extends AbstractVisitor {
protected void applyVisitor(IncludeStatement s, Visitor visitor) {
if (!(s.expression instanceof ValueExpression)) return;
try {
final Statement program = s.loadProgram(s.expression.eval().asString());
program.accept(visitor);
} catch (IOException ex) {
}
}
}

View File

@ -0,0 +1,31 @@
package com.annimon.ownlang.parser.visitors;
import com.annimon.ownlang.Console;
import com.annimon.ownlang.lib.Types;
import com.annimon.ownlang.lib.Value;
import com.annimon.ownlang.parser.ast.*;
public final class UseWithNonStringValueValidator extends LintVisitor {
@Override
public void visit(IncludeStatement st) {
super.visit(st);
applyVisitor(st, this);
}
@Override
public void visit(UseStatement st) {
super.visit(st);
if (!(st.expression instanceof ValueExpression)) {
Console.error(String.format(
"Warning: `use` with %s, not ValueExpression", st.expression.getClass().getSimpleName()));
return;
}
final Value value = ((ValueExpression) st.expression).value;
if (value.type() != Types.STRING) {
Console.error(String.format(
"Warning: `use` with %s - %s, not string", Types.typeToString(value.type()), value.asString()));
}
}
}