diff options
| author | AidenRushbrooke <72034940+AidenRushbrooke@users.noreply.github.com> | 2021-10-25 16:55:22 +0100 | 
|---|---|---|
| committer | AidenRushbrooke <72034940+AidenRushbrooke@users.noreply.github.com> | 2021-10-25 16:55:22 +0100 | 
| commit | 74c5732bded6695eed3aabf125a888fbdf206a40 (patch) | |
| tree | a429332a21ad595c190cae80264fbaf3bf19ed13 | |
| parent | cb29252f1e0d29d555fb232f39d343930fc76105 (diff) | |
| download | esotericFORTRAN-74c5732bded6695eed3aabf125a888fbdf206a40.tar.gz esotericFORTRAN-74c5732bded6695eed3aabf125a888fbdf206a40.zip | |
Interpreter capable of handing variables
| -rw-r--r-- | code/Interpreter/Environment.java | 30 | ||||
| -rw-r--r-- | code/Interpreter/Expression.java | 84 | ||||
| -rw-r--r-- | code/Interpreter/Interpreter.java | 149 | ||||
| -rw-r--r-- | code/Interpreter/Language.java | 63 | ||||
| -rw-r--r-- | code/Interpreter/Parser.java | 179 | ||||
| -rw-r--r-- | code/Interpreter/Statement.java | 49 | ||||
| -rw-r--r-- | code/Interpreter/Token.java | 2 | ||||
| -rw-r--r-- | code/Interpreter/TokenScanner.java | 47 | ||||
| -rw-r--r-- | code/Interpreter/TokenType.java | 12 | ||||
| -rw-r--r-- | code/example.txt | 12 | 
10 files changed, 592 insertions, 35 deletions
| diff --git a/code/Interpreter/Environment.java b/code/Interpreter/Environment.java new file mode 100644 index 0000000..d191bde --- /dev/null +++ b/code/Interpreter/Environment.java @@ -0,0 +1,30 @@ +package Interpreter; + +import java.util.HashMap; +import java.util.Map; + +public class Environment { +    private final Map<String,Object> variableMap = new HashMap<>(); + +    //Maybe check if variable is already defined? +    public void defineVariable(String name,Object value){ +        variableMap.put(name, value); +    } + +    public Object getVariable(String name){ +        if(variableMap.containsKey(name)){ +            return variableMap.get(name); +        } +        Language.displayError("Undefined Variable"); +        throw new Error(); +    } + +    public void assignVariable(String name,Object value){ +        if(variableMap.containsKey(name)){ +            variableMap.put(name, value); +            return; +        } +        Language.displayError("Variable undefined"); +        throw new Error(); +    } +} diff --git a/code/Interpreter/Expression.java b/code/Interpreter/Expression.java new file mode 100644 index 0000000..85ade48 --- /dev/null +++ b/code/Interpreter/Expression.java @@ -0,0 +1,84 @@ +package Interpreter; + +abstract class Expression { +    static class Binary extends Expression{ + +        final Expression left; +        final Expression right; +        final Token op; + +        Binary(Expression left, Token op, Expression right){ +            this.left=left; +            this.op=op; +            this.right = right; +        } + +        @Override +        public String getExpressionType() { +            return "binary"; +        } + +    } + +    static class Literal extends Expression{ +        final Token value; + +        Literal(Token value){ +            this.value=value; +        } +         + +        @Override +        public String getExpressionType() { +            return "literal"; +        } +         +    } + +    static class BracketedExpression extends Expression{ +        final Expression expr; + +        BracketedExpression(Expression expr){ +            this.expr=expr; +        } + +        @Override +        public String getExpressionType() { +            return "bracket"; +        } +         +         +    } + +    static class AssignmentExpression extends Expression{ +        final Token name; +        final Expression value; + +        AssignmentExpression(Token name,Expression value){ +            this.name=name; +            this.value=value; +        } + + +        @Override +        public String getExpressionType() { +            return "assign"; +        } +         +    } + +    static class Variable extends Expression{ +         +        Variable(Token name){ +            this.name=name; + +        } +        @Override +        public String getExpressionType() { +            return "var"; +        } +        final Token name; +         +    } +    public abstract String getExpressionType(); +} diff --git a/code/Interpreter/Interpreter.java b/code/Interpreter/Interpreter.java index 17f2ccf..65cdeb4 100644 --- a/code/Interpreter/Interpreter.java +++ b/code/Interpreter/Interpreter.java @@ -1,44 +1,131 @@  package Interpreter; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths;  import java.util.List; -import java.util.Scanner; -//Base class for the interpreter +import Interpreter.Expression.*; +import Interpreter.Statement.ExpressionStatement; +import Interpreter.Statement.PrintStatement; +import Interpreter.Statement.VariableDeclaration; +  public class Interpreter { -    public static void main(String[] args){ - -        //Allow users to input a single line of code -        //Still needs some work to re-ask for input after each line -        if (args.length < 1){ -            Scanner input = new Scanner(System.in); -            System.out.print("Code: "); -            String sourceCode = input.nextLine(); -            runInterpreter(sourceCode); -            input.close(); - -        //Allow users to provide a path to a file as an argument -        } else if (args.length==1){ -            try { -                String sourcecode = Files.readString(Paths.get(args[0]));  //Maybe should set charset here -                runInterpreter(sourcecode); -            } catch (IOException exception){ -                System.out.println("File not found"); + +    private Environment environment = new Environment(); + +    void interpret(List<Statement> statements){ +        try{ +            for (Statement statement: statements){ +                evaluateStatement(statement);              } +        } catch (Error e){ -        } else { -            System.out.println("Error, argument should be file path"); -            System.exit(64);          }      } -    private static void runInterpreter(String sourceCode){ -        TokenScanner scanner = new TokenScanner(); -        List<Token> tokens = scanner.extractTokens(sourceCode); -        for (Token token : tokens) { -            System.out.println(token); +    private Object evaluateStatement(Statement statement){ +        switch(statement.getStatmentType()){ +            case "exprStmt": +                return evalExpressionStatement((ExpressionStatement)statement); +            case "vardec": +                return evalVariableDeclaration((VariableDeclaration)statement); +            case "print": +                return evalPrintStatement((PrintStatement)statement); +            default: +                return null; +        } +    }  +    private Object evalExpressionStatement(ExpressionStatement stmt){ +        return evaluateExpression(stmt.expr); +    } + +    private Object evalVariableDeclaration(VariableDeclaration vardec){ +        environment.defineVariable(vardec.name.text, null); +        return null; +    } + +    private Object evalPrintStatement(PrintStatement print){ +        System.out.println(evaluateExpression(print.expr)); +        return null; +    } + +    private Object evaluateExpression(Expression expression){ +        switch(expression.getExpressionType()){ +            case "binary": +                return evaluateBinaryExpression((Binary)expression); +            case "literal": +                return evaluateLiteralExpression((Literal)expression); +            case "bracket": +                return evaluateBracketedExpression((BracketedExpression)expression); +            case "assign": +                return evaluateAssignmentExpression((AssignmentExpression)expression); +            case "var": +                return evaluateVariableExpression((Variable)expression); +            default: +                return null; +        } +    } + +    private Object evaluateBinaryExpression(Binary expr){ +        Object leftEval = evaluateExpression(expr.left); +        Object rightEval = evaluateExpression(expr.right); +        switch (expr.op.type){ +            case PLUS: +                if (checkOperandsNum(leftEval, leftEval)){ +                    return (double)leftEval + (double)rightEval; +                }  +            case STAR: +                if (checkOperandsNum(leftEval, leftEval)){ +                    return (double)leftEval * (double)rightEval; +                }  +            case MINUS: +                if (checkOperandsNum(leftEval, leftEval)){ +                    return (double)leftEval - (double)rightEval; +                } +            case SLASH: +                if (checkOperandsNum(leftEval, leftEval)){ +                    return (double)leftEval / (double)rightEval; +                } + +            case GREATER: +                if (checkOperandsNum(leftEval, leftEval)){ +                    return (double)leftEval > (double)rightEval; +                } +            case LESS: +                if (checkOperandsNum(leftEval, leftEval)){ +                    return (double)leftEval < (double)rightEval; +                } +             +            case EQUALITY: +                return leftEval.equals(rightEval); +            default: +                break; +        } +        return null; +    } + +    private Object evaluateLiteralExpression(Literal expr){ +        return expr.value.value; +    } + +    private Object evaluateBracketedExpression(BracketedExpression expr){ +        return evaluateExpression(expr.expr); +    } + +    private Object evaluateAssignmentExpression(AssignmentExpression expr){ +        Object assignedValue = evaluateExpression(expr.value); +        environment.assignVariable(expr.name.text, assignedValue); +        return null; +    } + +    private Object evaluateVariableExpression(Variable expr){ +        return environment.getVariable(expr.name.text); +    } + +    private boolean checkOperandsNum(Object left, Object right){ +        if (left instanceof Double && right instanceof Double){ +            return true; +        } else { +            Language.displayError("Operands must be numbers"); +            throw new Error();          }      }  } diff --git a/code/Interpreter/Language.java b/code/Interpreter/Language.java new file mode 100644 index 0000000..80aa1e3 --- /dev/null +++ b/code/Interpreter/Language.java @@ -0,0 +1,63 @@ +package Interpreter; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Scanner; + +//Base class for the interpreter +public class Language { +    static boolean hadError = false; +    public static void main(String[] args){ +         +        //Allow users to input a single line of code +        //Still needs some work to re-ask for input after each line +        if (args.length < 1){ +            Scanner input = new Scanner(System.in); +            String sourceCode = "1"; +            while (sourceCode!=""){ +                System.out.print("Code: "); +                sourceCode = input.nextLine(); +                runInterpreter(sourceCode); +                hadError=false; +            } +            input.close(); + +        //Allow users to provide a path to a file as an argument +        } else if (args.length==1){ +            try { +                String sourcecode = Files.readString(Paths.get(args[0]));  //Maybe should set charset here +                runInterpreter(sourcecode); +            } catch (IOException exception){ +                System.out.println("File not found"); +            } + +        } else { +            System.out.println("Error, argument should be file path"); +            System.exit(64); +        } +    } + +    //Extract and print each token +    private static void runInterpreter(String sourceCode){ +        TokenScanner scanner = new TokenScanner(); +        List<Token> tokens = scanner.extractTokens(sourceCode); +        //for (Token token : tokens) { +        //    System.out.println(token); +        //} +        if (hadError) return; +        //Parse into AST +        Parser parser = new Parser(tokens); +        List<Statement> ast = parser.parse(); +        if (hadError) return; +        Interpreter interpreter = new Interpreter(); +        interpreter.interpret(ast); +    } + +    static void displayError(String message){ +        hadError=true; +        System.out.println("An error was encountered"); +        System.out.println(message); +    } +} diff --git a/code/Interpreter/Parser.java b/code/Interpreter/Parser.java new file mode 100644 index 0000000..6b55299 --- /dev/null +++ b/code/Interpreter/Parser.java @@ -0,0 +1,179 @@ +package Interpreter; + +import java.util.ArrayList; +import java.util.List; + +public class Parser { +    private final List<Token> tokens; +    private int currentToken = 0; + +    Parser(List<Token> tokens){ +        this.tokens=tokens; +    } + +    List<Statement> parse(){ +        List<Statement> statements =  new ArrayList<>(); +        while (!checkEOF()){ +            statements.add(declaration()); +        } +        return statements; +         +    } + +    private Statement declaration(){ +        try{ +            if (matchAndAdvance(TokenType.VAR)){ +                if (matchOrError(TokenType.DEFINE, ":: Required for variable definition")){ +                    if (matchOrError(TokenType.IDENTIFIER,"Expected variable name.")){ +                        Token varName = getPreviousToken(); +                        return new Statement.VariableDeclaration(varName); +                    }  +                } +            } +            return statement(); +        } catch (Error e){ +            currentToken++; +            return null; +        } +    } + +    private Statement statement(){ +        if (matchAndAdvance(TokenType.PRINT)){ +            Expression expression = expression(); +            return new Statement.PrintStatement(expression); +        } +        return expressionStatement(); +    } + + + +    private Statement expressionStatement(){ +        Expression expression = assignment(); +        return new Statement.ExpressionStatement(expression); +    } + +    private Expression assignment(){ +        Expression variable = expression(); +        if (matchAndAdvance(TokenType.EQUALS)){ +            Expression assignedvalue = expression(); + +            if (variable instanceof Expression.Variable){ +                return new Expression.AssignmentExpression(((Expression.Variable)variable).name,assignedvalue); +            } +            throw error("Incorrect assignment operation"); +        } +        return variable; +    } + +    private Expression expression(){ +        Expression createdExpression = equality(); +        return createdExpression; +    } + +    private Expression equality(){ +        Expression createdExpression = comparison(); +        while (matchAndAdvance(TokenType.EQUALITY)){ +            Token op = getPreviousToken(); +            Expression right = comparison(); +            createdExpression = new Expression.Binary(createdExpression, op, right); +        } +        return createdExpression; +    } + +    private Expression comparison(){ +        Expression createdExpression = term(); +        while (matchAndAdvance(TokenType.GREATER)||matchAndAdvance(TokenType.LESS)){ +            Token op = getPreviousToken(); +            Expression right = term(); +            createdExpression = new Expression.Binary(createdExpression, op, right); +        } +        return createdExpression; +    } + +    private Expression term(){ +        Expression createdExpression = factor(); +        while (matchAndAdvance(TokenType.PLUS)||matchAndAdvance(TokenType.MINUS)){ +            Token op = getPreviousToken(); +            Expression right = factor(); +            createdExpression = new Expression.Binary(createdExpression, op, right); +        } +        return createdExpression; +    } + +    private Expression factor(){ +        Expression createdExpression = primary(); +        while (matchAndAdvance(TokenType.STAR)||matchAndAdvance(TokenType.SLASH)){ +            Token op = getPreviousToken(); +            Expression right = primary(); +            createdExpression = new Expression.Binary(createdExpression, op, right); +        } +        return createdExpression; +    } + +    private Expression primary(){ +        if (matchAndAdvance(TokenType.NUMBER)){ +            return new Expression.Literal(getPreviousToken()); +        } + +        if (matchAndAdvance(TokenType.IDENTIFIER)) { + +            return new Expression.Variable(getPreviousToken()); +          } + +        if (matchAndAdvance(TokenType.LEFT_PAREN)){ +            Expression expr = expression(); +            if (matchAndAdvance(TokenType.RIGHT_PAREN)){ +                return new Expression.BracketedExpression(expr); +            } +            else{ +                throw error("Expected ')"); +            } +        } +        throw error("Expected Expression"); +    } + +    private void advanceToken(){ +        if (!checkEOF()) { +            currentToken++; +        }; +    } + +    private boolean matchAndAdvance(TokenType type){ +        if (checkToken(type)) { +            advanceToken(); +            return true; +        } +        return false; +    } + +    private boolean matchOrError(TokenType type,String errorMessage){ +        if (matchAndAdvance(type)){ +            return true; +        } +        throw error(errorMessage); +    } + +    private boolean checkToken(TokenType type){ +        if (checkEOF()) return false; +        return getCurrentToken().type == type;  +    } + +    private boolean checkEOF(){ +        return tokens.get(currentToken).type==TokenType.EOF; +    } + +    private Token getCurrentToken(){ +        return tokens.get(currentToken); +    } + +    private Token getPreviousToken(){ +        return tokens.get(currentToken - 1); +    } + +    private Error error(String message){ +        Language.displayError(message); +        return new Error(); +    } + + +} diff --git a/code/Interpreter/Statement.java b/code/Interpreter/Statement.java new file mode 100644 index 0000000..5a9aef7 --- /dev/null +++ b/code/Interpreter/Statement.java @@ -0,0 +1,49 @@ +package Interpreter; + +abstract class Statement { + +    static class ExpressionStatement extends Statement{ +        ExpressionStatement(Expression expr){ +            this.expr = expr; +        } + + +        final Expression expr; + +        @Override +        public String getStatmentType() { +            return "exprStmt"; +        } +    } + + +    static class VariableDeclaration extends Statement{ +        VariableDeclaration(Token name){ +            this.name=name; +        } + + +        final Token name; + +        @Override +        public String getStatmentType() { +            return "vardec"; +        } + +    } + +    static class PrintStatement extends Statement{ +        PrintStatement(Expression expr){ +            this.expr=expr; +        } +        final Expression expr; + +        @Override +        public String getStatmentType() { +            return "print"; +        } +    } + + +    public abstract String getStatmentType(); +} diff --git a/code/Interpreter/Token.java b/code/Interpreter/Token.java index b1cf542..0129b78 100644 --- a/code/Interpreter/Token.java +++ b/code/Interpreter/Token.java @@ -4,7 +4,7 @@ public class Token {      //Stores the token type, the actual text and the runtime object -    final TokenType type; +    public final TokenType type;      final String text;      final Object value; diff --git a/code/Interpreter/TokenScanner.java b/code/Interpreter/TokenScanner.java index 87f1e4b..c9249a4 100644 --- a/code/Interpreter/TokenScanner.java +++ b/code/Interpreter/TokenScanner.java @@ -1,7 +1,9 @@  package Interpreter;  import java.util.ArrayList; +import java.util.HashMap;  import java.util.List; +import java.util.Map;  public class TokenScanner {      private String sourceCode; @@ -16,6 +18,7 @@ public class TokenScanner {              tokenStart=currentLoc;              readToken();          } +        tokens.add(new Token(TokenType.EOF, "", null));          return tokens;      } @@ -23,12 +26,20 @@ public class TokenScanner {      private void readToken(){          char checkChar = sourceCode.charAt(currentLoc);          switch(checkChar){ + +            case ' ':break; +            case '\n':break; +            case '\r':break; +            case '\t': +                break; +              case '(': createTokenNull(TokenType.LEFT_PAREN); break;              case ')': createTokenNull(TokenType.RIGHT_PAREN); break;              case '+': createTokenNull(TokenType.PLUS); break;              case '-': createTokenNull(TokenType.MINUS); break;              case '*': createTokenNull(TokenType.STAR); break;              case '/': createTokenNull(TokenType.SLASH); break; +            case ';': createTokenNull(TokenType.SEMI_COLON); break;              //Some tokens are multiple characters long (==, <=) etc              //so need to check next char as well @@ -40,6 +51,14 @@ public class TokenScanner {                      createTokenNull(TokenType.EQUALS);                      break;                  } +            case ':':  +                if (checkNextChar(':')){ +                    createTokenNull(TokenType.DEFINE); +                    break; +                } else { +                    createTokenNull(TokenType.COLON); +                    break; +                }              case '<':                   if (checkNextChar('=')){                      createTokenNull(TokenType.LESS_EQUAL); @@ -72,6 +91,21 @@ public class TokenScanner {                      }                      createToken(TokenType.NUMBER, Double.parseDouble(sourceCode.substring(tokenStart, currentLoc+1)));                  } +                else if (checkIsAlpha(checkChar)){ +                    while (checkIsAlpha(lookAhead())){ +                        currentLoc++; +                    }  +                    String text = sourceCode.substring(tokenStart, currentLoc+1); +                    TokenType type = keywords.get(text); +                    if(type == null){ +                        createToken(TokenType.IDENTIFIER, text); +                    } else{ +                        createToken(type, text); +                    } + +                } else { +                    Language.displayError("Unexpected Character"); +                }          }          currentLoc++; @@ -129,4 +163,17 @@ public class TokenScanner {      private boolean checkIsDigit(char checkChar){          return checkChar>='0' && checkChar<='9';      } + +    private boolean checkIsAlpha(char checkChar){ +        return ('a'<=checkChar && checkChar<='z')|| +               ('A'<=checkChar && checkChar<='Z'); +    } + +    private static final Map<String, TokenType> keywords; + +    static { +        keywords = new HashMap<>(); +        keywords.put("var",    TokenType.VAR); +        keywords.put("print",    TokenType.PRINT); +      }  } diff --git a/code/Interpreter/TokenType.java b/code/Interpreter/TokenType.java index 26ffb15..756fab6 100644 --- a/code/Interpreter/TokenType.java +++ b/code/Interpreter/TokenType.java @@ -1,11 +1,17 @@  package Interpreter; -enum TokenType { +public enum TokenType {      EQUALS, LEFT_PAREN, RIGHT_PAREN, -    PLUS, MINUS, SLASH, STAR, +    PLUS, MINUS, SLASH, STAR, SEMI_COLON, +    COLON,      EQUALITY, GREATER, LESS,      GREATER_EQUAL, LESS_EQUAL, +    DEFINE, -    NUMBER +    NUMBER,IDENTIFIER, + +    VAR,PRINT, + +    EOF  } diff --git a/code/example.txt b/code/example.txt new file mode 100644 index 0000000..cf6adc8 --- /dev/null +++ b/code/example.txt @@ -0,0 +1,12 @@ +var :: a +a=5 +a=a+1 +print a + +a=7 +a=a*2 +print a + +var :: b +b = 10 +print a+b
\ No newline at end of file | 
