diff --git a/src/com/annimon/ownlang/Main.java b/src/com/annimon/ownlang/Main.java index 14cfa65..feea6c8 100644 --- a/src/com/annimon/ownlang/Main.java +++ b/src/com/annimon/ownlang/Main.java @@ -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" + @@ -78,6 +81,11 @@ public final class Main { case "--repl": repl(); return; + + case "-l": + case "--lint": + options.lintMode = true; + return; case "-f": case "--file": @@ -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 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; + } } } } diff --git a/src/com/annimon/ownlang/parser/Linter.java b/src/com/annimon/ownlang/parser/Linter.java new file mode 100644 index 0000000..52555e0 --- /dev/null +++ b/src/com/annimon/ownlang/parser/Linter.java @@ -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(); + } +} diff --git a/src/com/annimon/ownlang/parser/ast/IncludeStatement.java b/src/com/annimon/ownlang/parser/ast/IncludeStatement.java index f43ec54..05b84cd 100644 --- a/src/com/annimon/ownlang/parser/ast/IncludeStatement.java +++ b/src/com/annimon/ownlang/parser/ast/IncludeStatement.java @@ -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 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(); } @@ -34,6 +32,17 @@ public final class IncludeStatement implements Statement { throw new RuntimeException(ex); } } + + public Statement loadProgram(String path) throws IOException { + final String input = SourceLoader.readSource(path); + final List 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) { diff --git a/src/com/annimon/ownlang/parser/visitors/AssignValidator.java b/src/com/annimon/ownlang/parser/visitors/AssignValidator.java index b1c7ad8..52bd6c4 100644 --- a/src/com/annimon/ownlang/parser/visitors/AssignValidator.java +++ b/src/com/annimon/ownlang/parser/visitors/AssignValidator.java @@ -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(); + } } diff --git a/src/com/annimon/ownlang/parser/visitors/DefaultFunctionsOverrideValidator.java b/src/com/annimon/ownlang/parser/visitors/DefaultFunctionsOverrideValidator.java new file mode 100644 index 0000000..b44946e --- /dev/null +++ b/src/com/annimon/ownlang/parser/visitors/DefaultFunctionsOverrideValidator.java @@ -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(); + } +} diff --git a/src/com/annimon/ownlang/parser/visitors/LintVisitor.java b/src/com/annimon/ownlang/parser/visitors/LintVisitor.java new file mode 100644 index 0000000..9f28d07 --- /dev/null +++ b/src/com/annimon/ownlang/parser/visitors/LintVisitor.java @@ -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) { + } + } +} diff --git a/src/com/annimon/ownlang/parser/visitors/UseWithNonStringValueValidator.java b/src/com/annimon/ownlang/parser/visitors/UseWithNonStringValueValidator.java new file mode 100644 index 0000000..655d05b --- /dev/null +++ b/src/com/annimon/ownlang/parser/visitors/UseWithNonStringValueValidator.java @@ -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())); + } + } +}