diff options
author | AidenRushbrooke <72034940+AidenRushbrooke@users.noreply.github.com> | 2021-12-05 03:27:18 +0000 |
---|---|---|
committer | AidenRushbrooke <72034940+AidenRushbrooke@users.noreply.github.com> | 2021-12-05 03:27:18 +0000 |
commit | 9e6c89f1fa93287104381e02f0bbbdd6060a9382 (patch) | |
tree | 65a26fbbccb0172805b131b7100ffcabb7790da3 | |
parent | 43909b350b9084ed33f121a15c5770224cbdc79f (diff) | |
download | esotericFORTRAN-9e6c89f1fa93287104381e02f0bbbdd6060a9382.tar.gz esotericFORTRAN-9e6c89f1fa93287104381e02f0bbbdd6060a9382.zip |
Added subroutines and comments for most files
-rw-r--r-- | src/Compiler/Environment.java | 6 | ||||
-rw-r--r-- | src/Compiler/ExecuteC.java | 3 | ||||
-rw-r--r-- | src/Compiler/Expression.java | 31 | ||||
-rw-r--r-- | src/Compiler/Language.java | 60 | ||||
-rw-r--r-- | src/Compiler/Parser.java | 241 | ||||
-rw-r--r-- | src/Compiler/Statement.java | 48 | ||||
-rw-r--r-- | src/Compiler/Token.java | 18 | ||||
-rw-r--r-- | src/Compiler/TokenScanner.java | 80 | ||||
-rw-r--r-- | src/Compiler/TokenType.java | 2 | ||||
-rw-r--r-- | src/Compiler/Translator.java | 197 | ||||
-rw-r--r-- | src/examples/example.ft | 27 |
11 files changed, 621 insertions, 92 deletions
diff --git a/src/Compiler/Environment.java b/src/Compiler/Environment.java index 1bb0e88..8ae8724 100644 --- a/src/Compiler/Environment.java +++ b/src/Compiler/Environment.java @@ -2,11 +2,13 @@ package Compiler; import java.util.HashMap; import java.util.Map; +/** + * Class to record defined variables during the translation stage + */ public class Environment { private final Map<String,Object> variableMap = new HashMap<>(); //Define a variable inside the current environment - //Maybe check if variable is already defined? public void defineVariable(String name,Object value){ variableMap.put(name, value); } @@ -20,7 +22,7 @@ public class Environment { throw new Error(); } - //Get a variable if it is defined, or report an error + //Check if a variable is defined, or report an error public Boolean checkVariable(Token token){ if(variableMap.containsKey(token.text)){ return true; diff --git a/src/Compiler/ExecuteC.java b/src/Compiler/ExecuteC.java index fbc7ade..128f541 100644 --- a/src/Compiler/ExecuteC.java +++ b/src/Compiler/ExecuteC.java @@ -69,7 +69,6 @@ public class ExecuteC { public Boolean compileC(){ try{ - String s= null; Process p; if (System.getProperty("os.name").equals("Linux")) { p = Runtime.getRuntime().exec(String.format( @@ -86,7 +85,7 @@ public class ExecuteC { BufferedReader stdError = new BufferedReader(new InputStreamReader(p.getErrorStream())); boolean error=false; - while ((s = stdError.readLine()) != null) { + while ((stdError.readLine()) != null) { error=true; } return error; diff --git a/src/Compiler/Expression.java b/src/Compiler/Expression.java index 2789462..056019a 100644 --- a/src/Compiler/Expression.java +++ b/src/Compiler/Expression.java @@ -2,7 +2,12 @@ package Compiler; import java.util.List; +//Class for possible expressions in the language abstract class Expression { + + /** + * Class for representing a binary expression, with two values between an operator + */ static class Binary extends Expression{ final Expression left; @@ -22,6 +27,9 @@ abstract class Expression { } + /** + * Class for representing a singlular expression, with one value and one operator + */ static class Singular extends Expression{ final Expression right; @@ -39,6 +47,9 @@ abstract class Expression { } + /** + * Class for representing a literal value + */ static class Literal extends Expression{ final Token value; final String type; @@ -56,6 +67,9 @@ abstract class Expression { } + /** + * Class for representing an expression between brackets + */ static class BracketedExpression extends Expression{ final Expression expr; @@ -66,11 +80,12 @@ abstract class Expression { @Override public String getExpressionType() { return "bracket"; - } - - + } } + /** + * Class for represening an assignment expression + */ static class AssignmentExpression extends Expression{ final Expression variable; final Expression value; @@ -87,6 +102,9 @@ abstract class Expression { } } + /** + * Class for representing a single variable + */ static class Variable extends Expression{ Variable(Token name){ @@ -101,6 +119,9 @@ abstract class Expression { } + /** + * Class for representing an array variable + */ static class ArrayVariable extends Expression{ ArrayVariable(Token name,List<Expression> positions){ this.name=name; @@ -115,6 +136,9 @@ abstract class Expression { final List<Expression> positions; } + /** + * Class for representing a function call + */ static class FunctionCall extends Expression{ FunctionCall(Token name, List<Token> arguments){ this.arguments=arguments; @@ -130,5 +154,6 @@ abstract class Expression { } + //Abstract method for getting an expression type public abstract String getExpressionType(); } diff --git a/src/Compiler/Language.java b/src/Compiler/Language.java index b433b36..5013cfd 100644 --- a/src/Compiler/Language.java +++ b/src/Compiler/Language.java @@ -8,7 +8,10 @@ import java.util.List; import java.util.Scanner; import java.util.ArrayList; -//Base class for the interpreter +/** + *Base class for running the Compiler + *Can run either from a file, or in an interactive mode + */ public class Language { static boolean leaveCFile = false; @@ -17,8 +20,10 @@ public class Language { static boolean executeAfter = false; static Path sourcefile; + //Main function for the compiler public static void main(String[] args) { + //Extract required command line arguments try { sourcefile = Paths.get(args[0]); } catch (java.lang.ArrayIndexOutOfBoundsException e) { @@ -38,6 +43,8 @@ public class Language { Path initOutPath = Paths.get(args[0]); String outname = initOutPath.getName(initOutPath.getNameCount() - 1).toString().split("\\.(?=[^\\.]+$)")[0]; + + //Extract optional command line arguments ArrayList<String> arrayArgs = new ArrayList<>(); for (int i = 0; i < args.length; i++) { String arg = args[i]; @@ -66,21 +73,24 @@ public class Language { return; } + //Run the compiler on the source code try { - runInterpreter(Files.readString(sourcefile), outname); + runCompiler(Files.readString(sourcefile), outname); } catch (IOException e) { e.printStackTrace(); } } - //Function to take source code and output the result - private static void runInterpreter(String sourceCode, String outName){ + /** + * Function to take source code, run the compiler and write to C + * + * @param sourcecode the full source code as a string + * @param outName the name to write the compiled code to + */ + private static void runCompiler(String sourceCode, String outName){ //Extract tokens from the source code TokenScanner scanner = new TokenScanner(); List<Token> tokens = scanner.extractTokens(sourceCode); - //for (Token token : tokens) { - // System.out.println(token); - //} if (hadError) return; //Parse into AST @@ -98,25 +108,37 @@ public class Language { cExecutor.compileAndExecuteC(code, outName, executeAfter, leaveCFile); } + /** + * Method for running the compiler in an interactive mode + */ private static void interactiveMode() { Scanner input = new Scanner(System.in); - String sourceCode = "1"; - while (sourceCode!=""){ - System.out.print("Code: "); - sourceCode = input.nextLine(); - runInterpreter(sourceCode, "out"); - hadError=false; - } - input.close(); + String sourceCode = "1"; + //Run compiler line by line + while (sourceCode!=""){ + System.out.print("Code: "); + sourceCode = input.nextLine(); + runCompiler(sourceCode, "out"); + hadError=false; + } + input.close(); } - //Basic error reporting + /** + * Method for displaying an error to the user + * @param line the line the error occured on + * @param message an error message to display to the user + */ static void displayError(int line,String message){ hadError=true; System.out.println("An error was encountered on line: "+line); System.out.println(message); } - //Basic error reporting + /** + * Method for displaying error based on a specific token + * @param token the token the parser detected the error on + * @param message an error message to display to the user + */ static void displayError(Token token,String message){ hadError=true; System.out.println("An error was encountered on line: "+token.line); @@ -124,6 +146,10 @@ public class Language { System.out.println(message); } + /** + * Method for getting the helpfile text + * @return the text for the helpfile + */ private static String getHelpText(){ String helpText = ""; try { diff --git a/src/Compiler/Parser.java b/src/Compiler/Parser.java index ae2ed23..433035c 100644 --- a/src/Compiler/Parser.java +++ b/src/Compiler/Parser.java @@ -5,17 +5,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - +/** + * Class for parsing the source code, utilising the recursive descent parsing method + */ public class Parser { private final List<Token> tokens; private int currentToken = 0; private final Map<String,String> definedVars = new HashMap<>(); List<Statement> statements = new ArrayList<>(); + /** + * Constructor of a parser object + * @param tokens the list of tokens representing the source code + */ Parser(List<Token> tokens){ this.tokens=tokens; } + /** + * Method for parsing the list of tokens + * @return A list of statements for the source code + */ List<Statement> parse(){ try{ while (!checkEOF()){ @@ -28,32 +38,53 @@ public class Parser { } + /** + * Method to extracting the main function, or other functions + * @return A statement for a function or main function + */ private Statement functionCalls(){ + + //Check for main function definition if(matchAndAdvance(TokenType.PROGRAM)){ matchOrError(TokenType.IDENTIFIER, "Expected program name"); Token programname = getPreviousToken(); Statement block = blockStatement(false); matchOrError(TokenType.PROGRAM,"Programs must end in 'program'"); matchOrError(TokenType.IDENTIFIER,"Expected program name"); + + //Check program names match if(!(getPreviousToken().text.equals(programname.text))){ throw error(getPreviousToken(), "Program names do not match"); } return new Statement.MainFunction(block); - } else if(matchAndAdvance(TokenType.FUNCTION)){ + + //Check for function or subroutine definition + } else if(matchAndAdvance(TokenType.FUNCTION)||matchAndAdvance(TokenType.SUBROUTINE)){ + String returntype=null; - if(matchAndAdvance(TokenType.INT)){ - returntype="int"; - } else if (matchAndAdvance(TokenType.REAL)){ - returntype="real"; - }else{ - throw error(getCurrentToken(), "Expected function return type"); + boolean isfunction=false; + //Get function return data type + if(getPreviousToken().type==TokenType.FUNCTION){ + isfunction=true; + if(matchAndAdvance(TokenType.INT)){ + returntype="int"; + } else if (matchAndAdvance(TokenType.REAL)){ + returntype="real"; + }else{ + throw error(getCurrentToken(), "Expected function return type"); + } } + matchOrError(TokenType.IDENTIFIER, "Expected function name"); Token name = getPreviousToken(); + + //Add function name to the list of defined values definedVars.put(name.text, "function"); matchOrError(TokenType.LEFT_PAREN, "Expected '("); List<Expression> arguments=new ArrayList<>(); List<String> types = new ArrayList<>(); + + //Extract the arguments for the function if(!matchAndAdvance(TokenType.RIGHT_PAREN)){ do{ if(matchAndAdvance(TokenType.INT)){ @@ -67,52 +98,85 @@ public class Parser { } while(matchAndAdvance(TokenType.COMMA)); matchOrError(TokenType.RIGHT_PAREN, "Expected ')'"); } - Statement block = blockStatement(true); + + Statement block = blockStatement(isfunction); + + //Add the function declaration to the start of the list of statements statements.add(0,new Statement.FunctionDeclaration(name,types,returntype)); return new Statement.Function(name,block,arguments,types,returntype); } - throw error(getCurrentToken(), "Expected program or function"); + throw error(getCurrentToken(), "Program must start with program or function"); } + /** + * Method for extracting a single statement, excluding function definitions + * @return any type of statement + */ private Statement statement(){ + //Check for variable definition if(checkToken(TokenType.INT)||checkToken(TokenType.REAL)||checkToken(TokenType.STRING)){ return declaration(); } + + //Check for print statement else if (matchAndAdvance(TokenType.PRINT)){ return printStatement(); + + //Check for if statement }else if (matchAndAdvance(TokenType.IF)){ return ifStatement(); + + //Check for do statement }else if (matchAndAdvance(TokenType.DO)){ return doStatement(); + + //Check for return statement }else if (matchAndAdvance(TokenType.RETURN)){ return returnStatement(); } + + //If no statement matches, assume an expression statement return expressionStatement(); } - //Clean up and reduce code mess + /** + * Method for parsing a variable definition + * @return A variable definition statement + */ private Statement declaration(){ + //Check if the variable is an integer if (matchAndAdvance(TokenType.INT)){ + + //Check for array declaration if(matchAndAdvance(TokenType.DIMENSION)){ return arrayDeclaration("int"); } + matchOrError(TokenType.DEFINE, ":: Required for variable definition"); matchOrError(TokenType.IDENTIFIER,"Expected variable name."); Token varName = getPreviousToken(); + + //Ensure that a variable with the same name has not been defined if(definedVars.containsKey(varName.text)){ throw error(varName, "Cannot define multiple variables with the same name"); }else{ definedVars.put(varName.text,"int"); return new Statement.VariableDeclaration(varName,"int"); } + + //Check if the variable is a real type } else if (matchAndAdvance(TokenType.REAL)){ + + //Check for array declaration if(matchAndAdvance(TokenType.DIMENSION)){ return arrayDeclaration("real"); } matchOrError(TokenType.DEFINE, ":: Required for variable definition"); matchOrError(TokenType.IDENTIFIER,"Expected variable name."); Token varName = getPreviousToken(); + + //Ensure that a variable with the same name has not been defined if(definedVars.containsKey(varName.text)){ throw error(varName, "Cannot define multiple variables with the same name"); }else{ @@ -120,12 +184,15 @@ public class Parser { return new Statement.VariableDeclaration(varName,"real"); } - //Could be improved significatly when verifiying length is a positive integer + //Check if the variable is a string } else if (matchAndAdvance(TokenType.STRING)){ + //Extract the length of the string matchOrError(TokenType.LEFT_PAREN, "Length of string must be defined"); matchOrError(TokenType.LEN, "Length of string must be defined"); matchOrError(TokenType.EQUALS, "Length of string must be defined"); Expression length = expression(); + + //Ensure the length of the string is a positive integer if(!(length instanceof Expression.Literal)){ throw error(getCurrentToken(),"String length must be a number"); } @@ -135,11 +202,13 @@ public class Parser { if((int)((Expression.Literal)length).value.value<1){ throw error(getCurrentToken(),"String length must be greater then 0"); } - matchOrError(TokenType.RIGHT_PAREN, "Length of string must be defined"); + matchOrError(TokenType.RIGHT_PAREN, "Length of string must be defined"); matchOrError(TokenType.DEFINE, ":: Required for variable definition"); matchOrError(TokenType.IDENTIFIER,"Expected variable name."); Token varName = getPreviousToken(); + + //Ensure that a variable with the same name has not been defined if(definedVars.containsKey(varName.text)){ throw error(varName, "Cannot define multiple variables with the same name"); }else{ @@ -150,19 +219,31 @@ public class Parser { return null; } + /** + * Method to parse an array declaration + * @param type The data type of the array + * @return A statement representing the array declaration + */ private Statement arrayDeclaration(String type){ matchOrError(TokenType.LEFT_PAREN,"Expected ')'"); + + //Extract the size of the array List<Expression> dimensions = new ArrayList<>(); Expression dimension = expression(); dimensions.add(dimension); + + //Extract the size of each dimension of the array while(matchAndAdvance(TokenType.COMMA)){ dimension = expression(); dimensions.add(dimension); } + matchOrError(TokenType.RIGHT_PAREN, "Expected ')'"); matchOrError(TokenType.DEFINE, ":: Required for variable definition"); matchOrError(TokenType.IDENTIFIER,"Expected variable name."); Token varName = getPreviousToken(); + + //Ensure that a variable with the same name has not been defined if(definedVars.containsKey(varName.text)){ throw error(varName, "Cannot define multiple variables with the same name"); }else{ @@ -171,10 +252,18 @@ public class Parser { } } + /** + * Method for parsing a block of statements + * @param isFunction whether the block is for a function and so must contain a return statement + * @return a statement representing a block + */ private Statement blockStatement(boolean isFunction){ boolean hasReturn=false; List<Statement> statements = new ArrayList<>(); + + //Match statement until an end or else token has been reached while(!matchAndAdvance(TokenType.END)&&!checkToken(TokenType.ELSE)){ + //Ensure the end of the file has not been reached if(checkEOF()){ throw error(getCurrentToken(),"end missing from block"); } @@ -184,15 +273,22 @@ public class Parser { } statements.add(statement); } + //Ensure that a function block contains a return statement if(isFunction&&!hasReturn){ throw error(getPreviousToken(), "Function must contain a return statement"); } return new Statement.BlockStatement(statements); } + /** + * Method for parsing a print statement + * @return A print statement object + */ private Statement printStatement(){ matchOrError(TokenType.STAR, "Syntax, print *, item1, item2..."); List<Expression> exprList = new ArrayList<>(); + + //Get the list of printed values while(matchAndAdvance(TokenType.COMMA)){ if(checkEOF()){ throw error(getCurrentToken(),"reached end of file"); @@ -203,13 +299,18 @@ public class Parser { return new Statement.PrintStatement(exprList); } - //Could be cleaned up to handle else statements better + /** + * Method for parsing an if statement + * @return A statement representing an if statement + */ private Statement ifStatement(){ Expression condition = expression(); if(matchOrError(TokenType.THEN, "then expected after if statement")){ + //Extract if block Statement ifBlock = blockStatement(false); Statement elseBlock=null; + //Extract else block if required if(matchAndAdvance(TokenType.ELSE)){ elseBlock=blockStatement(false); } @@ -220,12 +321,19 @@ public class Parser { return null; } + /** + * Method for parsing a do statement + * @return A statement representing a do statement + */ private Statement doStatement(){ + //Check for do while statement if(matchAndAdvance(TokenType.WHILE)){ return whileStatement(); } Expression variable =expression(); matchOrError(TokenType.EQUALS, "'=' missing"); + + //Extract start, stop, and step variables Expression start = expression(); matchOrError(TokenType.COMMA, " use ',' between values"); Expression stop = expression(); @@ -239,7 +347,12 @@ public class Parser { } + /** + * Method to parse a do-while statement + * @return a do-while statement object + */ private Statement whileStatement(){ + //Extract condition matchOrError(TokenType.LEFT_PAREN, " missing '(' for do statement condition"); Expression condition = expression(); matchOrError(TokenType.RIGHT_PAREN, " missing ')' for do condition"); @@ -248,18 +361,31 @@ public class Parser { return new Statement.DoWhileStatement(condition,codeBlock); } + /** + * Method to parse a return statement + * @return a return statement object + */ private Statement returnStatement(){ Expression returnValue = expression(); return new Statement.ReturnStatement(returnValue); } + /** + * Method to parse an expression statement + * @return an expression statement object + */ private Statement expressionStatement(){ Expression expression = assignment(); return new Statement.ExpressionStatement(expression); } + /** + * Method to parse an assignment expression + * @return an assignment expression object + */ private Expression assignment(){ Expression variable = expression(); + //Check for an equals token if (matchAndAdvance(TokenType.EQUALS)){ Expression assignedvalue = expression(); return new Expression.AssignmentExpression(variable,assignedvalue); @@ -267,11 +393,19 @@ public class Parser { return variable; } + /** + * Method to parse a non-assignment expression + * @return an expression object + */ private Expression expression(){ Expression createdExpression = equality(); return createdExpression; } + /** + * Method to parse an equality comparison expression + * @return an expression of rank equality or lower + */ private Expression equality(){ Expression createdExpression = logical(); while (matchAndAdvance(TokenType.EQUALITY)){ @@ -282,6 +416,10 @@ public class Parser { return createdExpression; } + /** + * Method to parse a logical expression + * @return an expression of rank logical or lower + */ private Expression logical(){ if(matchAndAdvance(TokenType.NOT)){ Token op = getPreviousToken(); @@ -298,8 +436,13 @@ public class Parser { return createdExpression; } + /** + * Method to parse a comparison expression + * @return an expression of rank comparison or lower + */ private Expression comparison(){ Expression createdExpression = term(); + //Check for different types of comparison while (matchAndAdvance(TokenType.GREATER)||matchAndAdvance(TokenType.LESS)||matchAndAdvance(TokenType.GREATER_EQUAL)||matchAndAdvance(TokenType.LESS_EQUAL)){ Token op = getPreviousToken(); Expression right = term(); @@ -308,8 +451,13 @@ public class Parser { return createdExpression; } + /** + * Method to parse a term expression + * @return an expression of rank term or lower + */ private Expression term(){ Expression createdExpression = factor(); + //Check for addition or subtraction expression while (matchAndAdvance(TokenType.PLUS)||matchAndAdvance(TokenType.MINUS)){ Token op = getPreviousToken(); Expression right = factor(); @@ -318,8 +466,13 @@ public class Parser { return createdExpression; } + /** + * Method to parse a factor expression + * @return an expression of rank factor or lower + */ private Expression factor(){ Expression createdExpression = primary(); + //Check for multiplication or division expressions while (matchAndAdvance(TokenType.STAR)||matchAndAdvance(TokenType.SLASH)){ Token op = getPreviousToken(); Expression right = primary(); @@ -328,26 +481,36 @@ public class Parser { return createdExpression; } + /** + * Method to parse a primary expression + * @return a primary expression + */ private Expression primary(){ + //Check for singular numbers if (matchAndAdvance(TokenType.NUMBER)){ + //Check number type if(getPreviousToken().value instanceof Integer){ return new Expression.Literal(getPreviousToken(),"int"); } else if(getPreviousToken().value instanceof Double){ return new Expression.Literal(getPreviousToken(),"double"); } } + //Check for strings if (matchAndAdvance(TokenType.STRING)){ return new Expression.Literal(getPreviousToken(), "string"); } - + //Check for variable names if (matchAndAdvance(TokenType.IDENTIFIER)) { Token name= getPreviousToken(); + //Check for function calls or array assignments if(matchAndAdvance(TokenType.LEFT_PAREN)){ + //Parse array if array with same name has been previously defined if(definedVars.containsKey(name.text)){ if(definedVars.get(name.text).equals("array")){ List<Expression> positions = new ArrayList<>(); Expression position = expression(); positions.add(position); + //Parse array positions for each dimension while(matchAndAdvance(TokenType.COMMA)){ position = expression(); positions.add(position); @@ -356,7 +519,9 @@ public class Parser { return new Expression.ArrayVariable(name, positions); } } + //If not previously declared, assume function call List<Token> arguments = new ArrayList<>(); + //Parse function call arguments do{ matchOrError(TokenType.IDENTIFIER, "Expected argument"); Token argumentValue = getPreviousToken(); @@ -369,24 +534,35 @@ public class Parser { matchOrError(TokenType.RIGHT_PAREN, "Expected ')"); return new Expression.FunctionCall(name, arguments); } + //Parse single variable name return new Expression.Variable(getPreviousToken()); } + //Parse bracketed expressions if (matchAndAdvance(TokenType.LEFT_PAREN)){ Expression expr = expression(); matchOrError(TokenType.RIGHT_PAREN,"Expected ')'"); return new Expression.BracketedExpression(expr); } + //Throw an error if the expression was not recognised throw error(getCurrentToken(),"Unknown syntax error"); } - + /** + * Method to move ahead one token + */ private void advanceToken(){ if (!checkEOF()) { currentToken++; }; } - + + /** + * Method to compare the current token with a given token, and move ahead if a match is found + * @param type the token type to compare against + * @return if the match was successful + */ private boolean matchAndAdvance(TokenType type){ + //If the tokens are a match, advance ahead if (checkToken(type)) { advanceToken(); return true; @@ -394,6 +570,12 @@ public class Parser { return false; } + /** + * Method to check if the current token matches a given token, and throw an error if they do not match + * @param type the token type to compare against + * @param errorMessage the message to report if a match is not found + * @return a boolean to say if a match was successful + */ private boolean matchOrError(TokenType type,String errorMessage){ if (matchAndAdvance(type)){ return true; @@ -401,23 +583,46 @@ public class Parser { throw error(getCurrentToken(),errorMessage); } + /** + * Method to comapre the current token to a given token + * @param type the token type to compare against + * @return if the match was successful + */ private boolean checkToken(TokenType type){ if (checkEOF()) return false; return getCurrentToken().type == type; } + /** + * Method to check for the end of the file + * @return if the end of the file has been reached + */ private boolean checkEOF(){ return tokens.get(currentToken).type==TokenType.EOF; } - + + /** + * Method to get the current token + * @return the token at the current position + */ private Token getCurrentToken(){ return tokens.get(currentToken); } + /** + * Method to get the previous token + * @return the previous token + */ private Token getPreviousToken(){ return tokens.get(currentToken - 1); } + /** + * Method to report an error to the user during parsing + * @param token the token the error was detected on + * @param message the message to display to the user + * @return An error object to stop parsing + */ private Error error(Token token, String message){ Language.displayError(token ,message); return new Error(); diff --git a/src/Compiler/Statement.java b/src/Compiler/Statement.java index dd3ff04..5e94d35 100644 --- a/src/Compiler/Statement.java +++ b/src/Compiler/Statement.java @@ -1,9 +1,14 @@ package Compiler; import java.util.List; - +/** + * Classes for possible statements in the language + */ abstract class Statement { + /** + * Class for the main function of the program + */ static class MainFunction extends Statement{ MainFunction(Statement block){ this.block = block; @@ -18,6 +23,9 @@ abstract class Statement { } + /** + * Class for representing a function + */ static class Function extends Statement{ Function(Token name,Statement block,List<Expression> arguments,List<String> argumentTypes,String returnType){ this.block=block; @@ -39,6 +47,10 @@ abstract class Statement { } } + /** + * Class for representing a function declaration + * needed for compiling to C + */ static class FunctionDeclaration extends Statement{ FunctionDeclaration(Token name,List<String> argumentTypes,String returnType){ this.returnType=returnType; @@ -56,6 +68,9 @@ abstract class Statement { } } + /** + * Class for representing a return statement + */ static class ReturnStatement extends Statement{ ReturnStatement(Expression returnValue){ @@ -71,6 +86,9 @@ abstract class Statement { } + /** + * Class for representing a statement for an expression + */ static class ExpressionStatement extends Statement{ ExpressionStatement(Expression expr){ this.expr = expr; @@ -85,6 +103,9 @@ abstract class Statement { } } + /** + * Class for representing a block of statements + */ static class BlockStatement extends Statement{ BlockStatement(List<Statement> statements){ this.statements=statements; @@ -99,6 +120,9 @@ abstract class Statement { } + /** + * Class for representing an if statement + */ static class IfStatement extends Statement{ IfStatement(Expression condition, Statement ifBlock,Statement elseBlock){ this.condition=condition; @@ -116,6 +140,9 @@ abstract class Statement { } } + /** + * Class for representing a do loop + */ static class DoStatement extends Statement{ DoStatement(Expression variable, Expression start,Expression stop,Expression step,Statement codeBlock){ this.variable=variable; @@ -138,6 +165,9 @@ abstract class Statement { } } + /** + * Class for representing a do while loop + */ static class DoWhileStatement extends Statement{ DoWhileStatement(Expression condition,Statement codeBlock){ this.condition=condition; @@ -154,7 +184,10 @@ abstract class Statement { } } - + /** + * Class for representing a integer or real declaration + * + */ static class VariableDeclaration extends Statement{ VariableDeclaration(Token name,String type){ this.name=name; @@ -172,6 +205,9 @@ abstract class Statement { } + /** + * Class for representing a string variable declaration + */ static class StringDeclaration extends Statement{ StringDeclaration(Token name,Expression length){ this.name=name; @@ -189,6 +225,9 @@ abstract class Statement { } + /** + * Class for representing an array declaration + */ static class ArrayDeclaration extends Statement{ ArrayDeclaration(Token name,String type,List<Expression> dimensions){ this.name=name; @@ -207,6 +246,9 @@ abstract class Statement { } + /** + * Class for representing a print statement + */ static class PrintStatement extends Statement{ PrintStatement(List<Expression> exprList){ this.exprList=exprList; @@ -219,6 +261,6 @@ abstract class Statement { } } - + //Abstract function for getting the statement type public abstract String getStatmentType(); } diff --git a/src/Compiler/Token.java b/src/Compiler/Token.java index 50d3804..b6f11a8 100644 --- a/src/Compiler/Token.java +++ b/src/Compiler/Token.java @@ -1,15 +1,23 @@ package Compiler; +/** + * Class for storing information about a single token + */ public class Token { - - //Stores the token type, the actual text and the runtime object public final TokenType type; final String text; final Object value; final int line; + /** + * Contructor for a token object + * @param type the type of token + * @param text the actual text for the token + * @param value the value the token represents + * @param line the line of code the token is on + */ Token(TokenType type, String text, Object value,int line){ this.type=type; this.text=text; @@ -17,10 +25,4 @@ public class Token { this.line=line; } - - @Override - public String toString() { - return type + " " + text + " " + value; - } - } diff --git a/src/Compiler/TokenScanner.java b/src/Compiler/TokenScanner.java index 02bbbc0..ecb5ad3 100644 --- a/src/Compiler/TokenScanner.java +++ b/src/Compiler/TokenScanner.java @@ -5,6 +5,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * The lexical scanner, which takes the source code and extracts a list of token objects + */ public class TokenScanner { private String sourceCode; List<Token> tokens = new ArrayList<>(); @@ -12,23 +15,33 @@ public class TokenScanner { private int currentLoc=0; private int line=0; - //Extract tokens from the source code by reading character by character + /** + * Method for extracting tokens, checking each character at a time + * @param sourceCode the original source code as a string + * @return a list of tokens represeting the source code + */ List<Token> extractTokens(String sourceCode){ this.sourceCode=sourceCode; + //Read until the end of the source code as been reached while (!checkEOF()){ tokenStart=currentLoc; readToken(); } + //Add a EOF token on the end tokens.add(new Token(TokenType.EOF, "", null,line)); return tokens; } - //Extract a single token + /** + * Extract a single token + */ private void readToken(){ + //Get the current character and find the matching token char checkChar = sourceCode.charAt(currentLoc); switch(checkChar){ case ' ':break; + //Advance line when line break found case '\n': line++; break; @@ -45,7 +58,7 @@ public class TokenScanner { case ';': createTokenNull(TokenType.SEMI_COLON); break; case ',': createTokenNull(TokenType.COMMA); break; - //Some tokens are multiple characters long (==, <=) etc + //Some tokens are multiple characters long //so need to check next char as well case '=': if (checkNextChar('=')){ @@ -55,6 +68,7 @@ public class TokenScanner { createTokenNull(TokenType.EQUALS); break; } + case ':': if (checkNextChar(':')){ createTokenNull(TokenType.DEFINE); @@ -63,6 +77,7 @@ public class TokenScanner { createTokenNull(TokenType.COLON); break; } + case '<': if (checkNextChar('=')){ createTokenNull(TokenType.LESS_EQUAL); @@ -71,6 +86,7 @@ public class TokenScanner { createTokenNull(TokenType.LESS); break; } + case '>': if (checkNextChar('=')){ createTokenNull(TokenType.GREATER_EQUAL); @@ -79,6 +95,7 @@ public class TokenScanner { createTokenNull(TokenType.GREATER); break; } + case '"': while(lookAhead()!='"' && !checkEOF()){ currentLoc++; @@ -90,6 +107,7 @@ public class TokenScanner { currentLoc++; createToken(TokenType.STRING, sourceCode.substring(tokenStart, currentLoc+1)); break; + case '.': if(checkIsAlpha(lookAhead())) while (checkIsAlpha(lookAhead())){ @@ -112,9 +130,11 @@ public class TokenScanner { } else { Language.displayError(line, "Expected '.' after logical expression"); } + + //Find tokens starting with alphanumeric characters default: - //Check for numer + //Check for numeric characters if (checkIsDigit(checkChar)){ String type = "int"; while (checkIsDigit(lookAhead())){ @@ -134,19 +154,24 @@ public class TokenScanner { createToken(TokenType.NUMBER, Integer.parseInt(sourceCode.substring(tokenStart, currentLoc+1))); } } + + //Check alphabetical character else if (checkIsAlpha(checkChar)){ while (checkIsAlpha(lookAhead())){ currentLoc++; } String text = sourceCode.substring(tokenStart, currentLoc+1); + + //Compare against a list of keywords in the language TokenType type = keywords.get(text); if(type == null){ createToken(TokenType.IDENTIFIER, text); } else{ createToken(type, text); } - + } else { + //Report an unknown character Language.displayError(line,"Unexpected Character"); } } @@ -154,23 +179,37 @@ public class TokenScanner { } - //Test for end of file + /** + * Method to check the end of the source code + * @return if the end of the source code has been reached + */ private boolean checkEOF(){ return currentLoc>=sourceCode.length(); } - //Create a token without a value + /** + * Create a token with a value of null + * @param type the token type + */ private void createTokenNull(TokenType type){ createToken(type, null); } - //Create token + /** + * Create a token and add to the list + * @param type the token type + * @param value the value of the token + */ private void createToken(TokenType type, Object value){ String tokenText = sourceCode.substring(tokenStart, currentLoc+1); tokens.add(new Token(type, tokenText, value, line)); } - //Check if the next char matches a given char + /** + * Compare the next character in the source code to a given character + * @param matchChar the character to compare against + * @return if the character matches + */ private boolean checkNextChar(char matchChar){ if (checkEOF()){ return false; @@ -182,7 +221,10 @@ public class TokenScanner { return false; } - //Look at the next char in the source code + /** + * gets the next character in the source code + * @return the next character + */ private char lookAhead(){ if (currentLoc+1>=sourceCode.length()){ return ' '; @@ -192,7 +234,10 @@ public class TokenScanner { } } - //Look 2 chars ahead in the source code + /** + * look at the character two ahead in the source code + * @return the character two ahead + */ private char lookTwoAhead(){ if (currentLoc+2>=sourceCode.length()){ return ' '; @@ -202,16 +247,26 @@ public class TokenScanner { } } - //Check if a given char is a digit + /** + * checks if a given character is numerical + * @param checkChar the character to check + * @return if the character is numerical + */ private boolean checkIsDigit(char checkChar){ return checkChar>='0' && checkChar<='9'; } + /** + * check if a character is alphabetical + * @param checkChar the character to check + * @return if the character is alphabetical + */ private boolean checkIsAlpha(char checkChar){ return ('a'<=checkChar && checkChar<='z')|| ('A'<=checkChar && checkChar<='Z'); } + //A hashmap of the keywords used in the language private static final Map<String, TokenType> keywords; static { @@ -232,5 +287,6 @@ public class TokenScanner { keywords.put("program", TokenType.PROGRAM); keywords.put("return", TokenType.RETURN); keywords.put("function", TokenType.FUNCTION); + keywords.put("subroutine", TokenType.SUBROUTINE); } } diff --git a/src/Compiler/TokenType.java b/src/Compiler/TokenType.java index 6a8f5c0..d3df02e 100644 --- a/src/Compiler/TokenType.java +++ b/src/Compiler/TokenType.java @@ -13,7 +13,7 @@ public enum TokenType { NUMBER,IDENTIFIER,STRING, INT,REAL,PRINT,ENDPRINT,IF,THEN,END,ELSE,LEN,DO,WHILE, - AND,OR,NOT,DIMENSION,PROGRAM,FUNCTION,RETURN, + AND,OR,NOT,DIMENSION,PROGRAM,FUNCTION,RETURN,SUBROUTINE, EOF } diff --git a/src/Compiler/Translator.java b/src/Compiler/Translator.java index e0bff23..d520b67 100644 --- a/src/Compiler/Translator.java +++ b/src/Compiler/Translator.java @@ -6,16 +6,25 @@ import java.util.List; import Compiler.Expression.*; import Compiler.Statement.*; - +/** + * Class to take a convert a list of statements into equivalent C source code + */ public class Translator{ List<String> CCode = new ArrayList<>(); private Environment environment = new Environment(); - + /** + * Method to take a list of statements and convert to C code + * @param statements a list of statement objects + * @param printC a variable to say if the produced C code should be outputted + * @return a list of strings for each line of the produced C code + */ public List<String> compileToC(List<Statement> statements, boolean printC){ + //Write basic include header files CCode.add("#include <stdio.h>"); CCode.add("#include <string.h>"); + //Try and write each statement, with a space between each try{ for (Statement statement: statements){ evaluateStatement(statement); @@ -25,6 +34,7 @@ public class Translator{ } + //Output the C code if desired if (printC) { for(String t:CCode){ System.out.println(t); @@ -34,7 +44,12 @@ public class Translator{ return CCode; } + /** + * Method to write a single statement to C + * @param statement the statement to write to C + */ private void evaluateStatement(Statement statement){ + //Call the correct function for each statement type switch(statement.getStatmentType()){ case "main": evalMainFunction((MainFunction)statement); @@ -78,40 +93,76 @@ public class Translator{ } } + /** + * Method to write the main function + * @param stmt statement to write + */ private void evalMainFunction(MainFunction stmt){ CCode.add("int main(){"); evaluateStatement(stmt.block); CCode.add("}"); } + /** + * Method to write a function + * @param stmt statement to write + */ private void evalFunction(Function stmt){ - - String functionString = stmt.returnType+" "+stmt.name.text+"("; + String functionString; + if(!(stmt.returnType==null)){ + functionString = stmt.returnType+" "+stmt.name.text+"("; + }else{ + functionString = "void "+stmt.name.text+"("; + } boolean first=true; + //Write each function argument into C for(int i=0;i<stmt.arguments.size();i++){ if(!first){ functionString+=","; } - environment.defineVariable(evaluateExpression(stmt.arguments.get(i)), stmt.argumentTypes.get(i)); - functionString+=stmt.argumentTypes.get(i)+" "+evaluateExpression(stmt.arguments.get(i)); + String type = stmt.argumentTypes.get(i); + Expression arg = stmt.arguments.get(i); + + //Define each argument variable in the environment + if(type.equals("char*")){ + environment.defineVariable(evaluateExpression(arg), "string"); + }else{ + environment.defineVariable(evaluateExpression(arg), type); + } + + functionString+=type+" "+evaluateExpression(arg); first=false; } functionString+="){"; + + //Write function block CCode.add(functionString); evaluateStatement(stmt.block); CCode.add("}"); } + /** + * Method to write a return statement + * @param stmt statement to write + */ private void evalReturnStatement(ReturnStatement stmt){ CCode.add("return "+evaluateExpression(stmt.returnValue)+";"); - - } + /** + * Method to write a function declaration + * @param stmt statement to write + */ private void evalFunctionDeclaration(FunctionDeclaration stmt){ - - String functionString = stmt.returnType+" "+stmt.name.text+"("; + String functionString; + if(!(stmt.returnType==null)){ + functionString = stmt.returnType+" "+stmt.name.text+"("; + }else{ + functionString = "void "+stmt.name.text+"("; + } boolean first=true; + + //Write each argument data type for(int i=0;i<stmt.argumentTypes.size();i++){ if(!first){ functionString+=","; @@ -120,22 +171,42 @@ public class Translator{ first=false; } functionString+=");"; + CCode.add(functionString); } + /** + * Method to write an expression statement + * @param stmt statement to write + */ private void evalExpressionStatement(ExpressionStatement stmt){ - evaluateExpression(stmt.expr); + String value = evaluateExpression(stmt.expr); + CCode.add(value+";"); } + /** + * Method to write a string declaration statement + * @param stringdec statement to write + */ private void evalStringDeclaration(StringDeclaration stringdec){ + //Add variable to the environment environment.defineVariable(stringdec.name.text, "string"); + + //Get the size of the string int size = (int)((Expression.Literal)stringdec.length).value.value; + //Increase the size of the string by 1 to match C code correctly size++; CCode.add("char "+stringdec.name.text+"["+size+"];"); } + /** + * Method to write a variable declaration + * @param vardec statement to write + */ private void evalVariableDeclaration(VariableDeclaration vardec){ + //Add variable to the environment environment.defineVariable(vardec.name.text, vardec.type); + //Write correct data type if(vardec.type.equals("int")){ CCode.add("int "+vardec.name.text+";"); } else if(vardec.type.equals("real")){ @@ -143,8 +214,15 @@ public class Translator{ } } + /** + * Method to write an array declaration + * @param arraydec statement to write + */ private void evalArrayDeclaration(ArrayDeclaration arraydec){ + //Add variable to the environment environment.defineVariable(arraydec.name.text, arraydec.type); + + //Get the array data type String arrayString = ""; if(arraydec.type.equals("int")){ arrayString+="int "; @@ -152,6 +230,8 @@ public class Translator{ arrayString+="real "; } arrayString+=arraydec.name.text; + + //Write each dimension of the array for(Expression expr:arraydec.dimensions){ arrayString+="["; arrayString+=evaluateExpression(expr); @@ -163,23 +243,36 @@ public class Translator{ } + /** + * Method to write a block statement + * @param block statement to write + */ private void evalBlockStatement(BlockStatement block){ + //Write each statement in the block for(Statement stmt:block.statements){ evaluateStatement(stmt); } } + /** + * Method to write a print statement + * @param print statement to write + */ private void evalPrintStatement(PrintStatement print){ String types=""; String values=""; boolean first=true; + + //Write each expression in the print statement for(Expression expr:print.exprList){ + //Don't add a comma for the first value in the print statement if(!first){ values+=","; }else{ first=false; } String exprType=""; + //Get the data type for each expression if(expr instanceof Expression.Literal){ exprType=((Expression.Literal)expr).type; } @@ -189,6 +282,8 @@ public class Translator{ else if (expr instanceof Expression.ArrayVariable){ exprType=(String)environment.getVariable((((Expression.ArrayVariable)expr).name)); } + + //Set the correct data type for the print statement if (exprType.equals("int")){ types+="%d"; } else if (exprType.equals("double")){ @@ -196,16 +291,24 @@ public class Translator{ } else if (exprType.equals("string")){ types+="%s"; } + //Add the actual expression value values+=evaluateExpression(expr); } + //Add a line break at the end of the print statement text types+="\\n"; CCode.add("printf(\""+types+"\","+values+");"); } + /** + * Method to write an if statement + * @param ifstatement statement to write + */ private void evalIfStatement(IfStatement ifstatement){ - + //Write if statement block CCode.add("if("+evaluateExpression(ifstatement.condition)+"){"); evaluateStatement(ifstatement.ifBlock); + + //Write else block if needed if(!(ifstatement.elseBlock==null)){ CCode.add("}"); CCode.add("else {"); @@ -214,24 +317,43 @@ public class Translator{ CCode.add("}"); } + /** + * Method to write a do statement + * @param dostatement statement to write + */ private void evalDoStatement(DoStatement dostatement){ + //Form the sections of the for loop, assuming the step variable has not been defined String start = evaluateExpression(dostatement.variable)+"="+evaluateExpression(dostatement.start); String stop = evaluateExpression(dostatement.variable)+"<="+evaluateExpression(dostatement.stop); String step = evaluateExpression(dostatement.variable)+"++"; + + //Update step variable if needed if(!(dostatement.step==null)){ step = evaluateExpression(dostatement.variable)+"+="+evaluateExpression(dostatement.step); } + CCode.add("for("+start+";"+stop+";"+step+"){"); evaluateStatement(dostatement.codeBlock); CCode.add("}"); } + /** + * Method to write a do while loop + * @param dowhilestatement statement to write + */ private void evalDoWhileStatement(DoWhileStatement dowhilestatement){ CCode.add("while("+evaluateExpression(dowhilestatement.condition)+"){"); evaluateStatement(dowhilestatement.codeBlock); CCode.add("}"); } + /** + * Method to write the correct expression object to C + * Since an expression is not a full line, the string is returned + * Except for assignment expressions + * @param expression the expression to write + * @return the string representation of an expression + */ private String evaluateExpression(Expression expression){ switch(expression.getExpressionType()){ case "binary": @@ -243,6 +365,7 @@ public class Translator{ case "bracket": return evaluateBracketedExpression((BracketedExpression)expression); case "assign": + //Assignments are full lines, so no need to return the string evaluateAssignmentExpression((AssignmentExpression)expression); return ""; case "arrayvar": @@ -256,6 +379,11 @@ public class Translator{ } } + /** + * Method to write a binary expression + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateBinaryExpression(Binary expr){ switch (expr.op.type){ case PLUS: @@ -288,6 +416,11 @@ public class Translator{ return null; } + /** + * Method to write a singular expression + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateSingularExpression(Singular expr){ switch (expr.op.type){ case NOT: @@ -298,24 +431,42 @@ public class Translator{ return null; } + /** + * Method to write a literal expression + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateLiteralExpression(Literal expr){ return (expr.value.value).toString(); } + /** + * Method to write a bracketed expression + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateBracketedExpression(BracketedExpression expr){ return "("+evaluateExpression(expr.expr)+")"; } + /** + * Method to write an assignment expression + * @param expr the expression to write + */ private void evaluateAssignmentExpression(AssignmentExpression expr){ Token name=null; + //Get the name of the variable being assigned to if(expr.variable instanceof Expression.Variable){ name = ((Expression.Variable)expr.variable).name; } else if(expr.variable instanceof Expression.ArrayVariable){ name = ((Expression.ArrayVariable)expr.variable).name; } + //Check if the variable has been previously declared if(environment.checkVariable(name)){ + //Check if the value being assigned is a literal or some other expression if(expr.value instanceof Expression.Literal){ + //Strings are handled differently in C, so the string has to be copied into the variable if(((Expression.Literal)expr.value).type.equals("string")){ CCode.add("strcpy("+evaluateExpression(expr.variable)+","+evaluateExpression(expr.value)+");"); }else{ @@ -328,10 +479,17 @@ public class Translator{ } } + /** + * Method to write an array variable + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateArrayVariable(ArrayVariable expr){ + //Check the array has been defined if(environment.checkVariable(expr.name)){ String arrayString=""; arrayString+=expr.name.text; + //Write each dimension value for(Expression position:expr.positions){ arrayString+="["; arrayString+=evaluateExpression(position); @@ -342,14 +500,26 @@ public class Translator{ return null; } + /** + * Method to write a variable expression + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateVariableExpression(Variable expr){ return expr.name.text; } + /** + * Method to write a function call + * @param expr the expression to write + * @return the string representation of the expression + */ private String evaluateFunctionCall(FunctionCall expr){ String functioncall=""; functioncall+=expr.name.text+"("; boolean first=true; + + //Write each argument of the function call for(Token arg:expr.arguments){ if(!first){ functioncall+=","; @@ -360,9 +530,6 @@ public class Translator{ functioncall+=")"; return functioncall; } - - - } diff --git a/src/examples/example.ft b/src/examples/example.ft index 4a4981a..ab5143d 100644 --- a/src/examples/example.ft +++ b/src/examples/example.ft @@ -1,15 +1,20 @@ program example -int ::test -int::testtwo -test=5 -testtwo=7 -int::testthree -testthree=add(test,testtwo) -print*,testthree +int::a +int::b +int::c +a=2+3 +b=3 +c=power(a,b) +print*,c end program example -function int add(int a, int b) -int::value -value=a+b -return value + +function int power(int value,int power) +int::i +int::final +final=1 +do i=0,power-1 +final=final*value +end do +return final end
\ No newline at end of file |