├── .gitignore ├── LICENSE ├── README.md ├── asset └── language-schema.png ├── pom.xml └── src ├── main └── java │ └── org │ └── example │ └── toylanguage │ ├── LexicalParser.java │ ├── RunToyLanguage.java │ ├── StatementParser.java │ ├── ToyLanguage.java │ ├── context │ ├── BreakContext.java │ ├── BreakScope.java │ ├── ClassInstanceContext.java │ ├── ExceptionContext.java │ ├── MemoryContext.java │ ├── MemoryScope.java │ ├── NextContext.java │ ├── NextScope.java │ ├── ReturnContext.java │ ├── ReturnScope.java │ ├── ValueReference.java │ ├── definition │ │ ├── ClassDefinition.java │ │ ├── ClassDetails.java │ │ ├── Definition.java │ │ ├── DefinitionContext.java │ │ ├── DefinitionScope.java │ │ ├── FunctionDefinition.java │ │ ├── FunctionDetails.java │ │ └── package-info.java │ └── package-info.java │ ├── exception │ ├── SyntaxException.java │ ├── ToyLanguageException.java │ └── package-info.java │ ├── expression │ ├── ArrayExpression.java │ ├── AssignExpression.java │ ├── ClassExpression.java │ ├── Expression.java │ ├── ExpressionReader.java │ ├── FunctionExpression.java │ ├── VariableExpression.java │ ├── operator │ │ ├── AdditionOperator.java │ │ ├── ArrayAppendOperator.java │ │ ├── ArrayValueOperator.java │ │ ├── AssignmentOperator.java │ │ ├── BinaryOperatorExpression.java │ │ ├── ClassCastOperator.java │ │ ├── ClassInstanceOfOperator.java │ │ ├── ClassInstanceOperator.java │ │ ├── ClassPropertyOperator.java │ │ ├── DivisionOperator.java │ │ ├── EqualsOperator.java │ │ ├── ExponentiationOperator.java │ │ ├── FloorDivisionOperator.java │ │ ├── GreaterThanOperator.java │ │ ├── GreaterThanOrEqualToOperator.java │ │ ├── LessThanOperator.java │ │ ├── LessThanOrEqualToOperator.java │ │ ├── LogicalAndOperator.java │ │ ├── LogicalOrOperator.java │ │ ├── ModuloOperator.java │ │ ├── MultiplicationOperator.java │ │ ├── NestedClassInstanceOperator.java │ │ ├── NotEqualsOperator.java │ │ ├── NotOperator.java │ │ ├── Operator.java │ │ ├── OperatorExpression.java │ │ ├── SubtractionOperator.java │ │ ├── UnaryOperatorExpression.java │ │ └── package-info.java │ ├── package-info.java │ └── value │ │ ├── ArrayValue.java │ │ ├── ClassValue.java │ │ ├── ComparableValue.java │ │ ├── IterableValue.java │ │ ├── LogicalValue.java │ │ ├── NullValue.java │ │ ├── NumericValue.java │ │ ├── TextValue.java │ │ ├── ThisValue.java │ │ ├── Value.java │ │ └── package-info.java │ ├── statement │ ├── AssertStatement.java │ ├── ClassStatement.java │ ├── CompositeStatement.java │ ├── ConditionStatement.java │ ├── ExpressionStatement.java │ ├── FunctionStatement.java │ ├── HandleExceptionStatement.java │ ├── InputStatement.java │ ├── PrintStatement.java │ ├── RaiseExceptionStatement.java │ ├── ReturnStatement.java │ ├── Statement.java │ ├── loop │ │ ├── AbstractLoopStatement.java │ │ ├── BreakStatement.java │ │ ├── ForLoopStatement.java │ │ ├── IterableLoopStatement.java │ │ ├── NextStatement.java │ │ └── WhileLoopStatement.java │ └── package-info.java │ └── token │ ├── Token.java │ ├── TokenType.java │ ├── TokensStack.java │ └── package-info.java └── test ├── java └── org │ └── example │ └── toylanguage │ ├── LexicalParserTest.java │ ├── StatementParserTest.java │ ├── ToyLanguageTest.java │ └── package-info.java └── resources ├── binary_search.toy ├── bubble_sort.toy ├── calculator.toy ├── cast_type.toy ├── fibonacci_number.toy ├── handle_exception.toy ├── instance_of.toy ├── is_same_tree.toy ├── raise_exception.toy └── stack.toy /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | out/ 4 | target/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Alexander Makeev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toy language 2 | 3 | ![Toy language](asset/language-schema.png) 4 | 5 | This is a simple toy language implementation. More details: [Building Your Own Programming Language From Scratch](https://hackernoon.com/building-your-own-programming-language-from-scratch) 6 | 7 | ### [Examples](src/test/resources) 8 | 9 | ## Syntax 10 | 11 | ### Basic constructions 12 | 1. Variables declaration 13 | ``` 14 | # plain types 15 | = 16 | 17 | a1 = 123 18 | a2 = "hello world" 19 | 20 | # class instance 21 | = new [ , , ... ] 22 | 23 | left_tree_node = new TreeNode [ 1 ] 24 | right_tree_node = new TreeNode [ 2 ] 25 | tree_node = new TreeNode [ 3, left_tree_node, right_tree_node ] 26 | tree_node = new TreeNode [ 3, new TreeNode [ 1 ], null ] 27 | 28 | # array 29 | = { , , ... } 30 | example_array = { 1, 2, "three", new TreeNode [ 4 ] } 31 | empty_array = {} 32 | ``` 33 | 34 | 2. Conditions 35 | ``` 36 | if 37 | # statements 38 | elif 39 | # statements 40 | else 41 | # statements 42 | end 43 | 44 | if a1 > 5 and tree_node :: value == 3 45 | # statements 46 | elif a2 == "hello" or a3 == "world" 47 | # statements 48 | else 49 | # statements 50 | end 51 | ``` 52 | 53 | 3. Print to console 54 | ``` 55 | print 56 | print a1 + a2 + tree_node :: value 57 | ``` 58 | 59 | 4. Input from console 60 | ``` 61 | input 62 | input number 63 | ``` 64 | 65 | 5. Functions 66 | ``` 67 | fun 68 | 69 | end 70 | 71 | fun [ , ... ] 72 | 73 | return 74 | end 75 | 76 | fun fibonacci_number [ n ] 77 | if n < 2 78 | return n 79 | end 80 | return fibonacci_number [ n - 1 ] + fibonacci_number [ n - 2 ] 81 | end 82 | ``` 83 | 84 | 6. Loops 85 | ``` 86 | # For loop 87 | loop in .. 88 | # statements 89 | end 90 | 91 | # Specify the step 92 | loop in .. by 93 | # statements 94 | # seed increment statement 95 | end 96 | 97 | # While loop 98 | loop 99 | # statements 100 | end 101 | 102 | # Iterable loop (for-each) 103 | loop in 104 | # statements 105 | end 106 | 107 | # terminate the loop 108 | loop in .. by 109 | if 110 | break 111 | end 112 | end 113 | 114 | # jump to the next iteration 115 | loop in .. by 116 | if 117 | next 118 | end 119 | end 120 | ``` 121 | 122 | 7. Exceptions 123 | ``` 124 | # raise (throw) an exception 125 | raise "Error" 126 | 127 | # raise a class instance 128 | class MyException [message] 129 | end 130 | 131 | raise new MyException ["Error message"] 132 | 133 | # rescue (catch) exception 134 | begin 135 | raise new MyException ["Error message"] 136 | rescue err 137 | print "Rescue block" 138 | # access property of raised (thrown) object 139 | print err :: message 140 | end 141 | 142 | # ensure (finally) 143 | begin 144 | raise new MyException ["error message"] 145 | rescue error 146 | print "Rescue block" 147 | print error :: message 148 | ensure 149 | print "Ensure block" 150 | end 151 | ``` 152 | 153 | ### Data types 154 | There are the following data types currently supported: 155 | 1. Numeric 156 | ``` 157 | number1 = 1 158 | number2 = 2. 159 | number3 = 3.21 160 | number4 = 0.432 161 | number5 = .543 162 | number6 = -1 163 | ``` 164 | 165 | 2. Text 166 | ``` 167 | text = "hello world" 168 | print text{1} 169 | text{1} = "a" 170 | print text 171 | ``` 172 | 173 | 3. Logical 174 | ``` 175 | logical1 = true 176 | logical2 = false 177 | ``` 178 | 179 | 4. Class 180 | ``` 181 | class 182 | end 183 | 184 | class [ , , ... ] 185 | # inner statements 186 | print 187 | 188 | fun [ , ] 189 | # function statements 190 | this :: = 191 | end 192 | end 193 | 194 | # derived classes 195 | class : , 196 | end 197 | 198 | class [ , ... ]: [ , ... ] 199 | end 200 | 201 | class Lamp [ type, is_on ] 202 | fun turn_on 203 | is_on = true 204 | end 205 | 206 | fun turn_off 207 | is_on = false 208 | end 209 | 210 | fun set_is_on [ is_on ] 211 | this :: is_on = is_on 212 | end 213 | end 214 | 215 | lamp_instance = new Lamp [ "Halogen", false ] 216 | 217 | # get/set class's property 218 | lamp_is_on = lamp_instance :: is_on 219 | lamp_instance :: type = "Led" 220 | 221 | # invoke class's function 222 | lamp_instance :: turn_off [] 223 | ``` 224 | 225 | 5. Arrays 226 | ``` 227 | = { , , ... } 228 | example_array = { 1, 2, "three", new TreeNode [ 4 ] } 229 | empty_array = {} 230 | 231 | # get an array's value 232 | = { } 233 | value = items{1} 234 | 235 | # set an array's value 236 | { } = 237 | items{1} = 123 238 | 239 | # append a value to array 240 | << 241 | items = {1,2} 242 | items << 3 #{1,2,3} 243 | ``` 244 | 245 | 6. Null 246 | ``` 247 | value = null 248 | ``` 249 | 250 | ### Operators 251 | To calculate a complex expression in the proper order, each of the supported operators has its own precedence: 252 | 253 | | Operator | Value | Precedence | Example | 254 | |------------------------|--------------|------------|----------------------------------| 255 | | Assignment | ```=``` | 1 | ```a = 5``` | 256 | | Append value to array | ```<<``` | 1 | ```array << "value"``` | 257 | | Logical OR | ```or``` | 2 | ```true or false``` | 258 | | Logical AND | ```and``` | 3 | ```true and true``` | 259 | | Left Paren | ```(``` | 4 | | 260 | | Right Paren | ```)``` | 4 | | 261 | | Equals | ```==``` | 5 | ```a == 5``` | 262 | | Not Equals | ```!=``` | 5 | ```a != 5``` | 263 | | Greater Than Or Equals | ```>=``` | 5 | ```a >= 5``` | 264 | | Greater Than | ```>``` | 5 | ```a > 5``` | 265 | | Less Than Or Equals | ```<=``` | 5 | ```a <= 5``` | 266 | | Less Than | ```<``` | 5 | ```a < 5``` | 267 | | Addition | ```+``` | 6 | ```a + 5``` | 268 | | Subtraction | ```-``` | 6 | ```a - 5``` | 269 | | Exponentiation | ```**``` | 7 | ```a ** 5``` | 270 | | Multiplication | ```*``` | 7 | ```a * 5``` | 271 | | Division | ```/``` | 7 | ```a / 5``` | 272 | | Floor Division | ```//``` | 7 | ```a // 5``` | 273 | | Modulo | ```%``` | 7 | ```a % 5``` | 274 | | NOT | ```!``` | 8 | ```!false``` | 275 | | Class Instance | ```new``` | 8 | ```type = new Type [ value ]``` | 276 | | Nested Class Instance | ```:: new``` | 8 | ```type :: new NestedType``` | 277 | | Class Property | ```::``` | 8 | ```type_value = type :: value``` | 278 | | Class Cast | ```as``` | 8 | ```type as Supertype``` | 279 | | Class Instance Of | ```is``` | 8 | ```type is Supertype``` | 280 | 281 | 282 | ### Run this program 283 | Firstly, compile and package the program into a uber jar, go to the ./target folder and then run the following command 284 | 285 | java -cp toy-language.jar org.example.toylanguage.RunToyLanguage ../src/test/resources/stack.toy 286 | -------------------------------------------------------------------------------- /asset/language-schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexandermakeev/toy-language/a837d7ca132eaa9cf259aee262c9dd112cb79da8/asset/language-schema.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | org.example 7 | toy-language 8 | 0.0.11 9 | 10 | 11 | 12 | central2 13 | Central Repository 14 | https://repo.maven.apache.org/maven2 15 | default 16 | 17 | false 18 | 19 | 20 | 21 | central 22 | Maven Central 23 | default 24 | https://repo1.maven.org/maven2 25 | 26 | false 27 | 28 | 29 | 30 | 31 | 32 | 11 33 | ${java.version} 34 | ${java.version} 35 | 5.9.2 36 | 3.12.0 37 | 1.18.26 38 | 39 | 40 | 41 | 42 | org.apache.commons 43 | commons-lang3 44 | ${commons-lang3.version} 45 | 46 | 47 | 48 | org.projectlombok 49 | lombok 50 | ${lombok.version} 51 | provided 52 | 53 | 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter-engine 58 | ${junit.version} 59 | test 60 | 61 | 62 | org.junit.jupiter 63 | junit-jupiter-api 64 | ${junit.version} 65 | test 66 | 67 | 68 | 69 | 70 | ${project.artifactId} 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-compiler-plugin 75 | 3.11.0 76 | 77 | ${java.version} 78 | ${java.version} 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-surefire-plugin 84 | 3.0.0 85 | 86 | 87 | org.apache.maven.plugins 88 | maven-shade-plugin 89 | 3.4.1 90 | 91 | 92 | package 93 | 94 | shade 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/LexicalParser.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import org.example.toylanguage.exception.SyntaxException; 4 | import org.example.toylanguage.token.Token; 5 | import org.example.toylanguage.token.TokenType; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Transforming the source code into tokens 14 | *

15 | * 16 | * @see Token 17 | * @see TokenType 18 | */ 19 | public class LexicalParser { 20 | /** 21 | * Accumulated tokens 22 | */ 23 | private final List tokens; 24 | /** 25 | * Source code 26 | */ 27 | private final String source; 28 | /** 29 | * Current row number 30 | */ 31 | private int rowNumber; 32 | 33 | /** 34 | * Parse incoming sourceCode into List of lexemes following the TokenType 35 | */ 36 | public static List parse(String sourceCode) { 37 | LexicalParser parser = new LexicalParser(sourceCode); 38 | parser.parse(); 39 | return parser.tokens; 40 | } 41 | 42 | private LexicalParser(String source) { 43 | this.source = source; 44 | this.tokens = new ArrayList<>(); 45 | this.rowNumber = 1; 46 | } 47 | 48 | private void parse() { 49 | int position = 0; // position in the source code 50 | while (position < source.length()) { 51 | // read a lexeme and skip its length 52 | position += nextToken(position); 53 | } 54 | } 55 | 56 | // find the next token 57 | // returns number of chars that have been parsed to find a lexeme including length of the found lexeme 58 | private int nextToken(int position) { 59 | String nextToken = source.substring(position); 60 | 61 | for (TokenType tokenType : TokenType.values()) { 62 | Pattern pattern = Pattern.compile("^" + tokenType.getRegex()); 63 | Matcher matcher = pattern.matcher(nextToken); 64 | if (matcher.find()) { 65 | if (tokenType != TokenType.Whitespace) { 66 | // group(1) is used to get text literal without double quotes 67 | String value = matcher.groupCount() > 0 ? matcher.group(1) : matcher.group(); 68 | Token token = Token.builder().type(tokenType).value(value).rowNumber(rowNumber).build(); 69 | tokens.add(token); 70 | 71 | if (tokenType == TokenType.LineBreak) { 72 | rowNumber++; 73 | } 74 | } 75 | 76 | return matcher.group().length(); 77 | } 78 | } 79 | 80 | throw new SyntaxException(String.format("Invalid expression at line %d", rowNumber)); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/RunToyLanguage.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import java.nio.file.FileSystems; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | 7 | public class RunToyLanguage { 8 | public static void main(String[] args) { 9 | Path sourceFilePath = FileSystems.getDefault().getPath(args[0]); 10 | if (Files.exists(sourceFilePath)) { 11 | new ToyLanguage().execute(sourceFilePath); 12 | } 13 | else { 14 | System.out.println("Cannot resolve args[0] [" + sourceFilePath + "] as a valid file path."); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/StatementParser.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.example.toylanguage.context.definition.*; 6 | import org.example.toylanguage.exception.SyntaxException; 7 | import org.example.toylanguage.expression.Expression; 8 | import org.example.toylanguage.expression.ExpressionReader; 9 | import org.example.toylanguage.expression.VariableExpression; 10 | import org.example.toylanguage.expression.operator.OperatorExpression; 11 | import org.example.toylanguage.expression.value.LogicalValue; 12 | import org.example.toylanguage.statement.*; 13 | import org.example.toylanguage.statement.loop.*; 14 | import org.example.toylanguage.token.Token; 15 | import org.example.toylanguage.token.TokenType; 16 | import org.example.toylanguage.token.TokensStack; 17 | 18 | import java.util.*; 19 | 20 | @RequiredArgsConstructor 21 | @Getter 22 | public class StatementParser { 23 | private final TokensStack tokens; 24 | private final Scanner scanner; 25 | private final CompositeStatement compositeStatement; 26 | 27 | public static void parse(StatementParser parent, CompositeStatement compositeStatement, DefinitionScope definitionScope) { 28 | DefinitionContext.pushScope(definitionScope); 29 | try { 30 | StatementParser parser = new StatementParser(parent.getTokens(), parent.getScanner(), compositeStatement); 31 | while (parser.hasNextStatement()) { 32 | parser.parseExpression(); 33 | } 34 | } finally { 35 | DefinitionContext.endScope(); 36 | } 37 | } 38 | 39 | public static void parse(List tokens, CompositeStatement compositeStatement) { 40 | StatementParser parser = new StatementParser(new TokensStack(tokens), new Scanner(System.in), compositeStatement); 41 | while (parser.hasNextStatement()) { 42 | parser.parseExpression(); 43 | } 44 | } 45 | 46 | private boolean hasNextStatement() { 47 | if (!tokens.hasNext()) 48 | return false; 49 | if (tokens.peek(TokenType.Operator, TokenType.Variable, TokenType.This)) 50 | return true; 51 | if (tokens.peek(TokenType.Keyword)) { 52 | return !tokens.peek(TokenType.Keyword, "elif", "else", "rescue", "ensure", "end"); 53 | } 54 | return false; 55 | } 56 | 57 | private void parseExpression() { 58 | Token token = tokens.next(TokenType.Keyword, TokenType.Variable, TokenType.This, TokenType.Operator); 59 | switch (token.getType()) { 60 | case Variable: 61 | case Operator: 62 | case This: 63 | parseExpressionStatement(token); 64 | break; 65 | case Keyword: 66 | parseKeywordStatement(token); 67 | break; 68 | default: 69 | throw new SyntaxException(String.format("Statement can't start with the following lexeme `%s`", token)); 70 | } 71 | } 72 | 73 | private void parseExpressionStatement(Token rowToken) { 74 | tokens.back(); // go back to read an expression from the beginning 75 | Expression value = ExpressionReader.readExpression(tokens); 76 | ExpressionStatement statement = new ExpressionStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), value); 77 | compositeStatement.addStatement(statement); 78 | } 79 | 80 | private void parseKeywordStatement(Token token) { 81 | switch (token.getValue()) { 82 | case "print": 83 | parsePrintStatement(token); 84 | break; 85 | case "input": 86 | parseInputStatement(token); 87 | break; 88 | case "if": 89 | parseConditionStatement(token); 90 | break; 91 | case "class": 92 | parseClassDefinition(token); 93 | break; 94 | case "fun": 95 | parseFunctionDefinition(token); 96 | break; 97 | case "return": 98 | parseReturnStatement(token); 99 | break; 100 | case "loop": 101 | parseLoopStatement(token); 102 | break; 103 | case "break": 104 | parseBreakStatement(token); 105 | break; 106 | case "next": 107 | parseNextStatement(token); 108 | break; 109 | case "assert": 110 | parseAssertStatement(token); 111 | break; 112 | case "raise": 113 | parseRaiseExceptionStatement(token); 114 | break; 115 | case "begin": 116 | parseHandleExceptionStatement(token); 117 | break; 118 | default: 119 | throw new SyntaxException(String.format("Failed to parse a keyword: %s", token.getValue())); 120 | } 121 | } 122 | 123 | private void parsePrintStatement(Token rowToken) { 124 | Expression expression = ExpressionReader.readExpression(tokens); 125 | PrintStatement statement = new PrintStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), expression); 126 | compositeStatement.addStatement(statement); 127 | } 128 | 129 | private void parseInputStatement(Token rowToken) { 130 | Token variable = tokens.next(TokenType.Variable); 131 | InputStatement statement = new InputStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), variable.getValue(), scanner::nextLine); 132 | compositeStatement.addStatement(statement); 133 | } 134 | 135 | private void parseConditionStatement(Token rowToken) { 136 | tokens.back(); 137 | ConditionStatement conditionStatement = new ConditionStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 138 | 139 | while (!tokens.peek(TokenType.Keyword, "end")) { 140 | //read condition case 141 | Token type = tokens.next(TokenType.Keyword, "if", "elif", "else"); 142 | Expression caseCondition; 143 | if (type.getValue().equals("else")) { 144 | caseCondition = new LogicalValue(true); //else case does not have the condition 145 | } else { 146 | caseCondition = ExpressionReader.readExpression(tokens); 147 | } 148 | 149 | //read case statements 150 | CompositeStatement caseStatement = new CompositeStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 151 | DefinitionScope caseScope = DefinitionContext.newScope(); 152 | StatementParser.parse(this, caseStatement, caseScope); 153 | 154 | //add case 155 | conditionStatement.addCase(caseCondition, caseStatement); 156 | } 157 | tokens.next(TokenType.Keyword, "end"); 158 | 159 | compositeStatement.addStatement(conditionStatement); 160 | } 161 | 162 | private void parseClassDefinition(Token rowToken) { 163 | // read class details 164 | ClassDetails classDetails = readClassDetails(); 165 | 166 | // read base types 167 | Set baseTypes = new LinkedHashSet<>(); 168 | if (tokens.peek(TokenType.GroupDivider, ":")) { 169 | while (tokens.peek(TokenType.GroupDivider, ":", ",")) { 170 | tokens.next(); 171 | ClassDetails baseClassDetails = readClassDetails(); 172 | baseTypes.add(baseClassDetails); 173 | } 174 | } 175 | 176 | // add class definition 177 | DefinitionScope classScope = DefinitionContext.newScope(); 178 | ClassStatement classStatement = new ClassStatement(rowToken.getRowNumber(), classDetails.getName()); 179 | ClassDefinition classDefinition = new ClassDefinition(classDetails, baseTypes, classStatement, classScope); 180 | DefinitionContext.getScope().addClass(classDefinition); 181 | 182 | //parse class's statements 183 | StatementParser.parse(this, classStatement, classScope); 184 | tokens.next(TokenType.Keyword, "end"); 185 | } 186 | 187 | private ClassDetails readClassDetails() { 188 | Token className = tokens.next(TokenType.Variable); 189 | List classArguments = new ArrayList<>(); 190 | if (tokens.peek(TokenType.GroupDivider, "[")) { 191 | tokens.next(); //skip open square bracket 192 | 193 | while (!tokens.peek(TokenType.GroupDivider, "]")) { 194 | Token argumentToken = tokens.next(TokenType.Variable); 195 | classArguments.add(argumentToken.getValue()); 196 | 197 | if (tokens.peek(TokenType.GroupDivider, ",")) 198 | tokens.next(); 199 | } 200 | 201 | tokens.next(TokenType.GroupDivider, "]"); //skip close square bracket 202 | } 203 | return new ClassDetails(className.getValue(), classArguments); 204 | } 205 | 206 | private void parseFunctionDefinition(Token rowToken) { 207 | Token name = tokens.next(TokenType.Variable); 208 | 209 | List arguments = new ArrayList<>(); 210 | 211 | if (tokens.peek(TokenType.GroupDivider, "[")) { 212 | 213 | tokens.next(TokenType.GroupDivider, "["); //skip open square bracket 214 | 215 | while (!tokens.peek(TokenType.GroupDivider, "]")) { 216 | Token argumentToken = tokens.next(TokenType.Variable); 217 | arguments.add(argumentToken.getValue()); 218 | 219 | if (tokens.peek(TokenType.GroupDivider, ",")) 220 | tokens.next(); 221 | } 222 | 223 | tokens.next(TokenType.GroupDivider, "]"); //skip close square bracket 224 | } 225 | 226 | //add function definition 227 | String blockName = name.getValue(); 228 | if (compositeStatement instanceof ClassStatement) { 229 | blockName = compositeStatement.getBlockName() + "#" + blockName; 230 | } 231 | FunctionStatement functionStatement = new FunctionStatement(rowToken.getRowNumber(), blockName); 232 | DefinitionScope functionScope = DefinitionContext.newScope(); 233 | FunctionDetails functionDetails = new FunctionDetails(name.getValue(), arguments); 234 | FunctionDefinition functionDefinition = new FunctionDefinition(functionDetails, functionStatement, functionScope); 235 | DefinitionContext.getScope().addFunction(functionDefinition); 236 | 237 | //parse function statements 238 | StatementParser.parse(this, functionStatement, functionScope); 239 | tokens.next(TokenType.Keyword, "end"); 240 | } 241 | 242 | private void parseReturnStatement(Token rowToken) { 243 | Expression expression = ExpressionReader.readExpression(tokens); 244 | ReturnStatement statement = new ReturnStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), expression); 245 | compositeStatement.addStatement(statement); 246 | } 247 | 248 | private void parseLoopStatement(Token rowToken) { 249 | Expression loopExpression = ExpressionReader.readExpression(tokens); 250 | if (loopExpression instanceof OperatorExpression || loopExpression instanceof VariableExpression) { 251 | AbstractLoopStatement loopStatement; 252 | 253 | if (loopExpression instanceof VariableExpression && tokens.peek(TokenType.Keyword, "in")) { 254 | // loop in 255 | VariableExpression variable = (VariableExpression) loopExpression; 256 | tokens.next(TokenType.Keyword, "in"); 257 | Expression bounds = ExpressionReader.readExpression(tokens); 258 | 259 | if (tokens.peek(TokenType.GroupDivider, "..")) { 260 | // loop in .. 261 | tokens.next(TokenType.GroupDivider, ".."); 262 | Expression upperBound = ExpressionReader.readExpression(tokens); 263 | 264 | if (tokens.peek(TokenType.Keyword, "by")) { 265 | // loop in .. by 266 | tokens.next(TokenType.Keyword, "by"); 267 | Expression step = ExpressionReader.readExpression(tokens); 268 | loopStatement = new ForLoopStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), variable, bounds, upperBound, step); 269 | } else { 270 | // use default step 271 | // loop in .. 272 | loopStatement = new ForLoopStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), variable, bounds, upperBound); 273 | } 274 | 275 | } else { 276 | // loop in 277 | loopStatement = new IterableLoopStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), variable, bounds); 278 | } 279 | 280 | } else { 281 | // loop 282 | loopStatement = new WhileLoopStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), loopExpression); 283 | } 284 | 285 | DefinitionScope loopScope = DefinitionContext.newScope(); 286 | StatementParser.parse(this, loopStatement, loopScope); 287 | tokens.next(TokenType.Keyword, "end"); 288 | 289 | compositeStatement.addStatement(loopStatement); 290 | } 291 | 292 | } 293 | 294 | private void parseBreakStatement(Token rowToken) { 295 | BreakStatement statement = new BreakStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 296 | compositeStatement.addStatement(statement); 297 | } 298 | 299 | private void parseNextStatement(Token rowToken) { 300 | NextStatement statement = new NextStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 301 | compositeStatement.addStatement(statement); 302 | } 303 | 304 | private void parseAssertStatement(Token rowToken) { 305 | Expression expression = ExpressionReader.readExpression(tokens); 306 | AssertStatement statement = new AssertStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), expression); 307 | compositeStatement.addStatement(statement); 308 | } 309 | 310 | private void parseRaiseExceptionStatement(Token rowToken) { 311 | Expression expression = ExpressionReader.readExpression(tokens); 312 | RaiseExceptionStatement statement = new RaiseExceptionStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), expression); 313 | compositeStatement.addStatement(statement); 314 | } 315 | 316 | private void parseHandleExceptionStatement(Token rowToken) { 317 | // read begin block 318 | CompositeStatement beginStatement = new CompositeStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 319 | DefinitionScope beginScope = DefinitionContext.newScope(); 320 | StatementParser.parse(this, beginStatement, beginScope); 321 | 322 | // read rescue block 323 | CompositeStatement rescueStatement = null; 324 | String errorVariable = null; 325 | if (tokens.peek(TokenType.Keyword, "rescue")) { 326 | tokens.next(); 327 | 328 | if (tokens.peekSameLine(TokenType.Variable)) { 329 | errorVariable = tokens.next().getValue(); 330 | } 331 | 332 | rescueStatement = new CompositeStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 333 | DefinitionScope rescueScope = DefinitionContext.newScope(); 334 | StatementParser.parse(this, rescueStatement, rescueScope); 335 | } 336 | 337 | // read ensure block 338 | CompositeStatement ensureStatement = null; 339 | if (tokens.peek(TokenType.Keyword, "ensure")) { 340 | tokens.next(); 341 | 342 | ensureStatement = new CompositeStatement(rowToken.getRowNumber(), compositeStatement.getBlockName()); 343 | DefinitionScope ensureScope = DefinitionContext.newScope(); 344 | StatementParser.parse(this, ensureStatement, ensureScope); 345 | } 346 | 347 | // skip end keyword 348 | tokens.next(TokenType.Keyword, "end"); 349 | 350 | // construct a statement 351 | HandleExceptionStatement statement = new HandleExceptionStatement(rowToken.getRowNumber(), compositeStatement.getBlockName(), 352 | beginStatement, rescueStatement, ensureStatement, errorVariable); 353 | compositeStatement.addStatement(statement); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/ToyLanguage.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import lombok.SneakyThrows; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.context.MemoryContext; 6 | import org.example.toylanguage.context.definition.DefinitionContext; 7 | import org.example.toylanguage.statement.CompositeStatement; 8 | import org.example.toylanguage.token.Token; 9 | 10 | import java.nio.file.Files; 11 | import java.nio.file.Path; 12 | import java.util.List; 13 | 14 | public class ToyLanguage { 15 | 16 | @SneakyThrows 17 | public void execute(Path path) { 18 | String sourceCode = Files.readString(path); 19 | List tokens = LexicalParser.parse(sourceCode); 20 | 21 | DefinitionContext.pushScope(DefinitionContext.newScope()); 22 | MemoryContext.pushScope(MemoryContext.newScope()); 23 | try { 24 | CompositeStatement statement = new CompositeStatement(null, path.getFileName().toString()); 25 | StatementParser.parse(tokens, statement); 26 | statement.execute(); 27 | } finally { 28 | DefinitionContext.endScope(); 29 | MemoryContext.endScope(); 30 | 31 | if (ExceptionContext.isRaised()) { 32 | ExceptionContext.printStackTrace(); 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/BreakContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import org.example.toylanguage.statement.loop.AbstractLoopStatement; 4 | import org.example.toylanguage.statement.loop.BreakStatement; 5 | 6 | /** 7 | * Associates a given {@link BreakScope} with a loop block 8 | *

9 | * 10 | * @see AbstractLoopStatement 11 | * @see BreakStatement 12 | */ 13 | public class BreakContext { 14 | private static BreakScope scope = new BreakScope(); 15 | 16 | /** 17 | * Get current {@link BreakScope} 18 | */ 19 | public static BreakScope getScope() { 20 | return scope; 21 | } 22 | 23 | /** 24 | * Reset state of the {@link BreakContext} on loop exit 25 | */ 26 | public static void reset() { 27 | BreakContext.scope = new BreakScope(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/BreakScope.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.statement.loop.BreakStatement; 5 | 6 | /** 7 | * Scope for the loop block defining if the break statement invoked 8 | *

9 | * 10 | * @see BreakContext 11 | * @see BreakStatement 12 | */ 13 | @Getter 14 | public class BreakScope { 15 | private boolean invoked; 16 | 17 | /** 18 | * Notify the loop block about invoking the break statement 19 | */ 20 | public void invoke() { 21 | this.invoked = true; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/ClassInstanceContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import org.example.toylanguage.expression.ExpressionReader; 4 | import org.example.toylanguage.expression.FunctionExpression; 5 | import org.example.toylanguage.expression.value.ClassValue; 6 | import org.example.toylanguage.expression.value.ThisValue; 7 | 8 | import java.util.Stack; 9 | 10 | /** 11 | * Associates a given {@link ClassValue} with this reference for the current block of code 12 | *

13 | * 14 | * @see ThisValue#getValue() 15 | * @see ExpressionReader 16 | * @see FunctionExpression 17 | */ 18 | public class ClassInstanceContext { 19 | private static final Stack values = new Stack<>(); 20 | 21 | /** 22 | * Get current this reference 23 | */ 24 | public static ClassValue getValue() { 25 | return values.peek(); 26 | } 27 | 28 | /** 29 | * Push new this reference when entering a class's constructor or invoking a class's function 30 | */ 31 | public static void pushValue(ClassValue instance) { 32 | values.push(instance); 33 | } 34 | 35 | /** 36 | * Pop this reference on block exit 37 | */ 38 | public static void popValue() { 39 | values.pop(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/ExceptionContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.example.toylanguage.expression.value.TextValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | import org.example.toylanguage.statement.HandleExceptionStatement; 8 | import org.example.toylanguage.statement.RaiseExceptionStatement; 9 | import org.example.toylanguage.statement.Statement; 10 | 11 | import java.util.List; 12 | import java.util.Stack; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * Associates thrown {@link Exception} with the current execution statement 17 | *

18 | * 19 | * @see RaiseExceptionStatement 20 | * @see HandleExceptionStatement 21 | */ 22 | public class ExceptionContext { 23 | /** 24 | * Raised exception 25 | */ 26 | @Getter 27 | private static Exception exception; 28 | /** 29 | * State of the exception 30 | */ 31 | private static State state = State.NONE; 32 | 33 | /** 34 | * Raise an exception 35 | *

36 | * 37 | * @param value raised value 38 | * @return null 39 | */ 40 | public static Value raiseException(Value value) { 41 | exception = new Exception(value, new Stack<>()); 42 | state = State.RAISED; 43 | return null; 44 | } 45 | 46 | /** 47 | * Raise an exception 48 | *

49 | * 50 | * @param textValue raised text value 51 | * @return null 52 | */ 53 | public static Value raiseException(String textValue) { 54 | return raiseException(new TextValue(textValue)); 55 | } 56 | 57 | /** 58 | * Rescue the exception if it's been handled 59 | */ 60 | public static void rescueException() { 61 | exception = null; 62 | state = State.NONE; 63 | } 64 | 65 | /** 66 | * Disable collecting of the stack trace records before executing ensure block 67 | */ 68 | public static void disable() { 69 | state = State.DISABLED; 70 | } 71 | 72 | /** 73 | * Enable collecting the stack trace after quiting the ensure block 74 | */ 75 | public static void enable() { 76 | state = State.RAISED; 77 | } 78 | 79 | /** 80 | * If an exception's been raised 81 | */ 82 | public static boolean isRaised() { 83 | return state == State.RAISED; 84 | } 85 | 86 | /** 87 | * Add record of the application's movement as a statement that initiated the exception 88 | */ 89 | public static void addTracedStatement(Statement statement) { 90 | if (isRaised()) { 91 | exception.stackTrace.add(statement); 92 | } 93 | } 94 | 95 | /** 96 | * Print an exception 97 | */ 98 | public static void printStackTrace() { 99 | System.err.println(exception); 100 | rescueException(); 101 | } 102 | 103 | /** 104 | * Exception details 105 | */ 106 | @RequiredArgsConstructor 107 | @Getter 108 | public static class Exception { 109 | /** 110 | * Raised error 111 | */ 112 | private final Value value; 113 | /** 114 | * Statements containing records of the application's movement leading to the statement that initiated the exception 115 | */ 116 | private final List stackTrace; 117 | 118 | @Override 119 | public String toString() { 120 | return String.format("%s%n%s", 121 | value, 122 | stackTrace 123 | .stream() 124 | .map(st -> String.format("%4sat %s:%d", "", st.getBlockName(), st.getRowNumber())) 125 | .collect(Collectors.joining("\n")) 126 | ); 127 | } 128 | } 129 | 130 | /** 131 | * States of the ExceptionContext 132 | */ 133 | private enum State { 134 | /** 135 | * No exception raised or the exception is rescued 136 | */ 137 | NONE, 138 | /** 139 | * The exception is raised 140 | */ 141 | RAISED, 142 | /** 143 | * The exception disabled to execute ensure block 144 | */ 145 | DISABLED 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/MemoryContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Associates a given {@link MemoryScope} with isolated block of code 7 | */ 8 | public class MemoryContext { 9 | private static final Stack scopes = new Stack<>(); 10 | 11 | /** 12 | * Get scope of the current block 13 | */ 14 | public static MemoryScope getScope() { 15 | return scopes.peek(); 16 | } 17 | 18 | /** 19 | * Create and set a new MemoryScope to enter a nested block 20 | */ 21 | public static MemoryScope newScope() { 22 | return new MemoryScope(scopes.isEmpty() ? null : scopes.peek()); 23 | } 24 | 25 | /** 26 | * Set an existing scope to enter any block 27 | */ 28 | public static void pushScope(MemoryScope scope) { 29 | scopes.push(scope); 30 | } 31 | 32 | /** 33 | * Terminate the current scope to exit block 34 | */ 35 | public static void endScope() { 36 | scopes.pop(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/MemoryScope.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import org.example.toylanguage.expression.value.NullValue; 4 | import org.example.toylanguage.expression.value.Value; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * Contains variables defined in a block of code 11 | *

12 | * 13 | * @see ValueReference 14 | * @see MemoryContext 15 | * @see Value 16 | */ 17 | public class MemoryScope { 18 | /** 19 | * Variables defined in this block 20 | */ 21 | private final Map variables; 22 | /** 23 | * Parent MemoryScope to access the variables defined in outer scopes 24 | */ 25 | private final MemoryScope parent; 26 | 27 | public MemoryScope(MemoryScope parent) { 28 | this.variables = new HashMap<>(); 29 | this.parent = parent; 30 | } 31 | 32 | /** 33 | * Get variable value from the current scope or in the outer scopes 34 | *

35 | * 36 | * @return {@link NullValue} if there is no variable defined 37 | */ 38 | public Value get(String name) { 39 | ValueReference variable = variables.get(name); 40 | if (variable != null) 41 | return variable.getValue(); 42 | else if (parent != null) 43 | return parent.get(name); 44 | else 45 | return NullValue.NULL_INSTANCE; 46 | } 47 | 48 | /** 49 | * Get variable from the current scope 50 | */ 51 | public Value getLocal(String name) { 52 | ValueReference variable = variables.get(name); 53 | return variable != null ? variable.getValue() : null; 54 | } 55 | 56 | /** 57 | * Set variable's value to the current scope 58 | */ 59 | public void set(String name, Value value) { 60 | MemoryScope variableScope = findScope(name); 61 | if (variableScope == null) { 62 | setLocal(name, value); 63 | } else { 64 | variableScope.setLocal(name, value); 65 | } 66 | } 67 | 68 | /** 69 | * Set variable's value directly using {@link ValueReference} in the current scope 70 | */ 71 | public void setLocal(String name, ValueReference variable) { 72 | variables.put(name, variable); 73 | } 74 | 75 | /** 76 | * Set variable's value in the current scope 77 | */ 78 | public void setLocal(String name, Value value) { 79 | if (variables.containsKey(name)) { 80 | variables.get(name).setValue(value); 81 | } else { 82 | variables.put(name, ValueReference.instanceOf(value)); 83 | } 84 | } 85 | 86 | private MemoryScope findScope(String name) { 87 | if (variables.containsKey(name)) 88 | return this; 89 | return parent == null ? null : parent.findScope(name); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/NextContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import org.example.toylanguage.statement.loop.AbstractLoopStatement; 4 | import org.example.toylanguage.statement.loop.NextStatement; 5 | 6 | /** 7 | * Associates a given {@link NextScope} with a loop block 8 | *

9 | * 10 | * @see AbstractLoopStatement 11 | * @see NextStatement 12 | */ 13 | public class NextContext { 14 | private static NextScope scope = new NextScope(); 15 | 16 | /** 17 | * Get current {@link NextScope} 18 | */ 19 | public static NextScope getScope() { 20 | return scope; 21 | } 22 | 23 | /** 24 | * Reset state of the {@link NextContext} on loop exit 25 | */ 26 | public static void reset() { 27 | NextContext.scope = new NextScope(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/NextScope.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * Scope for the loop block defining if the next statement invoked 7 | *

8 | * 9 | * @see NextContext 10 | */ 11 | @Getter 12 | public class NextScope { 13 | private boolean invoked; 14 | 15 | /** 16 | * Notify the loop block about invoking the next statement 17 | */ 18 | public void invoke() { 19 | this.invoked = true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/ReturnContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import org.example.toylanguage.expression.FunctionExpression; 4 | import org.example.toylanguage.statement.ReturnStatement; 5 | import org.example.toylanguage.statement.loop.AbstractLoopStatement; 6 | 7 | /** 8 | * Associates a given {@link ReturnScope} with {@link org.example.toylanguage.statement.CompositeStatement} 9 | *

10 | * 11 | * @see AbstractLoopStatement 12 | * @see ReturnStatement 13 | * @see FunctionExpression 14 | */ 15 | public class ReturnContext { 16 | private static ReturnScope scope = new ReturnScope(); 17 | 18 | /** 19 | * Get current {@link ReturnScope} 20 | */ 21 | public static ReturnScope getScope() { 22 | return scope; 23 | } 24 | 25 | /** 26 | * Reset state of the {@link ReturnContext} on block exit 27 | */ 28 | public static void reset() { 29 | ReturnContext.scope = new ReturnScope(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/ReturnScope.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.expression.value.Value; 5 | 6 | /** 7 | * Scope for the {@link org.example.toylanguage.statement.CompositeStatement} defining if the return statement invoked 8 | *

9 | * 10 | * @see BreakContext 11 | */ 12 | @Getter 13 | public class ReturnScope { 14 | private boolean invoked; 15 | private Value result; 16 | 17 | /** 18 | * Notify current scope that return statement invoked 19 | */ 20 | public void invoke(Value result) { 21 | this.invoked = true; 22 | this.result = result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/ValueReference.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import lombok.ToString; 6 | import org.example.toylanguage.expression.Expression; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | /** 10 | * Wrapper for the Value to keep the properties' relations between Base and Derived classes 11 | *

12 | *

{@code
13 |  * # Declare the Base class A
14 |  * class A [a_value]
15 |  * end
16 |  *
17 |  * # Declare the Derived class B that inherits class A and initializes its `a_value` property with the `b_value` parameter
18 |  * class B [b_value]: A [b_value]
19 |  * end
20 |  *
21 |  * # Create an instance of class B
22 |  * b = new B [ b_value ]
23 |  *
24 |  * # If we change the `b_value` property, the A class's property `a_value` should be updated as well
25 |  * b :: b_value = new_value
26 |  *
27 |  * # a_new_value should contain `new_value`
28 |  * a_new_value = b as A :: a_value
29 |  * }
30 | */ 31 | @Getter 32 | @Setter 33 | @ToString 34 | public class ValueReference implements Expression { 35 | private Value value; 36 | 37 | private ValueReference(Value value) { 38 | this.value = value; 39 | } 40 | 41 | /** 42 | * Evaluates Expression and creates ValueReference for it 43 | */ 44 | public static ValueReference instanceOf(Expression expression) { 45 | if (expression instanceof ValueReference) { 46 | // reuse variable 47 | return (ValueReference) expression; 48 | } else { 49 | Value value = expression.evaluate(); 50 | if (value == null) return null; 51 | return new ValueReference(value); 52 | } 53 | } 54 | 55 | @Override 56 | public Value evaluate() { 57 | return value; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/ClassDefinition.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import org.example.toylanguage.StatementParser; 7 | import org.example.toylanguage.statement.ClassStatement; 8 | import org.example.toylanguage.token.Token; 9 | 10 | import java.util.Set; 11 | 12 | /** 13 | * Definition for a class 14 | *

15 | * 16 | * @see StatementParser#parseClassDefinition(Token) 17 | */ 18 | @RequiredArgsConstructor 19 | @Getter 20 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 21 | public class ClassDefinition implements Definition { 22 | /** 23 | * Details for a class 24 | */ 25 | @EqualsAndHashCode.Include 26 | private final ClassDetails classDetails; 27 | /** 28 | * Details of the inherited (super) classes 29 | */ 30 | private final Set baseTypes; 31 | /** 32 | * Constructor statement 33 | */ 34 | private final ClassStatement statement; 35 | /** 36 | * Contains nested classes and functions defined in this class 37 | */ 38 | private final DefinitionScope definitionScope; 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/ClassDetails.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import org.example.toylanguage.StatementParser; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Details of a class 12 | *

13 | * 14 | * @see ClassDefinition 15 | * @see StatementParser#readClassDetails() 16 | */ 17 | @RequiredArgsConstructor 18 | @Getter 19 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 20 | public class ClassDetails { 21 | /** 22 | * Class's name 23 | */ 24 | @EqualsAndHashCode.Include 25 | private final String name; 26 | /** 27 | * Names of the constructor properties 28 | */ 29 | private final List properties; 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/Definition.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | /** 4 | * Interface to specify structures supported by toy-language 5 | *

6 | * 7 | * @see ClassDefinition 8 | * @see FunctionDefinition 9 | */ 10 | public interface Definition { 11 | /** 12 | * Contains nested structures declared in this definition 13 | */ 14 | DefinitionScope getDefinitionScope(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/DefinitionContext.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | import java.util.Stack; 4 | 5 | /** 6 | * Associates a given {@link DefinitionScope} with isolated block of code 7 | *

8 | * 9 | * @see DefinitionScope 10 | * @see ClassDefinition 11 | * @see FunctionDefinition 12 | */ 13 | public class DefinitionContext { 14 | private final static Stack scopes = new Stack<>(); 15 | 16 | /** 17 | * Get scope of the current block 18 | */ 19 | public static DefinitionScope getScope() { 20 | return scopes.peek(); 21 | } 22 | 23 | /** 24 | * Create and set a new DefinitionScope to enter a nested block 25 | */ 26 | public static DefinitionScope newScope() { 27 | return new DefinitionScope(scopes.isEmpty() ? null : scopes.peek()); 28 | } 29 | 30 | /** 31 | * Set an existing scope to enter any block 32 | */ 33 | public static void pushScope(DefinitionScope scope) { 34 | scopes.push(scope); 35 | } 36 | 37 | /** 38 | * Terminate the current scope to exit block 39 | */ 40 | public static void endScope() { 41 | scopes.pop(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/DefinitionScope.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.HashSet; 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | /** 11 | * Contains structures (classes, functions) defined in a block of code 12 | *

13 | * 14 | * @see ClassDefinition 15 | * @see FunctionDefinition 16 | * @see DefinitionContext 17 | */ 18 | public class DefinitionScope { 19 | /** 20 | * Classes defined in the block 21 | */ 22 | private final Set classes; 23 | /** 24 | * Functions declared in the block 25 | */ 26 | private final Set functions; 27 | /** 28 | * Parent DefinitionScope to access the structures defined in outer blocks of code 29 | */ 30 | @Getter 31 | private final DefinitionScope parent; 32 | 33 | public DefinitionScope(DefinitionScope parent) { 34 | this.classes = new HashSet<>(); 35 | this.functions = new HashSet<>(); 36 | this.parent = parent; 37 | } 38 | 39 | /** 40 | * Get ClassDefinition from the current block and from outer blocks of code 41 | * 42 | * @param name name of the class 43 | */ 44 | public ClassDefinition getClass(String name) { 45 | Optional classDefinition = classes.stream() 46 | .filter(t -> t.getClassDetails().getName().equals(name)) 47 | .findAny(); 48 | if (classDefinition.isPresent()) 49 | return classDefinition.get(); 50 | else if (parent != null) 51 | return parent.getClass(name); 52 | else 53 | return null; 54 | } 55 | 56 | /** 57 | * Add ClassDefinition to the current block 58 | */ 59 | public void addClass(ClassDefinition classDefinition) { 60 | classes.add(classDefinition); 61 | } 62 | 63 | /** 64 | * Get FunctionDefinition from the current block and from outer blocks of code 65 | * 66 | * @param name name of the function 67 | * @param argumentsSize count of function arguments, useful in case there are multiple functions with the same name but with different length of arguments declared 68 | */ 69 | public FunctionDefinition getFunction(String name, int argumentsSize) { 70 | Optional functionDefinition = functions.stream() 71 | .filter(t -> Objects.equals(t.getDetails().getName(), name) 72 | && Objects.equals(t.getDetails().getArguments().size(), argumentsSize)) 73 | .findAny(); 74 | if (functionDefinition.isPresent()) 75 | return functionDefinition.get(); 76 | else if (parent != null) 77 | return parent.getFunction(name, argumentsSize); 78 | else 79 | return null; 80 | } 81 | 82 | /** 83 | * Check that DefinitionScope contains the function 84 | * 85 | * @param name name of the function 86 | * @param argumentsSize amount of function arguments in case there are multiple functions with the same name declared 87 | */ 88 | public boolean containsFunction(String name, int argumentsSize) { 89 | return getFunction(name, argumentsSize) != null; 90 | } 91 | 92 | /** 93 | * Add FunctionDefinition to the current block 94 | */ 95 | public void addFunction(FunctionDefinition functionDefinition) { 96 | functions.add(functionDefinition); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/FunctionDefinition.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | import org.example.toylanguage.StatementParser; 7 | import org.example.toylanguage.statement.FunctionStatement; 8 | import org.example.toylanguage.token.Token; 9 | 10 | /** 11 | * Definition for a function 12 | *

13 | * 14 | * @see StatementParser#parseFunctionDefinition(Token) 15 | */ 16 | @RequiredArgsConstructor 17 | @Getter 18 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 19 | public class FunctionDefinition implements Definition { 20 | /** 21 | * Details for a function 22 | */ 23 | @EqualsAndHashCode.Include 24 | private final FunctionDetails details; 25 | /** 26 | * Statement(s) defined in the function body 27 | */ 28 | private final FunctionStatement statement; 29 | /** 30 | * Contains nested classes and functions defined in this function 31 | */ 32 | private final DefinitionScope definitionScope; 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/FunctionDetails.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | 9 | /** 10 | * Details for a function 11 | *

12 | * 13 | * @see FunctionDefinition 14 | */ 15 | @RequiredArgsConstructor 16 | @Getter 17 | public class FunctionDetails { 18 | /** 19 | * Function's name 20 | */ 21 | private final String name; 22 | /** 23 | * Names of the function arguments 24 | */ 25 | private final List arguments; 26 | 27 | /** 28 | * Compare function by its name and number of arguments 29 | */ 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | FunctionDetails that = (FunctionDetails) o; 35 | return Objects.equals(name, that.name) && arguments.size() == that.arguments.size(); 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(name, arguments.size()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/definition/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context.definition; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/context/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.context; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/exception/SyntaxException.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.exception; 2 | 3 | public class SyntaxException extends ToyLanguageException { 4 | public SyntaxException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/exception/ToyLanguageException.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.exception; 2 | 3 | public class ToyLanguageException extends RuntimeException { 4 | public ToyLanguageException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/exception/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.exception; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/ArrayExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | import org.example.toylanguage.expression.value.ArrayValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.List; 10 | 11 | @RequiredArgsConstructor 12 | @Getter 13 | @ToString 14 | public class ArrayExpression implements Expression { 15 | private final List values; 16 | 17 | @Override 18 | public Value evaluate() { 19 | return new ArrayValue(this); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/AssignExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import org.example.toylanguage.expression.value.Value; 4 | 5 | public interface AssignExpression { 6 | Value assign(Value value); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/ClassExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | import org.example.toylanguage.context.*; 7 | import org.example.toylanguage.context.definition.ClassDefinition; 8 | import org.example.toylanguage.context.definition.DefinitionContext; 9 | import org.example.toylanguage.expression.value.ClassValue; 10 | import org.example.toylanguage.expression.value.NullValue; 11 | import org.example.toylanguage.expression.value.Value; 12 | import org.example.toylanguage.statement.ClassStatement; 13 | 14 | import java.util.ArrayList; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.stream.Collectors; 19 | import java.util.stream.IntStream; 20 | 21 | @RequiredArgsConstructor 22 | @Getter 23 | @ToString(onlyExplicitlyIncluded = true) 24 | public class ClassExpression implements Expression { 25 | @ToString.Include 26 | private final String name; 27 | private final List propertiesExpressions; 28 | // contains Derived class and all the Base classes chain that Derived class inherits 29 | private final Map relations; 30 | 31 | public ClassExpression(String name, List propertiesExpressions) { 32 | this(name, propertiesExpressions, new HashMap<>()); 33 | } 34 | 35 | @Override 36 | public Value evaluate() { 37 | //initialize class's properties 38 | List values = new ArrayList<>(propertiesExpressions.size()); 39 | for (Expression expression : propertiesExpressions) { 40 | ValueReference value = ValueReference.instanceOf(expression); 41 | if (value == null) return null; 42 | values.add(value); 43 | } 44 | return evaluate(values); 45 | } 46 | 47 | /** 48 | * Evaluate nested class 49 | * 50 | * @param classValue instance of the parent class 51 | */ 52 | public Value evaluate(ClassValue classValue) { 53 | //initialize class's properties 54 | List values = new ArrayList<>(propertiesExpressions.size()); 55 | for (Expression expression : propertiesExpressions) { 56 | ValueReference value = ValueReference.instanceOf(expression); 57 | if (value == null) return null; 58 | values.add(value); 59 | } 60 | 61 | //set parent class's definition 62 | ClassDefinition classDefinition = classValue.getValue(); 63 | DefinitionContext.pushScope(classDefinition.getDefinitionScope()); 64 | 65 | try { 66 | return evaluate(values); 67 | } finally { 68 | DefinitionContext.endScope(); 69 | } 70 | } 71 | 72 | private Value evaluate(List values) { 73 | //get class's definition and statement 74 | ClassDefinition definition = DefinitionContext.getScope().getClass(name); 75 | if (definition == null) { 76 | return ExceptionContext.raiseException(String.format("Class `%s` is not defined", name)); 77 | } 78 | ClassStatement classStatement = definition.getStatement(); 79 | 80 | //set separate scope 81 | MemoryScope classScope = new MemoryScope(null); 82 | MemoryContext.pushScope(classScope); 83 | 84 | //initialize constructor arguments 85 | ClassValue classValue = new ClassValue(definition, classScope, relations); 86 | relations.put(name, classValue); 87 | 88 | // fill the missing properties with NullValue.NULL_INSTANCE 89 | // class A [arg1, arg2] 90 | // new A [arg1] -> new A [arg1, null] 91 | // new A [arg1, arg2, arg3] -> new A [arg1, arg2] 92 | List valuesToSet = IntStream.range(0, definition.getClassDetails().getProperties().size()) 93 | .boxed() 94 | .map(i -> values.size() > i ? values.get(i) : ValueReference.instanceOf(NullValue.NULL_INSTANCE)) 95 | .collect(Collectors.toList()); 96 | 97 | //invoke constructors of the base classes and set a ClassValue relation 98 | definition.getBaseTypes() 99 | .stream() 100 | .map(baseType -> { 101 | // initialize base class's properties 102 | // class A [a_arg] 103 | // class B [b_arg1, b_arg2]: A [b_arg1] 104 | List baseClassProperties = baseType.getProperties().stream() 105 | .map(t -> definition.getClassDetails().getProperties().indexOf(t)) 106 | .map(valuesToSet::get) 107 | .collect(Collectors.toList()); 108 | return new ClassExpression(baseType.getName(), baseClassProperties, relations); 109 | }) 110 | .forEach(ClassExpression::evaluate); 111 | 112 | try { 113 | ClassInstanceContext.pushValue(classValue); 114 | IntStream.range(0, definition.getClassDetails().getProperties().size()).boxed() 115 | .forEach(i -> MemoryContext.getScope() 116 | .setLocal(definition.getClassDetails().getProperties().get(i), valuesToSet.get(i))); 117 | 118 | //execute function body 119 | DefinitionContext.pushScope(definition.getDefinitionScope()); 120 | try { 121 | classStatement.execute(); 122 | } finally { 123 | DefinitionContext.endScope(); 124 | } 125 | 126 | // if exception have been thrown in the constructor 127 | if (ExceptionContext.isRaised()) 128 | return null; 129 | 130 | return classValue; 131 | } finally { 132 | MemoryContext.endScope(); 133 | ClassInstanceContext.popValue(); 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/Expression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import org.example.toylanguage.expression.value.Value; 4 | 5 | public interface Expression { 6 | Value evaluate(); 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/ExpressionReader.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import lombok.Getter; 4 | import lombok.SneakyThrows; 5 | import org.example.toylanguage.exception.SyntaxException; 6 | import org.example.toylanguage.expression.operator.*; 7 | import org.example.toylanguage.expression.value.LogicalValue; 8 | import org.example.toylanguage.expression.value.NumericValue; 9 | import org.example.toylanguage.expression.value.TextValue; 10 | import org.example.toylanguage.token.Token; 11 | import org.example.toylanguage.token.TokenType; 12 | import org.example.toylanguage.token.TokensStack; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Objects; 17 | import java.util.Stack; 18 | 19 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 20 | import static org.example.toylanguage.expression.value.ThisValue.THIS_INSTANCE; 21 | 22 | public class ExpressionReader { 23 | private final Stack operands; 24 | private final Stack operators; 25 | @Getter 26 | private final TokensStack tokens; 27 | 28 | private ExpressionReader(TokensStack tokens) { 29 | this.operands = new Stack<>(); 30 | this.operators = new Stack<>(); 31 | this.tokens = tokens; 32 | } 33 | 34 | public static Expression readExpression(TokensStack tokens) { 35 | ExpressionReader expressionReader = new ExpressionReader(tokens); 36 | return expressionReader.readExpression(); 37 | } 38 | 39 | public static Expression readExpression(ExpressionReader expressionReader) { 40 | return readExpression(expressionReader.getTokens()); 41 | } 42 | 43 | private boolean hasNextToken() { 44 | if (tokens.peekSameLine(TokenType.Operator, TokenType.Variable, TokenType.Numeric, TokenType.Logical, 45 | TokenType.Null, TokenType.This, TokenType.Text)) 46 | return true; 47 | //beginning of an array 48 | if (tokens.peekSameLine(TokenType.GroupDivider, "{")) 49 | return true; 50 | return false; 51 | } 52 | 53 | private Expression readExpression() { 54 | while (hasNextToken()) { 55 | Token token = tokens.next(); 56 | switch (token.getType()) { 57 | case Operator: 58 | Operator operator = Operator.getType(token.getValue()); 59 | switch (operator) { 60 | case LeftParen: 61 | operators.push(operator); 62 | break; 63 | case RightParen: 64 | //until left bracket is not reached 65 | while (!operators.empty() && operators.peek() != Operator.LeftParen) 66 | applyTopOperator(); 67 | operators.pop(); //pop left bracket 68 | break; 69 | default: 70 | //until top operator has greater precedence 71 | while (!operators.isEmpty() && operators.peek().greaterThan(operator)) 72 | applyTopOperator(); 73 | operators.push(operator); // finally, add less prioritized operator 74 | } 75 | break; 76 | default: 77 | String value = token.getValue(); 78 | Expression operand; 79 | switch (token.getType()) { 80 | case Numeric: 81 | operand = new NumericValue(Double.parseDouble(value)); 82 | break; 83 | case Logical: 84 | operand = new LogicalValue(Boolean.valueOf(value)); 85 | break; 86 | case Text: 87 | operand = new TextValue(value); 88 | break; 89 | case GroupDivider: 90 | if (Objects.equals(token.getValue(), "{")) { 91 | operand = readArrayInstance(); 92 | break; 93 | } 94 | case Null: 95 | operand = NULL_INSTANCE; 96 | break; 97 | case This: 98 | operand = THIS_INSTANCE; 99 | break; 100 | case Variable: 101 | default: 102 | if (!operators.isEmpty() && List.of(Operator.ClassInstance, Operator.NestedClassInstance).contains(operators.peek())) { 103 | operand = readClassInstance(token); 104 | } else if (tokens.peekSameLine(TokenType.GroupDivider, "[")) { 105 | operand = readFunctionInvocation(token); 106 | } else if (tokens.peekSameLine(TokenType.GroupDivider, "{")) { 107 | operand = readArrayValue(token); 108 | } else { 109 | operand = new VariableExpression(value); 110 | } 111 | } 112 | operands.push(operand); 113 | } 114 | } 115 | 116 | while (!operators.isEmpty()) { 117 | applyTopOperator(); 118 | } 119 | 120 | if (operands.isEmpty()) { 121 | return NULL_INSTANCE; 122 | } else { 123 | return operands.pop(); 124 | } 125 | } 126 | 127 | @SneakyThrows 128 | private void applyTopOperator() { 129 | // e.g. a + b 130 | Operator operator = operators.pop(); 131 | Class operatorType = operator.getType(); 132 | Expression left = operands.pop(); 133 | if (BinaryOperatorExpression.class.isAssignableFrom(operatorType)) { 134 | Expression right = operands.pop(); 135 | operands.push(operatorType 136 | .getConstructor(Expression.class, Expression.class) 137 | .newInstance(right, left)); 138 | } else if (UnaryOperatorExpression.class.isAssignableFrom(operatorType)) { 139 | // e.g. new Instance [] 140 | operands.push(operatorType 141 | .getConstructor(Expression.class) 142 | .newInstance(left)); 143 | } else { 144 | throw new SyntaxException(String.format("Operator `%s` is not supported", operatorType)); 145 | } 146 | } 147 | 148 | // read class instance: new Class [ property1, property2, ... ] 149 | private ClassExpression readClassInstance(Token token) { // token contains class name 150 | List properties = new ArrayList<>(); 151 | if (tokens.peekSameLine(TokenType.GroupDivider, "[")) { 152 | 153 | tokens.next(TokenType.GroupDivider, "["); //skip open square bracket 154 | 155 | while (!tokens.peekSameLine(TokenType.GroupDivider, "]")) { 156 | Expression value = ExpressionReader.readExpression(this); 157 | properties.add(value); 158 | 159 | if (tokens.peekSameLine(TokenType.GroupDivider, ",")) 160 | tokens.next(); 161 | } 162 | 163 | tokens.next(TokenType.GroupDivider, "]"); //skip close square bracket 164 | } 165 | return new ClassExpression(token.getValue(), properties); 166 | } 167 | 168 | // read function invocation: function_name [ argument1, argument2 ] 169 | private FunctionExpression readFunctionInvocation(Token token) { // token contains function name 170 | List arguments = new ArrayList<>(); 171 | if (tokens.peekSameLine(TokenType.GroupDivider, "[")) { 172 | 173 | tokens.next(TokenType.GroupDivider, "["); //skip open square bracket 174 | 175 | while (!tokens.peekSameLine(TokenType.GroupDivider, "]")) { 176 | Expression value = ExpressionReader.readExpression(this); 177 | arguments.add(value); 178 | 179 | if (tokens.peekSameLine(TokenType.GroupDivider, ",")) 180 | tokens.next(); 181 | } 182 | 183 | tokens.next(TokenType.GroupDivider, "]"); //skip close square bracket 184 | } 185 | 186 | return new FunctionExpression(token.getValue(), arguments); 187 | } 188 | 189 | // read array instantiation: array = {1,2,3} 190 | private ArrayExpression readArrayInstance() { 191 | List values = new ArrayList<>(); 192 | 193 | while (!tokens.peekSameLine(TokenType.GroupDivider, "}")) { 194 | Expression value = ExpressionReader.readExpression(this); 195 | values.add(value); 196 | 197 | if (tokens.peekSameLine(TokenType.GroupDivider, ",")) 198 | tokens.next(); 199 | } 200 | 201 | tokens.next(TokenType.GroupDivider, "}"); //skip close square bracket 202 | 203 | return new ArrayExpression(values); 204 | } 205 | 206 | // read array value: array{index} 207 | private ArrayValueOperator readArrayValue(Token token) { 208 | VariableExpression array = new VariableExpression(token.getValue()); 209 | tokens.next(TokenType.GroupDivider, "{"); 210 | Expression arrayIndex = ExpressionReader.readExpression(this); 211 | tokens.next(TokenType.GroupDivider, "}"); 212 | 213 | return new ArrayValueOperator(array, arrayIndex); 214 | } 215 | } 216 | 217 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/FunctionExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | import org.example.toylanguage.context.*; 7 | import org.example.toylanguage.context.definition.*; 8 | import org.example.toylanguage.expression.value.ClassValue; 9 | import org.example.toylanguage.expression.value.NullValue; 10 | import org.example.toylanguage.expression.value.Value; 11 | import org.example.toylanguage.statement.FunctionStatement; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | import java.util.stream.Collectors; 16 | import java.util.stream.IntStream; 17 | 18 | @RequiredArgsConstructor 19 | @Getter 20 | @ToString(onlyExplicitlyIncluded = true) 21 | public class FunctionExpression implements Expression { 22 | @ToString.Include 23 | private final String name; 24 | private final List argumentExpressions; 25 | 26 | @Override 27 | public Value evaluate() { 28 | //initialize function arguments 29 | List> values = new ArrayList<>(argumentExpressions.size()); 30 | for (Expression expression : argumentExpressions) { 31 | Value value = expression.evaluate(); 32 | if (value == null) return null; 33 | values.add(value); 34 | } 35 | return evaluate(values); 36 | } 37 | 38 | /** 39 | * Evaluate class's function 40 | * 41 | * @param classValue instance of class where the function is placed in 42 | */ 43 | public Value evaluate(ClassValue classValue) { 44 | //initialize function arguments 45 | List> values = new ArrayList<>(argumentExpressions.size()); 46 | for (Expression expression : argumentExpressions) { 47 | Value value = expression.evaluate(); 48 | if (value == null) return null; 49 | values.add(value); 50 | } 51 | 52 | // find a class containing the function 53 | ClassDefinition classDefinition = findClassDefinitionContainingFunction(classValue.getValue(), name, values.size()); 54 | if (classDefinition == null) { 55 | String args = IntStream.range(0, values.size()).mapToObj(t -> "arg" + (t + 1)).collect(Collectors.joining(", ")); 56 | return ExceptionContext.raiseException(String.format("Function `%s#%s [%s]` is not defined", 57 | classValue.getValue().getClassDetails().getName(), name, args)); 58 | } 59 | DefinitionScope classDefinitionScope = classDefinition.getDefinitionScope(); 60 | ClassValue functionClassValue = classValue.getRelation(classDefinition.getClassDetails().getName()); 61 | MemoryScope memoryScope = functionClassValue.getMemoryScope(); 62 | 63 | //set class's definition and memory scopes 64 | DefinitionContext.pushScope(classDefinitionScope); 65 | MemoryContext.pushScope(memoryScope); 66 | ClassInstanceContext.pushValue(functionClassValue); 67 | 68 | try { 69 | //proceed function 70 | return evaluate(values); 71 | } finally { 72 | DefinitionContext.endScope(); 73 | MemoryContext.endScope(); 74 | ClassInstanceContext.popValue(); 75 | } 76 | } 77 | 78 | private Value evaluate(List> values) { 79 | //get function's definition and statement 80 | FunctionDefinition definition = DefinitionContext.getScope().getFunction(name, values.size()); 81 | if (definition == null) { 82 | String args = IntStream.range(0, values.size()).mapToObj(t -> "arg" + (t + 1)).collect(Collectors.joining(", ")); 83 | return ExceptionContext.raiseException(String.format("Function `%s [%s]` is not defined", name, args)); 84 | } 85 | FunctionStatement statement = definition.getStatement(); 86 | FunctionDetails details = definition.getDetails(); 87 | 88 | //set new memory scope 89 | MemoryContext.pushScope(MemoryContext.newScope()); 90 | 91 | try { 92 | //initialize function arguments 93 | IntStream.range(0, details.getArguments().size()).boxed() 94 | .forEach(i -> MemoryContext.getScope() 95 | .setLocal(details.getArguments().get(i), values.size() > i ? values.get(i) : NullValue.NULL_INSTANCE)); 96 | 97 | //execute function body 98 | statement.execute(); 99 | 100 | //obtain function result 101 | return ReturnContext.getScope().getResult(); 102 | } finally { 103 | // release function memory and return context 104 | MemoryContext.endScope(); 105 | ReturnContext.reset(); 106 | } 107 | } 108 | 109 | /** 110 | * Find a Base class that contains the required function 111 | * 112 | *

{@code
113 |      * class A
114 |      *      fun action
115 |      *      end
116 |      * end
117 |      *
118 |      * class B
119 |      * end
120 |      *
121 |      * b = new B
122 |      * # Function `action` is not available from the DefinitionScope of class B as it's declared in the class A
123 |      * b :: action []
124 |      *
125 |      * }
126 | */ 127 | private ClassDefinition findClassDefinitionContainingFunction(ClassDefinition classDefinition, String functionName, int argumentsSize) { 128 | DefinitionScope definitionScope = classDefinition.getDefinitionScope(); 129 | if (definitionScope.containsFunction(functionName, argumentsSize)) { 130 | return classDefinition; 131 | } else { 132 | for (ClassDetails baseType : classDefinition.getBaseTypes()) { 133 | ClassDefinition baseTypeDefinition = definitionScope.getClass(baseType.getName()); 134 | ClassDefinition functionClassDefinition = findClassDefinitionContainingFunction(baseTypeDefinition, functionName, argumentsSize); 135 | if (functionClassDefinition != null) 136 | return functionClassDefinition; 137 | } 138 | return null; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/VariableExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | import org.example.toylanguage.context.MemoryContext; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | @RequiredArgsConstructor 10 | @Getter 11 | @ToString 12 | public class VariableExpression implements Expression, AssignExpression { 13 | private final String name; 14 | 15 | @Override 16 | public Value evaluate() { 17 | return MemoryContext.getScope().get(name); 18 | } 19 | 20 | @Override 21 | public Value assign(Value value) { 22 | if (value == null) return null; 23 | MemoryContext.getScope().set(name, value); 24 | return value; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/AdditionOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | import org.example.toylanguage.expression.value.ArrayValue; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.TextValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | import java.util.stream.Stream; 12 | 13 | public class AdditionOperator extends BinaryOperatorExpression { 14 | public AdditionOperator(Expression left, Expression right) { 15 | super(left, right); 16 | } 17 | 18 | @Override 19 | public Value evaluate() { 20 | Value left = getLeft().evaluate(); 21 | if (left == null) return null; 22 | Value right = getRight().evaluate(); 23 | if (right == null) return null; 24 | if (left instanceof NumericValue && right instanceof NumericValue) { 25 | return new NumericValue(((NumericValue) left).getValue() + ((NumericValue) right).getValue()); 26 | } else if (left instanceof ArrayValue || right instanceof ArrayValue) { 27 | List> newArray; 28 | if (left instanceof ArrayValue && right instanceof ArrayValue) { 29 | newArray = Stream.concat(((ArrayValue) left).getValue().stream(), ((ArrayValue) right).getValue().stream()) 30 | .collect(Collectors.toList()); 31 | } else if (left instanceof ArrayValue) { 32 | newArray = Stream.concat(((ArrayValue) left).getValue().stream(), Stream.of(right)) 33 | .collect(Collectors.toList()); 34 | } else { 35 | newArray = Stream.concat(((ArrayValue) right).getValue().stream(), Stream.of(left)) 36 | .collect(Collectors.toList()); 37 | } 38 | return new ArrayValue(newArray); 39 | } else { 40 | return new TextValue(left.toString() + right.toString()); 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ArrayAppendOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | import org.example.toylanguage.expression.value.ArrayValue; 5 | import org.example.toylanguage.expression.value.Value; 6 | 7 | public class ArrayAppendOperator extends BinaryOperatorExpression { 8 | public ArrayAppendOperator(Expression left, Expression right) { 9 | super(left, right); 10 | } 11 | 12 | @Override 13 | public Value evaluate() { 14 | Value left = getLeft().evaluate(); 15 | if (left == null) return null; 16 | Value right = getRight().evaluate(); 17 | if (right == null) return null; 18 | 19 | if (left instanceof ArrayValue) { 20 | ((ArrayValue) left).appendValue(right); 21 | } 22 | return left; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ArrayValueOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.AssignExpression; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.ArrayValue; 6 | import org.example.toylanguage.expression.value.TextValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | public class ArrayValueOperator extends BinaryOperatorExpression implements AssignExpression { 10 | public ArrayValueOperator(Expression left, Expression right) { 11 | super(left, right); 12 | } 13 | 14 | @Override 15 | public Value evaluate() { 16 | Value left = getLeft().evaluate(); 17 | if (left == null) return null; 18 | Value right = getRight().evaluate(); 19 | if (right == null) return null; 20 | 21 | if (left instanceof ArrayValue) { 22 | return ((ArrayValue) left).getValue(((Double) right.getValue()).intValue()); 23 | } 24 | if (left instanceof TextValue) { 25 | return ((TextValue) left).getValue(((Double) right.getValue()).intValue()); 26 | } 27 | return left; 28 | } 29 | 30 | @Override 31 | public Value assign(Value value) { 32 | Value left = getLeft().evaluate(); 33 | if (left == null) return null; 34 | Value right = getRight().evaluate(); 35 | if (right == null) return null; 36 | 37 | if (left instanceof ArrayValue) { 38 | ((ArrayValue) left).setValue(((Double) right.getValue()).intValue(), value); 39 | } 40 | if (left instanceof TextValue) { 41 | ((TextValue) left).setValue(((Double) right.getValue()).intValue(), value); 42 | } 43 | return left; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/AssignmentOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.AssignExpression; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class AssignmentOperator extends BinaryOperatorExpression { 9 | public AssignmentOperator(Expression left, Expression right) { 10 | super(left, right); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value left = getLeft().evaluate(); 16 | if (left == null) return null; 17 | Value right = getRight().evaluate(); 18 | if (right == null) return null; 19 | 20 | if (getLeft() instanceof AssignExpression) { 21 | return ((AssignExpression) getLeft()).assign(right); 22 | } else { 23 | return ExceptionContext.raiseException(String.format("Unable to make an assignment for `%s``", getLeft())); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/BinaryOperatorExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | import org.example.toylanguage.expression.Expression; 7 | 8 | @RequiredArgsConstructor 9 | @Getter 10 | @ToString 11 | public abstract class BinaryOperatorExpression implements OperatorExpression { 12 | private final Expression left; 13 | private final Expression right; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ClassCastOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.definition.ClassDetails; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.VariableExpression; 6 | import org.example.toylanguage.expression.value.ClassValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | /** 10 | * Cast a class instance from one type to other 11 | */ 12 | public class ClassCastOperator extends BinaryOperatorExpression { 13 | public ClassCastOperator(Expression left, Expression right) { 14 | super(left, right); 15 | } 16 | 17 | @Override 18 | public Value evaluate() { 19 | Value left = getLeft().evaluate(); 20 | if (left == null) return null; 21 | 22 | // evaluate expressions 23 | ClassValue classInstance = (ClassValue) left; 24 | String typeToCastName = ((VariableExpression) getRight()).getName(); 25 | 26 | // retrieve class details 27 | ClassDetails classDetails = classInstance.getValue().getClassDetails(); 28 | 29 | // check if the type to cast is different from original 30 | if (classDetails.getName().equals(typeToCastName)) { 31 | return classInstance; 32 | } else { 33 | // retrieve ClassValue of other type 34 | return classInstance.getRelation(typeToCastName); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ClassInstanceOfOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.VariableExpression; 6 | import org.example.toylanguage.expression.value.ClassValue; 7 | import org.example.toylanguage.expression.value.LogicalValue; 8 | import org.example.toylanguage.expression.value.Value; 9 | 10 | public class ClassInstanceOfOperator extends BinaryOperatorExpression { 11 | public ClassInstanceOfOperator(Expression left, Expression right) { 12 | super(left, right); 13 | } 14 | 15 | @Override 16 | public Value evaluate() { 17 | Value left = getLeft().evaluate(); 18 | if (left == null) return null; 19 | if (left instanceof ClassValue && getRight() instanceof VariableExpression) { 20 | String classType = ((VariableExpression) getRight()).getName(); 21 | return new LogicalValue(((ClassValue) left).containsRelation(classType)); 22 | } else { 23 | return ExceptionContext.raiseException(String.format("Unable to perform `is` operator for the following operands `%s` and `%s`", left, getRight())); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ClassInstanceOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | import org.example.toylanguage.expression.value.Value; 5 | 6 | public class ClassInstanceOperator extends UnaryOperatorExpression { 7 | public ClassInstanceOperator(Expression value) { 8 | super(value); 9 | } 10 | 11 | @Override 12 | public Value evaluate() { 13 | return getValue().evaluate(); // will return toString() value 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ClassPropertyOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.AssignExpression; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.FunctionExpression; 7 | import org.example.toylanguage.expression.VariableExpression; 8 | import org.example.toylanguage.expression.value.ClassValue; 9 | import org.example.toylanguage.expression.value.ThisValue; 10 | import org.example.toylanguage.expression.value.Value; 11 | 12 | public class ClassPropertyOperator extends BinaryOperatorExpression implements AssignExpression { 13 | public ClassPropertyOperator(Expression left, Expression right) { 14 | super(left, right); 15 | } 16 | 17 | @Override 18 | public Value evaluate() { 19 | Value left = getLeft().evaluate(); 20 | if (left == null) return null; 21 | 22 | // access class's property via this instance 23 | // this :: class_argument 24 | if (left instanceof ThisValue) { 25 | left = ((ThisValue) left).getValue(); 26 | } 27 | 28 | if (left instanceof ClassValue) { 29 | if (getRight() instanceof VariableExpression) { 30 | // access class's property 31 | // new Class [] :: class_property 32 | return ((ClassValue) left).getValue(((VariableExpression) getRight()).getName()); 33 | } else if (getRight() instanceof FunctionExpression) { 34 | // execute class's function 35 | // new Class [] :: class_function [] 36 | return ((FunctionExpression) getRight()).evaluate((ClassValue) left); 37 | } 38 | } 39 | 40 | return ExceptionContext.raiseException(String.format("Unable to access class's property `%s``", getRight())); 41 | } 42 | 43 | @Override 44 | public Value assign(Value value) { 45 | Value left = getLeft().evaluate(); 46 | if (left == null) return null; 47 | 48 | // access class's property via this instance 49 | // this :: class_argument 50 | if (left instanceof ThisValue) { 51 | left = ((ThisValue) left).getValue(); 52 | } 53 | 54 | if (left instanceof ClassValue && getRight() instanceof VariableExpression) { 55 | String propertyName = ((VariableExpression) getRight()).getName(); 56 | ((ClassValue) left).setValue(propertyName, value); 57 | } 58 | return left; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/DivisionOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class DivisionOperator extends BinaryOperatorExpression { 9 | public DivisionOperator(Expression left, Expression right) { 10 | super(left, right); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value left = getLeft().evaluate(); 16 | if (left == null) return null; 17 | Value right = getRight().evaluate(); 18 | if (right == null) return null; 19 | if (left instanceof NumericValue && right instanceof NumericValue) { 20 | return new NumericValue(((NumericValue) left).getValue() / ((NumericValue) right).getValue()); 21 | } else { 22 | return ExceptionContext.raiseException(String.format("Unable to divide non numeric values `%s` and `%s`", left, right)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/EqualsOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | import org.example.toylanguage.expression.value.LogicalValue; 5 | import org.example.toylanguage.expression.value.Value; 6 | 7 | import java.util.Objects; 8 | 9 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 10 | 11 | public class EqualsOperator extends BinaryOperatorExpression { 12 | public EqualsOperator(Expression left, Expression right) { 13 | super(left, right); 14 | } 15 | 16 | @Override 17 | public Value evaluate() { 18 | Value left = getLeft().evaluate(); 19 | if (left == null) return null; 20 | Value right = getRight().evaluate(); 21 | if (right == null) return null; 22 | boolean result; 23 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 24 | result = left == right; 25 | } else if (Objects.equals(left.getClass(), right.getClass())) { 26 | result = left.getValue().equals(right.getValue()); 27 | } else { 28 | result = left.toString().equals(right.toString()); 29 | } 30 | return new LogicalValue(result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ExponentiationOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class ExponentiationOperator extends BinaryOperatorExpression { 9 | public ExponentiationOperator(Expression left, Expression right) { 10 | super(left, right); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value left = getLeft().evaluate(); 16 | if (left == null) return null; 17 | Value right = getRight().evaluate(); 18 | if (right == null) return null; 19 | if (left instanceof NumericValue && right instanceof NumericValue) { 20 | return new NumericValue(Math.pow(((NumericValue) left).getValue(), ((NumericValue) right).getValue())); 21 | } else { 22 | return ExceptionContext.raiseException(String.format("Unable to make exponentiation with non numeric values `%s` and `%s`", left, right)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/FloorDivisionOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 9 | 10 | public class FloorDivisionOperator extends BinaryOperatorExpression { 11 | public FloorDivisionOperator(Expression left, Expression right) { 12 | super(left, right); 13 | } 14 | 15 | @Override 16 | public Value evaluate() { 17 | Value left = getLeft().evaluate(); 18 | if (left == null) return null; 19 | Value right = getRight().evaluate(); 20 | if (right == null) return null; 21 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 22 | return ExceptionContext.raiseException(String.format("Unable to perform floor division for NULL values `%s`, '%s'", left, right)); 23 | } else if (left instanceof NumericValue && right instanceof NumericValue) { 24 | return new NumericValue(Math.floor(((NumericValue) left).getValue() / ((NumericValue) right).getValue())); 25 | } else { 26 | return ExceptionContext.raiseException(String.format("Unable to divide non numeric values `%s` and `%s`", left, right)); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/GreaterThanOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.ComparableValue; 6 | import org.example.toylanguage.expression.value.LogicalValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.Objects; 10 | 11 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 12 | 13 | public class GreaterThanOperator extends BinaryOperatorExpression { 14 | public GreaterThanOperator(Expression left, Expression right) { 15 | super(left, right); 16 | } 17 | 18 | @Override 19 | public Value evaluate() { 20 | Value left = getLeft().evaluate(); 21 | if (left == null) return null; 22 | Value right = getRight().evaluate(); 23 | if (right == null) return null; 24 | boolean result; 25 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 26 | return ExceptionContext.raiseException(String.format("Unable to perform greater than for NULL values `%s`, '%s'", left, right)); 27 | } else if (Objects.equals(left.getClass(), right.getClass()) && left instanceof ComparableValue) { 28 | //noinspection unchecked,rawtypes 29 | result = ((Comparable) left.getValue()).compareTo(right.getValue()) > 0; 30 | } else { 31 | result = left.toString().compareTo(right.toString()) > 0; 32 | } 33 | return new LogicalValue(result); 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/GreaterThanOrEqualToOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.ComparableValue; 6 | import org.example.toylanguage.expression.value.LogicalValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.Objects; 10 | 11 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 12 | 13 | public class GreaterThanOrEqualToOperator extends BinaryOperatorExpression { 14 | public GreaterThanOrEqualToOperator(Expression left, Expression right) { 15 | super(left, right); 16 | } 17 | 18 | @Override 19 | public Value evaluate() { 20 | Value left = getLeft().evaluate(); 21 | if (left == null) return null; 22 | Value right = getRight().evaluate(); 23 | if (right == null) return null; 24 | boolean result; 25 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 26 | return ExceptionContext.raiseException(String.format("Unable to perform greater than or equal to for NULL values `%s`, '%s'", left, right)); 27 | } else if (Objects.equals(left.getClass(), right.getClass()) && left instanceof ComparableValue) { 28 | //noinspection unchecked,rawtypes 29 | result = ((Comparable) left.getValue()).compareTo(right.getValue()) >= 0; 30 | } else { 31 | result = left.toString().compareTo(right.toString()) >= 0; 32 | } 33 | return new LogicalValue(result); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/LessThanOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.ComparableValue; 6 | import org.example.toylanguage.expression.value.LogicalValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.Objects; 10 | 11 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 12 | 13 | public class LessThanOperator extends BinaryOperatorExpression { 14 | public LessThanOperator(Expression left, Expression right) { 15 | super(left, right); 16 | } 17 | 18 | @Override 19 | public Value evaluate() { 20 | Value left = getLeft().evaluate(); 21 | if (left == null) return null; 22 | Value right = getRight().evaluate(); 23 | if (right == null) return null; 24 | boolean result; 25 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 26 | return ExceptionContext.raiseException(String.format("Unable to perform less than for NULL values `%s`, '%s'", left, right)); 27 | } else if (Objects.equals(left.getClass(), right.getClass()) && left instanceof ComparableValue) { 28 | //noinspection unchecked,rawtypes 29 | result = ((Comparable) left.getValue()).compareTo(right.getValue()) < 0; 30 | } else { 31 | result = left.toString().compareTo(right.toString()) < 0; 32 | } 33 | return new LogicalValue(result); 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/LessThanOrEqualToOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.ComparableValue; 6 | import org.example.toylanguage.expression.value.LogicalValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.Objects; 10 | 11 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 12 | 13 | public class LessThanOrEqualToOperator extends BinaryOperatorExpression { 14 | public LessThanOrEqualToOperator(Expression left, Expression right) { 15 | super(left, right); 16 | } 17 | 18 | @Override 19 | public Value evaluate() { 20 | Value left = getLeft().evaluate(); 21 | if (left == null) return null; 22 | Value right = getRight().evaluate(); 23 | if (right == null) return null; 24 | boolean result; 25 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 26 | return ExceptionContext.raiseException(String.format("Unable to perform less than or equal to for NULL values `%s`, '%s'", left, right)); 27 | } else if (Objects.equals(left.getClass(), right.getClass()) && left instanceof ComparableValue) { 28 | //noinspection unchecked,rawtypes 29 | result = ((Comparable) left.getValue()).compareTo(right.getValue()) <= 0; 30 | } else { 31 | result = left.toString().compareTo(right.toString()) <= 0; 32 | } 33 | return new LogicalValue(result); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/LogicalAndOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.LogicalValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class LogicalAndOperator extends BinaryOperatorExpression { 9 | public LogicalAndOperator(Expression left, Expression right) { 10 | super(left, right); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value left = getLeft().evaluate(); 16 | if (left == null) return null; 17 | Value right = getRight().evaluate(); 18 | if (right == null) return null; 19 | if (left instanceof LogicalValue && right instanceof LogicalValue) { 20 | return new LogicalValue(((LogicalValue) left).getValue() && ((LogicalValue) right).getValue()); 21 | } else { 22 | return ExceptionContext.raiseException(String.format("Unable to perform AND operator for non logical values `%s`, '%s'", left, right)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/LogicalOrOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.LogicalValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class LogicalOrOperator extends BinaryOperatorExpression { 9 | public LogicalOrOperator(Expression left, Expression right) { 10 | super(left, right); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value left = getLeft().evaluate(); 16 | if (left == null) return null; 17 | Value right = getRight().evaluate(); 18 | if (right == null) return null; 19 | if (left instanceof LogicalValue && right instanceof LogicalValue) { 20 | return new LogicalValue(((LogicalValue) left).getValue() || ((LogicalValue) right).getValue()); 21 | } else { 22 | return ExceptionContext.raiseException(String.format("Unable to perform OR operator for non logical values `%s`, '%s'", left, right)); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/ModuloOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class ModuloOperator extends BinaryOperatorExpression { 9 | public ModuloOperator(Expression left, Expression right) { 10 | super(left, right); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value left = getLeft().evaluate(); 16 | if (left == null) return null; 17 | Value right = getRight().evaluate(); 18 | if (right == null) return null; 19 | if (left instanceof NumericValue && right instanceof NumericValue) { 20 | return new NumericValue(((NumericValue) left).getValue() % ((NumericValue) right).getValue()); 21 | } else { 22 | return ExceptionContext.raiseException(String.format("Unable to perform modulo for non numeric values `%s` and `%s`", left, right)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/MultiplicationOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.TextValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 10 | 11 | public class MultiplicationOperator extends BinaryOperatorExpression { 12 | public MultiplicationOperator(Expression left, Expression right) { 13 | super(left, right); 14 | } 15 | 16 | @Override 17 | public Value evaluate() { 18 | Value left = getLeft().evaluate(); 19 | if (left == null) return null; 20 | Value right = getRight().evaluate(); 21 | if (right == null) return null; 22 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 23 | return ExceptionContext.raiseException(String.format("Unable to perform multiplication for NULL values `%s`, '%s'", left, right)); 24 | } else if (left instanceof NumericValue && right instanceof NumericValue) { 25 | return new NumericValue(((NumericValue) left).getValue() * ((NumericValue) right).getValue()); 26 | } else if (left instanceof NumericValue) { 27 | return new TextValue(right.toString().repeat(((NumericValue) left).getValue().intValue())); 28 | } else if (right instanceof NumericValue) { 29 | return new TextValue(left.toString().repeat(((NumericValue) right).getValue().intValue())); 30 | } else { 31 | return ExceptionContext.raiseException(String.format("Unable to multiply non numeric values `%s` and `%s`", left, right)); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/NestedClassInstanceOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.ClassExpression; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.value.ClassValue; 7 | import org.example.toylanguage.expression.value.ThisValue; 8 | import org.example.toylanguage.expression.value.Value; 9 | 10 | public class NestedClassInstanceOperator extends BinaryOperatorExpression { 11 | public NestedClassInstanceOperator(Expression left, Expression right) { 12 | super(left, right); 13 | } 14 | 15 | @Override 16 | public Value evaluate() { 17 | Value left = getLeft().evaluate(); 18 | if (left == null) return null; 19 | 20 | // access class's property via this instance 21 | // this :: new NestedClass [] 22 | if (left instanceof ThisValue) { 23 | left = ((ThisValue) left).getValue(); 24 | } 25 | 26 | if (left instanceof ClassValue && getRight() instanceof ClassExpression) { 27 | // instantiate nested class 28 | // new Class [] :: new NestedClass [] 29 | return ((ClassExpression) getRight()).evaluate((ClassValue) left); 30 | } else { 31 | return ExceptionContext.raiseException(String.format("Unable to access class's nested class `%s``", getRight())); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/NotEqualsOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | import org.example.toylanguage.expression.value.LogicalValue; 5 | import org.example.toylanguage.expression.value.Value; 6 | 7 | import java.util.Objects; 8 | 9 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 10 | 11 | public class NotEqualsOperator extends BinaryOperatorExpression { 12 | public NotEqualsOperator(Expression left, Expression right) { 13 | super(left, right); 14 | } 15 | 16 | @Override 17 | public Value evaluate() { 18 | Value left = getLeft().evaluate(); 19 | if (left == null) return null; 20 | Value right = getRight().evaluate(); 21 | if (right == null) return null; 22 | boolean result; 23 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 24 | result = left != right; 25 | } else if (Objects.equals(left.getClass(), right.getClass())) { 26 | result = !left.getValue().equals(right.getValue()); 27 | } else { 28 | result = !left.toString().equals(right.toString()); 29 | } 30 | return new LogicalValue(result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/NotOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.LogicalValue; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | public class NotOperator extends UnaryOperatorExpression { 9 | public NotOperator(Expression value) { 10 | super(value); 11 | } 12 | 13 | @Override 14 | public Value evaluate() { 15 | Value value = getValue().evaluate(); 16 | if (value == null) return null; 17 | if (value instanceof LogicalValue) { 18 | return new LogicalValue(!(((LogicalValue) value).getValue())); 19 | } else { 20 | return ExceptionContext.raiseException(String.format("Unable to perform NOT operator for non logical value `%s`", value)); 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/Operator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | import java.util.Arrays; 7 | 8 | @RequiredArgsConstructor 9 | @Getter 10 | public enum Operator { 11 | Not("!", NotOperator.class, 7), 12 | ClassInstance("new", ClassInstanceOperator.class, 7), 13 | NestedClassInstance(":{2}\\s+new", NestedClassInstanceOperator.class, 7), 14 | ClassProperty(":{2}", ClassPropertyOperator.class, 7), 15 | ClassCast("as", ClassCastOperator.class, 7), 16 | ClassInstanceOf("is", ClassInstanceOfOperator.class, 7), 17 | 18 | ExponentiationOperator("\\*{2}", ExponentiationOperator.class, 6), 19 | Multiplication("\\*", MultiplicationOperator.class, 6), 20 | Division("/", DivisionOperator.class, 6), 21 | FloorDivision("//", FloorDivisionOperator.class, 6), 22 | Modulo("%", ModuloOperator.class, 6), 23 | 24 | Addition("\\+", AdditionOperator.class, 5), 25 | Subtraction("-", SubtractionOperator.class, 5), 26 | 27 | Equals("==", EqualsOperator.class, 4), 28 | NotEquals("!=", NotEqualsOperator.class, 4), 29 | LessThan("<", LessThanOperator.class, 4), 30 | LessThanOrEqualTo("<=", LessThanOrEqualToOperator.class, 4), 31 | GreaterThan(">", GreaterThanOperator.class, 4), 32 | GreaterThanOrEqualTo(">=", GreaterThanOrEqualToOperator.class, 4), 33 | 34 | LeftParen("\\(", 3), 35 | RightParen("\\)", 3), 36 | 37 | LogicalAnd("and", LogicalAndOperator.class, 2), 38 | LogicalOr("or", LogicalOrOperator.class, 1), 39 | 40 | ArrayAppend("<<", ArrayAppendOperator.class, 0), 41 | Assignment("=", AssignmentOperator.class, 0); 42 | 43 | private final String character; 44 | private final Class type; 45 | private final Integer precedence; 46 | 47 | Operator(String character, Integer precedence) { 48 | this(character, null, precedence); 49 | } 50 | 51 | public static Operator getType(String character) { 52 | return Arrays.stream(values()) 53 | .filter(t -> character.matches(t.getCharacter())) 54 | .findAny().orElse(null); 55 | } 56 | 57 | public boolean greaterThan(Operator o) { 58 | return getPrecedence().compareTo(o.getPrecedence()) >= 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/OperatorExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | 5 | public interface OperatorExpression extends Expression { 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/SubtractionOperator.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.value.NumericValue; 6 | import org.example.toylanguage.expression.value.TextValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 10 | 11 | public class SubtractionOperator extends BinaryOperatorExpression { 12 | public SubtractionOperator(Expression left, Expression right) { 13 | super(left, right); 14 | } 15 | 16 | @Override 17 | public Value evaluate() { 18 | Value left = getLeft().evaluate(); 19 | if (left == null) return null; 20 | Value right = getRight().evaluate(); 21 | if (right == null) return null; 22 | if (left == NULL_INSTANCE || right == NULL_INSTANCE) { 23 | return ExceptionContext.raiseException(String.format("Unable to perform subtraction for NULL values `%s`, '%s'", left, right)); 24 | } else if (left instanceof NumericValue && right instanceof NumericValue) { 25 | return new NumericValue(((NumericValue) left).getValue() - ((NumericValue) right).getValue()); 26 | } else { 27 | return new TextValue(left.toString().replaceAll(right.toString(), "")); 28 | } 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/UnaryOperatorExpression.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | import org.example.toylanguage.expression.Expression; 7 | 8 | @RequiredArgsConstructor 9 | @Getter 10 | @ToString 11 | public abstract class UnaryOperatorExpression implements OperatorExpression { 12 | private final Expression value; 13 | } 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/operator/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.operator; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/ArrayValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | import org.example.toylanguage.expression.ArrayExpression; 4 | import org.example.toylanguage.expression.Expression; 5 | 6 | import java.util.HashSet; 7 | import java.util.Iterator; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 12 | 13 | public class ArrayValue extends IterableValue>> { 14 | public ArrayValue(ArrayExpression expression) { 15 | this(expression.getValues() 16 | .stream() 17 | .map(Expression::evaluate) 18 | .collect(Collectors.toList())); 19 | } 20 | 21 | public ArrayValue(List> values) { 22 | super(values); 23 | } 24 | 25 | public Value getValue(int index) { 26 | if (getValue().size() > index) 27 | return getValue().get(index); 28 | return NULL_INSTANCE; 29 | } 30 | 31 | public void setValue(int index, Value value) { 32 | if (getValue().size() > index) 33 | getValue().set(index, value); 34 | } 35 | 36 | public void appendValue(Value value) { 37 | getValue().add(value); 38 | } 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) return true; 43 | if (o == null) return false; 44 | if (getClass() != o.getClass()) return false; 45 | //noinspection unchecked 46 | List> oValue = (List>) o; 47 | return new HashSet<>(getValue()).containsAll(oValue); 48 | } 49 | 50 | @Override 51 | public Iterator> iterator() { 52 | return getValue().iterator(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/ClassValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.MemoryContext; 5 | import org.example.toylanguage.context.MemoryScope; 6 | import org.example.toylanguage.context.definition.ClassDefinition; 7 | 8 | import java.util.Iterator; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | 12 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 13 | 14 | @Getter 15 | public class ClassValue extends IterableValue { 16 | private final MemoryScope memoryScope; 17 | // contains ClassValue for the Derived class and all the Base classes chain that Derived class inherits 18 | private final Map relations; 19 | 20 | public ClassValue(ClassDefinition definition, MemoryScope memoryScope, Map relations) { 21 | super(definition); 22 | this.memoryScope = memoryScope; 23 | this.relations = relations; 24 | } 25 | 26 | public ClassValue getRelation(String name) { 27 | return relations.get(name); 28 | } 29 | 30 | public boolean containsRelation(String name) { 31 | return relations.containsKey(name); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | MemoryContext.pushScope(memoryScope); 37 | try { 38 | return getValue().getClassDetails().getProperties().stream() 39 | .map(t -> t + " = " + getValue(t)) 40 | .collect(Collectors.joining(", ", getValue().getClassDetails().getName() + " [ ", " ]")); 41 | } finally { 42 | MemoryContext.endScope(); 43 | } 44 | } 45 | 46 | public Value getValue(String name) { 47 | MemoryContext.pushScope(memoryScope); 48 | try { 49 | Value result = MemoryContext.getScope().getLocal(name); 50 | return result != null ? result : NULL_INSTANCE; 51 | } finally { 52 | MemoryContext.endScope(); 53 | } 54 | } 55 | 56 | public void setValue(String name, Value value) { 57 | MemoryContext.pushScope(memoryScope); 58 | try { 59 | MemoryContext.getScope().setLocal(name, value); 60 | } finally { 61 | MemoryContext.endScope(); 62 | } 63 | } 64 | 65 | @Override 66 | public boolean equals(Object o) { 67 | if (this == o) return true; 68 | if (o == null) return false; 69 | if (getClass() != o.getClass()) return false; 70 | ClassValue oValue = (ClassValue) o; 71 | 72 | return getValue() 73 | .getClassDetails() 74 | .getProperties() 75 | .stream() 76 | .allMatch(e -> getValue(e).equals(oValue.getValue(e))); 77 | } 78 | 79 | @Override 80 | public Iterator> iterator() { 81 | return getValue() 82 | .getClassDetails() 83 | .getProperties() 84 | .stream() 85 | .>map(this::getValue) 86 | .iterator(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/ComparableValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | public class ComparableValue> extends Value { 4 | public ComparableValue(T value) { 5 | super(value); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/IterableValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | public abstract class IterableValue extends Value implements Iterable> { 4 | public IterableValue(T value) { 5 | super(value); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/LogicalValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | public class LogicalValue extends ComparableValue { 4 | public LogicalValue(Boolean value) { 5 | super(value); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/NullValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | public class NullValue> extends ComparableValue { 4 | 5 | public static final NullValue NULL_INSTANCE = new NullValue<>(null); 6 | 7 | public NullValue(T value) { 8 | super(value); 9 | } 10 | 11 | @Override 12 | public T getValue() { 13 | //noinspection unchecked 14 | return (T) this; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "null"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/NumericValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | public class NumericValue extends ComparableValue { 4 | public NumericValue(Double value) { 5 | super(value); 6 | } 7 | 8 | @Override 9 | public String toString() { 10 | if ((getValue() % 1) == 0) 11 | return String.valueOf(getValue().intValue()); 12 | return super.toString(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/TextValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | import static org.example.toylanguage.expression.value.NullValue.NULL_INSTANCE; 4 | 5 | public class TextValue extends ComparableValue { 6 | public TextValue(String value) { 7 | super(value); 8 | } 9 | 10 | public Value getValue(int index) { 11 | if (getValue().length() > index) 12 | return new TextValue(getValue().substring(index, index + 1)); 13 | return NULL_INSTANCE; 14 | } 15 | 16 | public void setValue(int index, Value value) { 17 | if (getValue().length() > index) { 18 | String val = getValue(); 19 | super.setValue(val.substring(0, index) + value.toString() + val.substring(index)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/ThisValue.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | import org.example.toylanguage.context.ClassInstanceContext; 4 | 5 | public class ThisValue extends Value { 6 | 7 | public static final ThisValue THIS_INSTANCE = new ThisValue(); 8 | 9 | public ThisValue() { 10 | super(null); 11 | } 12 | 13 | @Override 14 | public ClassValue getValue() { 15 | return ClassInstanceContext.getValue(); 16 | } 17 | 18 | @Override 19 | public Value evaluate() { 20 | return getValue(); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return getValue().toString(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/Value.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import org.example.toylanguage.expression.Expression; 6 | 7 | @Getter 8 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 9 | public class Value implements Expression { 10 | @EqualsAndHashCode.Include 11 | private T value; 12 | 13 | public Value(T v) { 14 | setValue(v); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return value.toString(); 20 | } 21 | 22 | public void setValue(T v) { 23 | this.value = v; 24 | } 25 | 26 | @Override 27 | public Value evaluate() { 28 | return this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/expression/value/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.expression.value; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/AssertStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.value.LogicalValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | @Getter 10 | public class AssertStatement extends Statement { 11 | private final Expression expression; 12 | 13 | public AssertStatement(Integer rowNumber, String blockName, Expression expression) { 14 | super(rowNumber, blockName); 15 | this.expression = expression; 16 | } 17 | 18 | @Override 19 | public void execute() { 20 | Value value = expression.evaluate(); 21 | if (value instanceof LogicalValue && !((LogicalValue) value).getValue()) { 22 | ExceptionContext.raiseException("Assertion error"); 23 | ExceptionContext.addTracedStatement(this); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/ClassStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.StatementParser; 5 | import org.example.toylanguage.context.definition.ClassDefinition; 6 | import org.example.toylanguage.token.Token; 7 | 8 | /** 9 | * Statement for constructor 10 | *

11 | * 12 | * @see ClassDefinition 13 | * @see StatementParser#parseClassDefinition(Token) 14 | */ 15 | @Getter 16 | public class ClassStatement extends CompositeStatement { 17 | private final Integer rowNumber; 18 | 19 | public ClassStatement(Integer rowNumber, String blockName) { 20 | super(rowNumber, blockName); 21 | this.rowNumber = rowNumber; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/CompositeStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.context.ReturnContext; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Getter 11 | public class CompositeStatement extends Statement { 12 | private final List statements2Execute = new ArrayList<>(); 13 | 14 | public CompositeStatement(Integer rowNumber, String blockName) { 15 | super(rowNumber, blockName); 16 | } 17 | 18 | public void addStatement(Statement statement) { 19 | if (statement != null) 20 | statements2Execute.add(statement); 21 | } 22 | 23 | @Override 24 | public void execute() { 25 | for (Statement statement : statements2Execute) { 26 | statement.execute(); 27 | 28 | // stop the execution in case Exception occurred 29 | if (ExceptionContext.isRaised()) 30 | return; 31 | 32 | //stop the execution in case ReturnStatement is invoked 33 | if (ReturnContext.getScope().isInvoked()) 34 | return; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/ConditionStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.MemoryContext; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.value.LogicalValue; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | @Getter 13 | public class ConditionStatement extends Statement { 14 | private final Map cases; 15 | 16 | public ConditionStatement(Integer rowNumber, String blockName) { 17 | super(rowNumber, blockName); 18 | //keep the cases order 19 | this.cases = new LinkedHashMap<>(); 20 | } 21 | 22 | public void addCase(Expression caseCondition, CompositeStatement caseStatement) { 23 | cases.put(caseCondition, caseStatement); 24 | } 25 | 26 | @Override 27 | public void execute() { 28 | for (Map.Entry entry : cases.entrySet()) { 29 | 30 | Expression condition = entry.getKey(); 31 | Value value = condition.evaluate(); 32 | if (value instanceof LogicalValue && ((LogicalValue) value).getValue()) { 33 | MemoryContext.pushScope(MemoryContext.newScope()); 34 | try { 35 | CompositeStatement statement = entry.getValue(); 36 | statement.execute(); 37 | } finally { 38 | MemoryContext.endScope(); 39 | } 40 | break; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/ExpressionStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.expression.Expression; 6 | 7 | @Getter 8 | public class ExpressionStatement extends Statement { 9 | private final Expression expression; 10 | 11 | public ExpressionStatement(Integer rowNumber, String blockName, Expression expression) { 12 | super(rowNumber, blockName); 13 | this.expression = expression; 14 | } 15 | 16 | @Override 17 | public void execute() { 18 | expression.evaluate(); 19 | ExceptionContext.addTracedStatement(this); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/FunctionStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class FunctionStatement extends CompositeStatement { 7 | public FunctionStatement(Integer rowNumber, String blockName) { 8 | super(rowNumber, blockName); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/HandleExceptionStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.context.MemoryContext; 6 | 7 | @Getter 8 | public class HandleExceptionStatement extends Statement { 9 | private final CompositeStatement beginStatement; 10 | private final CompositeStatement rescueStatement; 11 | private final CompositeStatement ensureStatement; 12 | private final String errorVariable; 13 | 14 | public HandleExceptionStatement(Integer rowNumber, String blockName, CompositeStatement beginStatement, 15 | CompositeStatement rescueStatement, CompositeStatement ensureStatement, String errorVariable) { 16 | super(rowNumber, blockName); 17 | this.beginStatement = beginStatement; 18 | this.rescueStatement = rescueStatement; 19 | this.ensureStatement = ensureStatement; 20 | this.errorVariable = errorVariable; 21 | } 22 | 23 | @Override 24 | public void execute() { 25 | MemoryContext.pushScope(MemoryContext.newScope()); 26 | try { 27 | beginStatement.execute(); 28 | } finally { 29 | MemoryContext.endScope(); 30 | } 31 | 32 | // rescue block 33 | if (rescueStatement != null && ExceptionContext.isRaised()) { 34 | 35 | MemoryContext.pushScope(MemoryContext.newScope()); 36 | if (errorVariable != null) { 37 | MemoryContext.getScope().setLocal(errorVariable, ExceptionContext.getException().getValue()); 38 | } 39 | 40 | ExceptionContext.rescueException(); 41 | 42 | try { 43 | rescueStatement.execute(); 44 | } finally { 45 | MemoryContext.endScope(); 46 | } 47 | } 48 | 49 | // ensure block 50 | if (ensureStatement != null) { 51 | boolean raised = ExceptionContext.isRaised(); 52 | if (raised) { 53 | // ensure block shouldn't accumulate stack trace 54 | ExceptionContext.disable(); 55 | } 56 | 57 | MemoryContext.pushScope(MemoryContext.newScope()); 58 | try { 59 | ensureStatement.execute(); 60 | } finally { 61 | MemoryContext.endScope(); 62 | if (raised) { 63 | // continue to accumulate stack trace 64 | ExceptionContext.enable(); 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/InputStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.MemoryContext; 5 | import org.example.toylanguage.expression.value.LogicalValue; 6 | import org.example.toylanguage.expression.value.NumericValue; 7 | import org.example.toylanguage.expression.value.TextValue; 8 | import org.example.toylanguage.expression.value.Value; 9 | import org.example.toylanguage.token.TokenType; 10 | 11 | import java.util.function.Supplier; 12 | 13 | @Getter 14 | public class InputStatement extends Statement { 15 | private final String name; 16 | private final Supplier consoleSupplier; 17 | 18 | public InputStatement(Integer rowNumber, String blockName, String name, Supplier consoleSupplier) { 19 | super(rowNumber, blockName); 20 | this.name = name; 21 | this.consoleSupplier = consoleSupplier; 22 | } 23 | 24 | @Override 25 | public void execute() { 26 | System.out.printf("enter \"%s\" >>> ", name.replace("_", " ")); 27 | String line = consoleSupplier.get(); 28 | 29 | Value value; 30 | if (line.matches(TokenType.Numeric.getRegex())) { 31 | value = new NumericValue(Double.parseDouble(line)); 32 | } else if (line.matches(TokenType.Logical.getRegex())) { 33 | value = new LogicalValue(Boolean.valueOf(line)); 34 | } else { 35 | value = new TextValue(line); 36 | } 37 | 38 | MemoryContext.getScope().set(name, value); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/PrintStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.value.Value; 7 | 8 | @Getter 9 | public class PrintStatement extends Statement { 10 | private final Expression expression; 11 | 12 | public PrintStatement(Integer rowNumber, String blockName, Expression expression) { 13 | super(rowNumber, blockName); 14 | this.expression = expression; 15 | } 16 | 17 | @Override 18 | public void execute() { 19 | Value value = expression.evaluate(); 20 | if (value != null) { 21 | System.out.println(value); 22 | } 23 | ExceptionContext.addTracedStatement(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/RaiseExceptionStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.value.NullValue; 7 | import org.example.toylanguage.expression.value.TextValue; 8 | import org.example.toylanguage.expression.value.Value; 9 | 10 | @Getter 11 | public class RaiseExceptionStatement extends Statement { 12 | private final Expression expression; 13 | 14 | public RaiseExceptionStatement(Integer rowNumber, String blockName, Expression expression) { 15 | super(rowNumber, blockName); 16 | this.expression = expression; 17 | } 18 | 19 | @Override 20 | public void execute() { 21 | Value value = expression.evaluate(); 22 | if (value != null) { 23 | if (value == NullValue.NULL_INSTANCE) { 24 | value = new TextValue("Empty exception"); 25 | } 26 | ExceptionContext.raiseException(value); 27 | } 28 | ExceptionContext.addTracedStatement(this); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/ReturnStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import org.example.toylanguage.context.ExceptionContext; 5 | import org.example.toylanguage.context.ReturnContext; 6 | import org.example.toylanguage.expression.Expression; 7 | import org.example.toylanguage.expression.value.Value; 8 | 9 | @Getter 10 | public class ReturnStatement extends Statement { 11 | private final Expression expression; 12 | 13 | public ReturnStatement(Integer rowNumber, String blockName, Expression expression) { 14 | super(rowNumber, blockName); 15 | this.expression = expression; 16 | } 17 | 18 | @Override 19 | public void execute() { 20 | Value result = expression.evaluate(); 21 | if (result != null) { 22 | ReturnContext.getScope().invoke(result); 23 | } 24 | ExceptionContext.addTracedStatement(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/Statement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public abstract class Statement { 9 | private final Integer rowNumber; 10 | private final String blockName; 11 | 12 | public abstract void execute(); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/loop/AbstractLoopStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement.loop; 2 | 3 | import org.example.toylanguage.context.*; 4 | import org.example.toylanguage.statement.CompositeStatement; 5 | import org.example.toylanguage.statement.Statement; 6 | 7 | public abstract class AbstractLoopStatement extends CompositeStatement { 8 | public AbstractLoopStatement(Integer rowNumber, String blockName) { 9 | super(rowNumber, blockName); 10 | } 11 | 12 | protected abstract void init(); 13 | 14 | protected abstract boolean hasNext(); 15 | 16 | protected abstract void preIncrement(); 17 | 18 | protected abstract void postIncrement(); 19 | 20 | @Override 21 | public void execute() { 22 | // memory scope for counter variables 23 | MemoryContext.pushScope(MemoryContext.newScope()); 24 | try { 25 | 26 | // init loop 27 | init(); 28 | 29 | while (hasNext()) { 30 | preIncrement(); 31 | 32 | // isolated memory scope for each iteration 33 | MemoryContext.pushScope(MemoryContext.newScope()); 34 | 35 | try { 36 | 37 | // execute inner statements 38 | for (Statement statement : getStatements2Execute()) { 39 | statement.execute(); 40 | 41 | // stop the execution in case Exception occurred 42 | if (ExceptionContext.isRaised()) 43 | return; 44 | 45 | // stop the execution in case ReturnStatement is invoked 46 | if (ReturnContext.getScope().isInvoked()) 47 | return; 48 | 49 | // stop the execution in case BreakStatement is invoked 50 | if (BreakContext.getScope().isInvoked()) 51 | return; 52 | 53 | // jump to the next iteration in case NextStatement is invoked 54 | if (NextContext.getScope().isInvoked()) 55 | break; 56 | } 57 | } finally { 58 | NextContext.reset(); 59 | MemoryContext.endScope(); // release each iteration memory 60 | 61 | // increment the counter even if the NextStatement is called 62 | postIncrement(); 63 | } 64 | 65 | } 66 | } finally { 67 | MemoryContext.endScope(); // release loop memory 68 | BreakContext.reset(); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/loop/BreakStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement.loop; 2 | 3 | import org.example.toylanguage.context.BreakContext; 4 | import org.example.toylanguage.statement.Statement; 5 | 6 | public class BreakStatement extends Statement { 7 | public BreakStatement(Integer rowNumber, String blockName) { 8 | super(rowNumber, blockName); 9 | } 10 | 11 | @Override 12 | public void execute() { 13 | BreakContext.getScope().invoke(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/loop/ForLoopStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement.loop; 2 | 3 | import org.example.toylanguage.context.MemoryContext; 4 | import org.example.toylanguage.expression.Expression; 5 | import org.example.toylanguage.expression.VariableExpression; 6 | import org.example.toylanguage.expression.operator.AdditionOperator; 7 | import org.example.toylanguage.expression.operator.LessThanOperator; 8 | import org.example.toylanguage.expression.value.LogicalValue; 9 | import org.example.toylanguage.expression.value.NumericValue; 10 | import org.example.toylanguage.expression.value.Value; 11 | 12 | public class ForLoopStatement extends AbstractLoopStatement { 13 | private final VariableExpression variable; 14 | private final Expression lowerBound; 15 | private final Expression uppedBound; 16 | private final Expression step; 17 | private static final Expression DEFAULT_STEP = new NumericValue(1.0); 18 | 19 | public ForLoopStatement(Integer rowNumber, String blockName, VariableExpression variable, Expression lowerBound, Expression uppedBound) { 20 | this(rowNumber, blockName, variable, lowerBound, uppedBound, DEFAULT_STEP); 21 | } 22 | 23 | public ForLoopStatement(Integer rowNumber, String blockName, VariableExpression variable, Expression lowerBound, Expression uppedBound, Expression step) { 24 | super(rowNumber, blockName); 25 | this.variable = variable; 26 | this.lowerBound = lowerBound; 27 | this.uppedBound = uppedBound; 28 | this.step = step; 29 | } 30 | 31 | @Override 32 | protected void init() { 33 | MemoryContext.getScope().set(variable.getName(), lowerBound.evaluate()); 34 | } 35 | 36 | @Override 37 | protected boolean hasNext() { 38 | LessThanOperator hasNext = new LessThanOperator(variable, uppedBound); 39 | Value value = hasNext.evaluate(); 40 | return value instanceof LogicalValue && ((LogicalValue) value).getValue(); 41 | } 42 | 43 | @Override 44 | protected void preIncrement() { 45 | } 46 | 47 | @Override 48 | protected void postIncrement() { 49 | AdditionOperator stepOperator = new AdditionOperator(variable, step); 50 | MemoryContext.getScope().set(variable.getName(), stepOperator.evaluate()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/loop/IterableLoopStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement.loop; 2 | 3 | import org.example.toylanguage.context.ExceptionContext; 4 | import org.example.toylanguage.context.MemoryContext; 5 | import org.example.toylanguage.expression.Expression; 6 | import org.example.toylanguage.expression.VariableExpression; 7 | import org.example.toylanguage.expression.value.IterableValue; 8 | import org.example.toylanguage.expression.value.Value; 9 | 10 | import java.util.Iterator; 11 | 12 | public class IterableLoopStatement extends AbstractLoopStatement { 13 | private final VariableExpression variableExpression; 14 | private final Expression iterableExpression; 15 | 16 | private Iterator> iterator; 17 | 18 | public IterableLoopStatement(Integer rowNumber, String blockName, VariableExpression variableExpression, Expression iterableExpression) { 19 | super(rowNumber, blockName); 20 | this.variableExpression = variableExpression; 21 | this.iterableExpression = iterableExpression; 22 | } 23 | 24 | @Override 25 | protected void init() { 26 | Value value = iterableExpression.evaluate(); 27 | if (!(value instanceof IterableValue)) { 28 | ExceptionContext.raiseException(String.format("Unable to iterate `%s`", value)); 29 | return; 30 | } 31 | this.iterator = ((IterableValue) value).iterator(); 32 | } 33 | 34 | @Override 35 | protected boolean hasNext() { 36 | return iterator.hasNext(); 37 | } 38 | 39 | @Override 40 | protected void preIncrement() { 41 | MemoryContext.getScope().set(variableExpression.getName(), iterator.next()); 42 | } 43 | 44 | @Override 45 | protected void postIncrement() { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/loop/NextStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement.loop; 2 | 3 | import org.example.toylanguage.context.NextContext; 4 | import org.example.toylanguage.statement.Statement; 5 | 6 | public class NextStatement extends Statement { 7 | public NextStatement(Integer rowNumber, String blockName) { 8 | super(rowNumber, blockName); 9 | } 10 | 11 | @Override 12 | public void execute() { 13 | NextContext.getScope().invoke(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/loop/WhileLoopStatement.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement.loop; 2 | 3 | import org.example.toylanguage.expression.Expression; 4 | import org.example.toylanguage.expression.value.LogicalValue; 5 | import org.example.toylanguage.expression.value.Value; 6 | 7 | public class WhileLoopStatement extends AbstractLoopStatement { 8 | private final Expression hasNext; 9 | 10 | public WhileLoopStatement(Integer rowNumber, String blockName, Expression hasNext) { 11 | super(rowNumber, blockName); 12 | this.hasNext = hasNext; 13 | } 14 | 15 | @Override 16 | protected void init() { 17 | } 18 | 19 | @Override 20 | protected boolean hasNext() { 21 | Value value = hasNext.evaluate(); 22 | return value instanceof LogicalValue && ((LogicalValue) value).getValue(); 23 | } 24 | 25 | @Override 26 | protected void preIncrement() { 27 | } 28 | 29 | @Override 30 | protected void postIncrement() { 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/statement/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.statement; -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/token/Token.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.token; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.ToString; 6 | 7 | /** 8 | * Token (lexeme) details 9 | */ 10 | @Builder 11 | @Getter 12 | @ToString 13 | public class Token { 14 | /** 15 | * Type of the token 16 | */ 17 | private final TokenType type; 18 | /** 19 | * Value of the token 20 | */ 21 | private final String value; 22 | /** 23 | * Row number where the token is defined 24 | */ 25 | private final Integer rowNumber; 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/token/TokenType.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.token; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.example.toylanguage.LexicalParser; 6 | 7 | /** 8 | * Lexeme types with matching regex expression 9 | *

10 | * 11 | * @see Token 12 | * @see LexicalParser 13 | */ 14 | @RequiredArgsConstructor 15 | @Getter 16 | public enum TokenType { 17 | /** 18 | * Comment 19 | */ 20 | Comment("\\#.*"), 21 | /** 22 | * Line break 23 | */ 24 | LineBreak("[\\n\\r]"), 25 | /** 26 | * Whitespace 27 | */ 28 | Whitespace("[\\s\\t]"), 29 | /** 30 | * Words with a specific sense assigned by the compiler 31 | *

32 | * 1. Conditions: if, elif, else, end 33 | *

34 | * 2. Printing to a console: print 35 | *

36 | * 4. Defining a class: class 37 | *

38 | * 5. Defining a function: fun, return 39 | *

40 | * 6. Loops: loop, in, by, break, next 41 | *

42 | * 7. Asserting a value: assert 43 | *

44 | * 8. Raising and handling exceptions: raise, begin, rescue, ensure 45 | */ 46 | Keyword("(if|elif|else|end|print|input|class|fun|return|loop|in|by|break|next|assert|raise|begin|rescue|ensure)(?=\\s|$)(?!_)"), 47 | /** 48 | * Dividers for the different lexeme groups 49 | *

50 | * 1. Defining a class or a function properties: [ ] 51 | *

52 | * 2. Counting multiple values: , 53 | *

54 | * 3. Defining an array values: { } 55 | *

56 | * 4. Splitting a loop range: .. 57 | *

58 | * 5. Splitting Derived class from inherited Base types: : 59 | */ 60 | GroupDivider("(\\[|\\]|\\,|\\{|}|\\.{2}|(\\:(?!\\:)))"), 61 | /** 62 | * Logical 63 | */ 64 | Logical("(true|false)(?=\\s|$)(?!_)"), 65 | /** 66 | * Numeric 67 | */ 68 | Numeric("([-]?(?=[.]?[0-9])[0-9]*(?![.]{2})[.]?[0-9]*)"), 69 | /** 70 | * Null 71 | */ 72 | Null("(null)(?=,|\\s|$)(?!_)"), 73 | /** 74 | * This reference 75 | */ 76 | This("(this)(?=,|\\s|$)(?!_)"), 77 | /** 78 | * Text value in quotes 79 | */ 80 | Text("\"([^\"]*)\""), 81 | /** 82 | * Operators 83 | *

84 | * 1. Addition + 85 | *

86 | * 2. Subtraction - 87 | *

88 | * 3. Multiplication * and Exponentiation ** 89 | *

90 | * 4. Division / and Floor division // 91 | *

92 | * 5. Modulo % 93 | *

94 | * 6. Greater than > 95 | *

96 | * 7. Greater than or equal to >= 97 | *

98 | * 8. Less than < 99 | *

100 | * 9. Less than or equal to <= 101 | *

102 | * 10. Append a value to array << 103 | *

104 | * 11. Equal = 105 | *

106 | * 12. Equal to == and not equal to != 107 | *

108 | * 13. Not operator ! 109 | *

110 | * 14. Creating an instance of a nested class: :: new 111 | *

112 | * 15. Accessing class's property or class's function: :: 113 | *

114 | * 16. Creating an instance of a class: new 115 | *

116 | * 17. And operator and 117 | *

118 | * 18. Or operator or 119 | *

120 | * 19. Cast operator as 121 | *

122 | * 20. Instance of operator is 123 | */ 124 | Operator("(\\+|-|\\*{1,2}|/{1,2}|%|>=?|<=|<{1,2}|={1,2}|!=|!|:{2}\\s+new|:{2}|\\(|\\)|(new|and|or|as|is)(?=\\s|$)(?!_))"), 125 | /** 126 | * Variable 127 | */ 128 | Variable("[a-zA-Z_]+[a-zA-Z0-9_]*"); 129 | 130 | private final String regex; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/token/TokensStack.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.token; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | import org.example.toylanguage.exception.SyntaxException; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.stream.Stream; 11 | 12 | @RequiredArgsConstructor 13 | public class TokensStack { 14 | private final List tokens; 15 | private int position; 16 | 17 | private static final List EMPTY_TOKENS = List.of(TokenType.LineBreak, TokenType.Comment); 18 | 19 | public Token next(TokenType type, TokenType... types) { 20 | skipEmptyTokens(); 21 | TokenType[] tokenTypes = ArrayUtils.add(types, type); 22 | if (position < tokens.size()) { 23 | Token token = tokens.get(position); 24 | if (Stream.of(tokenTypes).anyMatch(t -> t == token.getType())) { 25 | position++; 26 | return token; 27 | } 28 | } 29 | throw new SyntaxException(String.format("After `%s` declaration expected any of the following lexemes `%s`", previous(), Arrays.toString(tokenTypes))); 30 | } 31 | 32 | public void back() { 33 | position--; 34 | } 35 | 36 | public boolean hasNext() { 37 | skipEmptyTokens(); 38 | return position < tokens.size(); 39 | } 40 | 41 | public Token next(TokenType type, String value, String... values) { 42 | skipEmptyTokens(); 43 | if (position < tokens.size()) { 44 | String[] allValues = ArrayUtils.add(values, value); 45 | Token token = tokens.get(position); 46 | if (token.getType() == type && Arrays.stream(allValues).anyMatch(t -> Objects.equals(t, token.getValue()))) { 47 | position++; 48 | return token; 49 | } 50 | } 51 | throw new SyntaxException(String.format("After `%s` declaration expected `%s, %s` lexeme", previous(), type, value)); 52 | } 53 | 54 | public Token next() { 55 | skipEmptyTokens(); 56 | return tokens.get(position++); 57 | } 58 | 59 | public boolean peek(TokenType type, String value, String... values) { 60 | skipEmptyTokens(); 61 | return peekSameLine(type, value, values); 62 | } 63 | 64 | public boolean peekSameLine(TokenType type, String value, String... values) { 65 | if (position < tokens.size()) { 66 | String[] allValues = ArrayUtils.add(values, value); 67 | Token token = tokens.get(position); 68 | return type == token.getType() && Arrays.stream(allValues).anyMatch(t -> Objects.equals(t, token.getValue())); 69 | } 70 | return false; 71 | } 72 | 73 | public boolean peek(TokenType type, TokenType... types) { 74 | skipEmptyTokens(); 75 | return peekSameLine(type, types); 76 | } 77 | 78 | public boolean peekSameLine(TokenType type, TokenType... types) { 79 | if (position < tokens.size()) { 80 | TokenType[] tokenTypes = ArrayUtils.add(types, type); 81 | Token token = tokens.get(position); 82 | return Stream.of(tokenTypes).anyMatch(t -> t == token.getType()); 83 | } 84 | return false; 85 | } 86 | 87 | private Token previous() { 88 | return tokens.get(position - 1); 89 | } 90 | 91 | private void skipEmptyTokens() { 92 | while (position != tokens.size() && EMPTY_TOKENS.contains(tokens.get(position).getType())) 93 | position++; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/org/example/toylanguage/token/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage.token; -------------------------------------------------------------------------------- /src/test/java/org/example/toylanguage/LexicalParserTest.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import org.example.toylanguage.token.Token; 4 | import org.example.toylanguage.token.TokenType; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.List; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | class LexicalParserTest { 12 | 13 | @Test 14 | public void testPrint() { 15 | String source = "print \"Hello World\""; 16 | List tokens = LexicalParser.parse(source); 17 | 18 | assertEquals(2, tokens.size()); 19 | 20 | int count = 0; 21 | assertEquals(TokenType.Keyword, tokens.get(count).getType()); 22 | assertEquals("print", tokens.get(count).getValue()); 23 | assertEquals(1, tokens.get(count).getRowNumber()); 24 | 25 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 26 | assertEquals("Hello World", tokens.get(count).getValue()); 27 | assertEquals(1, tokens.get(count).getRowNumber()); 28 | } 29 | 30 | @Test 31 | public void testInput() { 32 | 33 | String source = "input a"; 34 | List tokens = LexicalParser.parse(source); 35 | 36 | assertEquals(2, tokens.size()); 37 | 38 | int count = 0; 39 | assertEquals(TokenType.Keyword, tokens.get(count).getType()); 40 | assertEquals("input", tokens.get(count).getValue()); 41 | assertEquals(1, tokens.get(count).getRowNumber()); 42 | 43 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 44 | assertEquals("a", tokens.get(count).getValue()); 45 | assertEquals(1, tokens.get(count).getRowNumber()); 46 | } 47 | 48 | @Test 49 | public void testAssignment() { 50 | 51 | String source = "a = 2 + 5"; 52 | List tokens = LexicalParser.parse(source); 53 | 54 | assertEquals(5, tokens.size()); 55 | 56 | int count = 0; 57 | assertEquals(TokenType.Variable, tokens.get(count).getType()); 58 | assertEquals("a", tokens.get(count).getValue()); 59 | assertEquals(1, tokens.get(count).getRowNumber()); 60 | 61 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 62 | assertEquals("=", tokens.get(count).getValue()); 63 | assertEquals(1, tokens.get(count).getRowNumber()); 64 | 65 | assertEquals(TokenType.Numeric, tokens.get(++count).getType()); 66 | assertEquals("2", tokens.get(count).getValue()); 67 | assertEquals(1, tokens.get(count).getRowNumber()); 68 | 69 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 70 | assertEquals("+", tokens.get(count).getValue()); 71 | assertEquals(1, tokens.get(count).getRowNumber()); 72 | 73 | assertEquals(TokenType.Numeric, tokens.get(++count).getType()); 74 | assertEquals("5", tokens.get(count).getValue()); 75 | assertEquals(1, tokens.get(count).getRowNumber()); 76 | } 77 | 78 | @Test 79 | public void testCondition() { 80 | 81 | String source = "if a > 5\n" + 82 | " print \"a is greater than 5\"\n" + 83 | "elif a >= 1\n" + 84 | " print \"a is greater than or equal to 1\"\n" + 85 | "else\n" + 86 | " print \"a is less than 1\"\n" + 87 | "end"; 88 | List tokens = LexicalParser.parse(source); 89 | 90 | assertEquals(22, tokens.size()); 91 | 92 | int count = 0; 93 | assertEquals(TokenType.Keyword, tokens.get(count).getType()); 94 | assertEquals("if", tokens.get(count).getValue()); 95 | assertEquals(1, tokens.get(count).getRowNumber()); 96 | 97 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 98 | assertEquals("a", tokens.get(count).getValue()); 99 | assertEquals(1, tokens.get(count).getRowNumber()); 100 | 101 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 102 | assertEquals(">", tokens.get(count).getValue()); 103 | assertEquals(1, tokens.get(count).getRowNumber()); 104 | 105 | assertEquals(TokenType.Numeric, tokens.get(++count).getType()); 106 | assertEquals("5", tokens.get(count).getValue()); 107 | assertEquals(1, tokens.get(count).getRowNumber()); 108 | 109 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 110 | assertEquals("\n", tokens.get(count).getValue()); 111 | assertEquals(1, tokens.get(count).getRowNumber()); 112 | 113 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 114 | assertEquals("print", tokens.get(count).getValue()); 115 | assertEquals(2, tokens.get(count).getRowNumber()); 116 | 117 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 118 | assertEquals("a is greater than 5", tokens.get(count).getValue()); 119 | assertEquals(2, tokens.get(count).getRowNumber()); 120 | 121 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 122 | assertEquals("\n", tokens.get(count).getValue()); 123 | assertEquals(2, tokens.get(count).getRowNumber()); 124 | 125 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 126 | assertEquals("elif", tokens.get(count).getValue()); 127 | assertEquals(3, tokens.get(count).getRowNumber()); 128 | 129 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 130 | assertEquals("a", tokens.get(count).getValue()); 131 | assertEquals(3, tokens.get(count).getRowNumber()); 132 | 133 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 134 | assertEquals(">=", tokens.get(count).getValue()); 135 | assertEquals(3, tokens.get(count).getRowNumber()); 136 | 137 | assertEquals(TokenType.Numeric, tokens.get(++count).getType()); 138 | assertEquals("1", tokens.get(count).getValue()); 139 | assertEquals(3, tokens.get(count).getRowNumber()); 140 | 141 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 142 | assertEquals("\n", tokens.get(count).getValue()); 143 | assertEquals(3, tokens.get(count).getRowNumber()); 144 | 145 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 146 | assertEquals("print", tokens.get(count).getValue()); 147 | assertEquals(4, tokens.get(count).getRowNumber()); 148 | 149 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 150 | assertEquals("a is greater than or equal to 1", tokens.get(count).getValue()); 151 | assertEquals(4, tokens.get(count).getRowNumber()); 152 | 153 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 154 | assertEquals("\n", tokens.get(count).getValue()); 155 | assertEquals(4, tokens.get(count).getRowNumber()); 156 | 157 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 158 | assertEquals("else", tokens.get(count).getValue()); 159 | assertEquals(5, tokens.get(count).getRowNumber()); 160 | 161 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 162 | assertEquals("\n", tokens.get(count).getValue()); 163 | assertEquals(5, tokens.get(count).getRowNumber()); 164 | 165 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 166 | assertEquals("print", tokens.get(count).getValue()); 167 | assertEquals(6, tokens.get(count).getRowNumber()); 168 | 169 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 170 | assertEquals("a is less than 1", tokens.get(count).getValue()); 171 | assertEquals(6, tokens.get(count).getRowNumber()); 172 | 173 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 174 | assertEquals("\n", tokens.get(count).getValue()); 175 | assertEquals(6, tokens.get(count).getRowNumber()); 176 | 177 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 178 | assertEquals("end", tokens.get(count).getValue()); 179 | assertEquals(7, tokens.get(count).getRowNumber()); 180 | } 181 | 182 | @Test 183 | public void testClass() { 184 | 185 | String source = "class Person [ name, age ]\n" + 186 | "end\n" + 187 | "person = new Person[\"Randy Marsh\", 45]\n" + 188 | "print person :: name + \" is \" + person :: age + \" years old\""; 189 | List tokens = LexicalParser.parse(source); 190 | 191 | assertEquals(32, tokens.size()); 192 | 193 | int count = 0; 194 | assertEquals(TokenType.Keyword, tokens.get(count).getType()); 195 | assertEquals("class", tokens.get(count).getValue()); 196 | assertEquals(1, tokens.get(count).getRowNumber()); 197 | 198 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 199 | assertEquals("Person", tokens.get(count).getValue()); 200 | assertEquals(1, tokens.get(count).getRowNumber()); 201 | 202 | assertEquals(TokenType.GroupDivider, tokens.get(++count).getType()); 203 | assertEquals("[", tokens.get(count).getValue()); 204 | assertEquals(1, tokens.get(count).getRowNumber()); 205 | 206 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 207 | assertEquals("name", tokens.get(count).getValue()); 208 | assertEquals(1, tokens.get(count).getRowNumber()); 209 | 210 | assertEquals(TokenType.GroupDivider, tokens.get(++count).getType()); 211 | assertEquals(",", tokens.get(count).getValue()); 212 | assertEquals(1, tokens.get(count).getRowNumber()); 213 | 214 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 215 | assertEquals("age", tokens.get(count).getValue()); 216 | assertEquals(1, tokens.get(count).getRowNumber()); 217 | 218 | assertEquals(TokenType.GroupDivider, tokens.get(++count).getType()); 219 | assertEquals("]", tokens.get(count).getValue()); 220 | assertEquals(1, tokens.get(count).getRowNumber()); 221 | 222 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 223 | assertEquals("\n", tokens.get(count).getValue()); 224 | assertEquals(1, tokens.get(count).getRowNumber()); 225 | 226 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 227 | assertEquals("end", tokens.get(count).getValue()); 228 | assertEquals(2, tokens.get(count).getRowNumber()); 229 | 230 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 231 | assertEquals("\n", tokens.get(count).getValue()); 232 | assertEquals(2, tokens.get(count).getRowNumber()); 233 | 234 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 235 | assertEquals("person", tokens.get(count).getValue()); 236 | assertEquals(3, tokens.get(count).getRowNumber()); 237 | 238 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 239 | assertEquals("=", tokens.get(count).getValue()); 240 | assertEquals(3, tokens.get(count).getRowNumber()); 241 | 242 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 243 | assertEquals("new", tokens.get(count).getValue()); 244 | assertEquals(3, tokens.get(count).getRowNumber()); 245 | 246 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 247 | assertEquals("Person", tokens.get(count).getValue()); 248 | assertEquals(3, tokens.get(count).getRowNumber()); 249 | 250 | assertEquals(TokenType.GroupDivider, tokens.get(++count).getType()); 251 | assertEquals("[", tokens.get(count).getValue()); 252 | assertEquals(3, tokens.get(count).getRowNumber()); 253 | 254 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 255 | assertEquals("Randy Marsh", tokens.get(count).getValue()); 256 | assertEquals(3, tokens.get(count).getRowNumber()); 257 | 258 | assertEquals(TokenType.GroupDivider, tokens.get(++count).getType()); 259 | assertEquals(",", tokens.get(count).getValue()); 260 | assertEquals(3, tokens.get(count).getRowNumber()); 261 | 262 | assertEquals(TokenType.Numeric, tokens.get(++count).getType()); 263 | assertEquals("45", tokens.get(count).getValue()); 264 | assertEquals(3, tokens.get(count).getRowNumber()); 265 | 266 | assertEquals(TokenType.GroupDivider, tokens.get(++count).getType()); 267 | assertEquals("]", tokens.get(count).getValue()); 268 | assertEquals(3, tokens.get(count).getRowNumber()); 269 | 270 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 271 | assertEquals("\n", tokens.get(count).getValue()); 272 | assertEquals(3, tokens.get(count).getRowNumber()); 273 | 274 | assertEquals(TokenType.Keyword, tokens.get(++count).getType()); 275 | assertEquals("print", tokens.get(count).getValue()); 276 | assertEquals(4, tokens.get(count).getRowNumber()); 277 | 278 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 279 | assertEquals("person", tokens.get(count).getValue()); 280 | assertEquals(4, tokens.get(count).getRowNumber()); 281 | 282 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 283 | assertEquals("::", tokens.get(count).getValue()); 284 | assertEquals(4, tokens.get(count).getRowNumber()); 285 | 286 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 287 | assertEquals("name", tokens.get(count).getValue()); 288 | assertEquals(4, tokens.get(count).getRowNumber()); 289 | 290 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 291 | assertEquals("+", tokens.get(count).getValue()); 292 | assertEquals(4, tokens.get(count).getRowNumber()); 293 | 294 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 295 | assertEquals(" is ", tokens.get(count).getValue()); 296 | assertEquals(4, tokens.get(count).getRowNumber()); 297 | 298 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 299 | assertEquals("+", tokens.get(count).getValue()); 300 | assertEquals(4, tokens.get(count).getRowNumber()); 301 | 302 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 303 | assertEquals("person", tokens.get(count).getValue()); 304 | assertEquals(4, tokens.get(count).getRowNumber()); 305 | 306 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 307 | assertEquals("::", tokens.get(count).getValue()); 308 | assertEquals(4, tokens.get(count).getRowNumber()); 309 | 310 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 311 | assertEquals("age", tokens.get(count).getValue()); 312 | assertEquals(4, tokens.get(count).getRowNumber()); 313 | 314 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 315 | assertEquals("+", tokens.get(count).getValue()); 316 | assertEquals(4, tokens.get(count).getRowNumber()); 317 | 318 | assertEquals(TokenType.Text, tokens.get(++count).getType()); 319 | assertEquals(" years old", tokens.get(count).getValue()); 320 | assertEquals(4, tokens.get(count).getRowNumber()); 321 | } 322 | 323 | @Test 324 | public void testComment() { 325 | String source = "# a = 5\n" + 326 | "a = 5 # a is equal to 5"; 327 | List tokens = LexicalParser.parse(source); 328 | 329 | assertEquals(6, tokens.size()); 330 | 331 | int count = 0; 332 | assertEquals(TokenType.Comment, tokens.get(count).getType()); 333 | assertEquals("# a = 5", tokens.get(count).getValue()); 334 | assertEquals(1, tokens.get(count).getRowNumber()); 335 | 336 | assertEquals(TokenType.LineBreak, tokens.get(++count).getType()); 337 | assertEquals("\n", tokens.get(count).getValue()); 338 | assertEquals(1, tokens.get(count).getRowNumber()); 339 | 340 | assertEquals(TokenType.Variable, tokens.get(++count).getType()); 341 | assertEquals("a", tokens.get(count).getValue()); 342 | assertEquals(2, tokens.get(count).getRowNumber()); 343 | 344 | assertEquals(TokenType.Operator, tokens.get(++count).getType()); 345 | assertEquals("=", tokens.get(count).getValue()); 346 | assertEquals(2, tokens.get(count).getRowNumber()); 347 | 348 | assertEquals(TokenType.Numeric, tokens.get(++count).getType()); 349 | assertEquals("5", tokens.get(count).getValue()); 350 | assertEquals(2, tokens.get(count).getRowNumber()); 351 | 352 | assertEquals(TokenType.Comment, tokens.get(++count).getType()); 353 | assertEquals("# a is equal to 5", tokens.get(count).getValue()); 354 | assertEquals(2, tokens.get(count).getRowNumber()); 355 | } 356 | 357 | } -------------------------------------------------------------------------------- /src/test/java/org/example/toylanguage/StatementParserTest.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import org.example.toylanguage.context.MemoryContext; 4 | import org.example.toylanguage.context.definition.DefinitionContext; 5 | import org.example.toylanguage.expression.ClassExpression; 6 | import org.example.toylanguage.expression.Expression; 7 | import org.example.toylanguage.expression.VariableExpression; 8 | import org.example.toylanguage.expression.operator.*; 9 | import org.example.toylanguage.expression.value.LogicalValue; 10 | import org.example.toylanguage.expression.value.NumericValue; 11 | import org.example.toylanguage.expression.value.TextValue; 12 | import org.example.toylanguage.statement.*; 13 | import org.example.toylanguage.token.Token; 14 | import org.example.toylanguage.token.TokenType; 15 | import org.junit.jupiter.api.Test; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | import static org.junit.jupiter.api.Assertions.assertEquals; 22 | import static org.junit.jupiter.api.Assertions.assertTrue; 23 | 24 | class StatementParserTest { 25 | 26 | @Test 27 | public void printTest() { 28 | List tokens = List.of( 29 | Token.builder().type(TokenType.Keyword).value("print").build(), 30 | Token.builder().type(TokenType.Text).value("Hello World").build() 31 | ); 32 | DefinitionContext.pushScope(DefinitionContext.newScope()); 33 | MemoryContext.pushScope(MemoryContext.newScope()); 34 | CompositeStatement statement = new CompositeStatement(null, "printTest"); 35 | StatementParser.parse(tokens, statement); 36 | 37 | List statements = statement.getStatements2Execute(); 38 | assertEquals(1, statements.size()); 39 | 40 | assertEquals(PrintStatement.class, statements.get(0).getClass()); 41 | PrintStatement printStatement = (PrintStatement) statements.get(0); 42 | 43 | assertEquals(TextValue.class, printStatement.getExpression().getClass()); 44 | TextValue textValue = (TextValue) printStatement.getExpression(); 45 | 46 | assertEquals("Hello World", textValue.getValue()); 47 | 48 | DefinitionContext.endScope(); 49 | MemoryContext.endScope(); 50 | } 51 | 52 | @Test 53 | public void testInput() { 54 | List tokens = List.of( 55 | Token.builder().type(TokenType.Keyword).value("input").build(), 56 | Token.builder().type(TokenType.Variable).value("a").build() 57 | ); 58 | DefinitionContext.pushScope(DefinitionContext.newScope()); 59 | MemoryContext.pushScope(MemoryContext.newScope()); 60 | CompositeStatement statement = new CompositeStatement(null, "testInput"); 61 | StatementParser.parse(tokens, statement); 62 | 63 | List statements = statement.getStatements2Execute(); 64 | assertEquals(1, statements.size()); 65 | 66 | assertEquals(InputStatement.class, statements.get(0).getClass()); 67 | InputStatement inputStatement = (InputStatement) statements.get(0); 68 | 69 | assertEquals("a", inputStatement.getName()); 70 | 71 | DefinitionContext.endScope(); 72 | MemoryContext.endScope(); 73 | } 74 | 75 | @Test 76 | public void testAssignment() { 77 | List tokens = List.of( 78 | Token.builder().type(TokenType.Variable).value("a").build(), 79 | Token.builder().type(TokenType.Operator).value("=").build(), 80 | Token.builder().type(TokenType.Numeric).value("2").build(), 81 | Token.builder().type(TokenType.Operator).value("+").build(), 82 | Token.builder().type(TokenType.Numeric).value("5").build() 83 | ); 84 | DefinitionContext.pushScope(DefinitionContext.newScope()); 85 | MemoryContext.pushScope(MemoryContext.newScope()); 86 | CompositeStatement statement = new CompositeStatement(null, "testAssignment"); 87 | StatementParser.parse(tokens, statement); 88 | 89 | List statements = statement.getStatements2Execute(); 90 | assertEquals(1, statements.size()); 91 | 92 | assertEquals(ExpressionStatement.class, statements.get(0).getClass()); 93 | ExpressionStatement expressionStatement = (ExpressionStatement) statements.get(0); 94 | 95 | assertEquals(expressionStatement.getExpression().getClass(), AssignmentOperator.class); 96 | AssignmentOperator assignOperator = (AssignmentOperator) expressionStatement.getExpression(); 97 | 98 | assertTrue(assignOperator.getLeft() instanceof VariableExpression); 99 | VariableExpression variableExpression = (VariableExpression) assignOperator.getLeft(); 100 | assertEquals("a", variableExpression.getName()); 101 | 102 | assertEquals(AdditionOperator.class, assignOperator.getRight().getClass()); 103 | AdditionOperator operator = (AdditionOperator) assignOperator.getRight(); 104 | 105 | assertEquals(NumericValue.class, operator.getLeft().getClass()); 106 | NumericValue left = (NumericValue) operator.getLeft(); 107 | assertEquals(2, left.getValue()); 108 | 109 | assertEquals(NumericValue.class, operator.getRight().getClass()); 110 | NumericValue right = (NumericValue) operator.getRight(); 111 | assertEquals(5, right.getValue()); 112 | 113 | DefinitionContext.endScope(); 114 | MemoryContext.endScope(); 115 | } 116 | 117 | @Test 118 | public void testCondition() { 119 | List tokens = List.of( 120 | Token.builder().type(TokenType.Keyword).value("if").build(), 121 | Token.builder().type(TokenType.Variable).value("a").build(), 122 | Token.builder().type(TokenType.Operator).value(">").build(), 123 | Token.builder().type(TokenType.Numeric).value("5").build(), 124 | Token.builder().type(TokenType.Keyword).value("print").build(), 125 | Token.builder().type(TokenType.Text).value("a is greater than 5").build(), 126 | Token.builder().type(TokenType.Keyword).value("elif").build(), 127 | Token.builder().type(TokenType.Variable).value("a").build(), 128 | Token.builder().type(TokenType.Operator).value(">=").build(), 129 | Token.builder().type(TokenType.Numeric).value("1").build(), 130 | Token.builder().type(TokenType.Keyword).value("print").build(), 131 | Token.builder().type(TokenType.Text).value("a is greater than or equal to 1").build(), 132 | Token.builder().type(TokenType.Keyword).value("else").build(), 133 | Token.builder().type(TokenType.Keyword).value("print").build(), 134 | Token.builder().type(TokenType.Text).value("a is less than 1").build(), 135 | Token.builder().type(TokenType.Keyword).value("end").build() 136 | ); 137 | DefinitionContext.pushScope(DefinitionContext.newScope()); 138 | MemoryContext.pushScope(MemoryContext.newScope()); 139 | CompositeStatement statement = new CompositeStatement(null, "testCondition"); 140 | StatementParser.parse(tokens, statement); 141 | 142 | List statements = statement.getStatements2Execute(); 143 | assertEquals(1, statements.size()); 144 | 145 | assertEquals(ConditionStatement.class, statements.get(0).getClass()); 146 | ConditionStatement conditionStatement = (ConditionStatement) statements.get(0); 147 | 148 | Map cases = conditionStatement.getCases(); 149 | assertEquals(3, cases.size()); 150 | 151 | List conditions = new ArrayList<>(cases.keySet()); 152 | 153 | //if case 154 | assertEquals(GreaterThanOperator.class, conditions.get(0).getClass()); 155 | GreaterThanOperator ifCondition = (GreaterThanOperator) conditions.get(0); 156 | 157 | assertTrue(ifCondition.getLeft() instanceof VariableExpression); 158 | VariableExpression ifLeftExpression = (VariableExpression) ifCondition.getLeft(); 159 | assertEquals("a", ifLeftExpression.getName()); 160 | 161 | NumericValue ifRightExpression = (NumericValue) ifCondition.getRight(); 162 | assertEquals(5, ifRightExpression.getValue()); 163 | 164 | List ifStatements = cases.get(ifCondition).getStatements2Execute(); 165 | assertEquals(1, ifStatements.size()); 166 | 167 | assertEquals(PrintStatement.class, ifStatements.get(0).getClass()); 168 | PrintStatement ifPrintStatement = (PrintStatement) ifStatements.get(0); 169 | 170 | assertEquals(TextValue.class, ifPrintStatement.getExpression().getClass()); 171 | TextValue ifPrintValue = (TextValue) ifPrintStatement.getExpression(); 172 | assertEquals("a is greater than 5", ifPrintValue.getValue()); 173 | 174 | //elif case 175 | assertEquals(GreaterThanOrEqualToOperator.class, conditions.get(1).getClass()); 176 | GreaterThanOrEqualToOperator elifCondition = (GreaterThanOrEqualToOperator) conditions.get(1); 177 | 178 | assertTrue(elifCondition.getLeft() instanceof VariableExpression); 179 | VariableExpression elifLeftExpression = (VariableExpression) elifCondition.getLeft(); 180 | assertEquals("a", elifLeftExpression.getName()); 181 | 182 | NumericValue elifRightExpression = (NumericValue) elifCondition.getRight(); 183 | assertEquals(1, elifRightExpression.getValue()); 184 | 185 | List elifStatements = cases.get(elifCondition).getStatements2Execute(); 186 | assertEquals(1, elifStatements.size()); 187 | 188 | assertEquals(PrintStatement.class, elifStatements.get(0).getClass()); 189 | PrintStatement elifPrintStatement = (PrintStatement) elifStatements.get(0); 190 | 191 | assertEquals(TextValue.class, elifPrintStatement.getExpression().getClass()); 192 | TextValue elifPrintValue = (TextValue) elifPrintStatement.getExpression(); 193 | assertEquals("a is greater than or equal to 1", elifPrintValue.getValue()); 194 | 195 | //else case 196 | assertEquals(LogicalValue.class, conditions.get(2).getClass()); 197 | LogicalValue elseCondition = (LogicalValue) conditions.get(2); 198 | 199 | assertEquals(true, elseCondition.getValue()); 200 | 201 | List elseStatements = cases.get(elseCondition).getStatements2Execute(); 202 | assertEquals(1, elseStatements.size()); 203 | 204 | assertEquals(PrintStatement.class, elseStatements.get(0).getClass()); 205 | PrintStatement elsePrintStatement = (PrintStatement) elseStatements.get(0); 206 | 207 | assertEquals(TextValue.class, elsePrintStatement.getExpression().getClass()); 208 | TextValue elsePrintValue = (TextValue) elsePrintStatement.getExpression(); 209 | assertEquals("a is less than 1", elsePrintValue.getValue()); 210 | 211 | DefinitionContext.endScope(); 212 | MemoryContext.endScope(); 213 | } 214 | 215 | @Test 216 | public void testClass() { 217 | List tokens = List.of( 218 | Token.builder().type(TokenType.Keyword).value("class").rowNumber(1).build(), 219 | Token.builder().type(TokenType.Variable).value("Person").rowNumber(1).build(), 220 | Token.builder().type(TokenType.GroupDivider).value("[").rowNumber(1).build(), 221 | Token.builder().type(TokenType.Variable).value("name").rowNumber(1).build(), 222 | Token.builder().type(TokenType.GroupDivider).value(",").rowNumber(1).build(), 223 | Token.builder().type(TokenType.Variable).value("age").rowNumber(1).build(), 224 | Token.builder().type(TokenType.GroupDivider).value("]").rowNumber(1).build(), 225 | Token.builder().type(TokenType.LineBreak).value("\n").rowNumber(1).build(), 226 | Token.builder().type(TokenType.Keyword).value("end").rowNumber(2).build(), 227 | Token.builder().type(TokenType.LineBreak).value("\n").rowNumber(2).build(), 228 | Token.builder().type(TokenType.Variable).value("person").rowNumber(3).build(), 229 | Token.builder().type(TokenType.Operator).value("=").rowNumber(3).build(), 230 | Token.builder().type(TokenType.Operator).value("new").rowNumber(3).build(), 231 | Token.builder().type(TokenType.Variable).value("Person").rowNumber(3).build(), 232 | Token.builder().type(TokenType.GroupDivider).value("[").rowNumber(3).build(), 233 | Token.builder().type(TokenType.Text).value("Randy Marsh").rowNumber(3).build(), 234 | Token.builder().type(TokenType.GroupDivider).value(",").rowNumber(3).build(), 235 | Token.builder().type(TokenType.Numeric).value("45").rowNumber(3).build(), 236 | Token.builder().type(TokenType.GroupDivider).value("]").rowNumber(3).build(), 237 | Token.builder().type(TokenType.LineBreak).value("\n").rowNumber(3).build(), 238 | Token.builder().type(TokenType.Keyword).value("print").rowNumber(4).build(), 239 | Token.builder().type(TokenType.Variable).value("person").rowNumber(4).build(), 240 | Token.builder().type(TokenType.Operator).value("::").rowNumber(4).build(), 241 | Token.builder().type(TokenType.Variable).value("name").rowNumber(4).build(), 242 | Token.builder().type(TokenType.Operator).value("+").rowNumber(4).build(), 243 | Token.builder().type(TokenType.Text).value(" is ").rowNumber(4).build(), 244 | Token.builder().type(TokenType.Operator).value("+").rowNumber(4).build(), 245 | Token.builder().type(TokenType.Variable).value("person").rowNumber(4).build(), 246 | Token.builder().type(TokenType.Operator).value("::").rowNumber(4).build(), 247 | Token.builder().type(TokenType.Variable).value("age").rowNumber(4).build(), 248 | Token.builder().type(TokenType.Operator).value("+").rowNumber(4).build(), 249 | Token.builder().type(TokenType.Text).value(" years old").rowNumber(4).build() 250 | ); 251 | DefinitionContext.pushScope(DefinitionContext.newScope()); 252 | MemoryContext.pushScope(MemoryContext.newScope()); 253 | CompositeStatement statement = new CompositeStatement(null, "testClass"); 254 | StatementParser.parse(tokens, statement); 255 | 256 | List statements = statement.getStatements2Execute(); 257 | assertEquals(2, statements.size()); 258 | 259 | // 1st statement 260 | assertEquals(ExpressionStatement.class, statements.get(0).getClass()); 261 | ExpressionStatement expressionStatement = (ExpressionStatement) statements.get(0); 262 | 263 | assertEquals(expressionStatement.getExpression().getClass(), AssignmentOperator.class); 264 | AssignmentOperator assignStatement = (AssignmentOperator) expressionStatement.getExpression(); 265 | 266 | assertTrue(assignStatement.getLeft() instanceof VariableExpression); 267 | VariableExpression variableExpression = (VariableExpression) assignStatement.getLeft(); 268 | assertEquals("person", variableExpression.getName()); 269 | 270 | assertEquals(ClassInstanceOperator.class, assignStatement.getRight().getClass()); 271 | ClassInstanceOperator instanceOperator = (ClassInstanceOperator) assignStatement.getRight(); 272 | 273 | assertEquals(ClassExpression.class, instanceOperator.getValue().getClass()); 274 | ClassExpression type = (ClassExpression) instanceOperator.getValue(); 275 | 276 | assertEquals("Person", type.getName()); 277 | assertEquals("Randy Marsh", type.getPropertiesExpressions().get(0).toString()); 278 | assertEquals("45", type.getPropertiesExpressions().get(1).toString()); 279 | 280 | // 2nd statement 281 | PrintStatement printStatement = (PrintStatement) statements.get(1); 282 | assertEquals(AdditionOperator.class, printStatement.getExpression().getClass()); 283 | 284 | DefinitionContext.endScope(); 285 | MemoryContext.endScope(); 286 | } 287 | 288 | @Test 289 | public void testComment() { 290 | List tokens = List.of( 291 | Token.builder().type(TokenType.Comment).value("# a = 5").build(), 292 | Token.builder().type(TokenType.LineBreak).value("\n").build(), 293 | Token.builder().type(TokenType.Variable).value("a").build(), 294 | Token.builder().type(TokenType.Operator).value("=").build(), 295 | Token.builder().type(TokenType.Numeric).value("5").build(), 296 | Token.builder().type(TokenType.Comment).value("# a is equal to 5").build() 297 | ); 298 | DefinitionContext.pushScope(DefinitionContext.newScope()); 299 | MemoryContext.pushScope(MemoryContext.newScope()); 300 | CompositeStatement statement = new CompositeStatement(null, "testComment"); 301 | StatementParser.parse(tokens, statement); 302 | 303 | List statements = statement.getStatements2Execute(); 304 | assertEquals(1, statements.size()); 305 | 306 | assertEquals(ExpressionStatement.class, statements.get(0).getClass()); 307 | ExpressionStatement expressionStatement = (ExpressionStatement) statements.get(0); 308 | 309 | assertEquals(expressionStatement.getExpression().getClass(), AssignmentOperator.class); 310 | AssignmentOperator assignStatement = (AssignmentOperator) expressionStatement.getExpression(); 311 | 312 | assertTrue(assignStatement.getLeft() instanceof VariableExpression); 313 | VariableExpression variableExpression = (VariableExpression) assignStatement.getLeft(); 314 | assertEquals("a", variableExpression.getName()); 315 | assertEquals(NumericValue.class, assignStatement.getRight().getClass()); 316 | NumericValue numericValue = (NumericValue) assignStatement.getRight(); 317 | 318 | assertEquals(5, numericValue.getValue()); 319 | 320 | DefinitionContext.endScope(); 321 | MemoryContext.endScope(); 322 | } 323 | 324 | } -------------------------------------------------------------------------------- /src/test/java/org/example/toylanguage/ToyLanguageTest.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.*; 6 | import java.net.URISyntaxException; 7 | import java.net.URL; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | 13 | class ToyLanguageTest { 14 | 15 | @Test 16 | void fibonacciNumber() throws URISyntaxException, IOException { 17 | URL resource = getClass().getClassLoader().getResource("fibonacci_number.toy"); 18 | Path path = Paths.get(resource.toURI()); 19 | 20 | try (InputStream in = new ByteArrayInputStream("11".getBytes()); 21 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 22 | PrintStream out = new PrintStream(baos)) { 23 | 24 | System.setIn(in); 25 | System.setOut(out); 26 | System.setErr(out); 27 | 28 | ToyLanguage lang = new ToyLanguage(); 29 | lang.execute(path); 30 | 31 | assertEquals( 32 | "enter \"index number\" >>> " + 33 | "fibonacci number is 89\n", 34 | baos.toString() 35 | ); 36 | } 37 | } 38 | 39 | @Test 40 | void isSameTree() throws URISyntaxException, IOException { 41 | URL resource = getClass().getClassLoader().getResource("is_same_tree.toy"); 42 | Path path = Paths.get(resource.toURI()); 43 | 44 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 45 | PrintStream out = new PrintStream(baos)) { 46 | 47 | System.setOut(out); 48 | System.setErr(out); 49 | 50 | ToyLanguage lang = new ToyLanguage(); 51 | lang.execute(path); 52 | 53 | assertEquals("", baos.toString()); 54 | } 55 | } 56 | 57 | @Test 58 | void binarySearch() throws URISyntaxException, IOException { 59 | URL resource = getClass().getClassLoader().getResource("binary_search.toy"); 60 | Path path = Paths.get(resource.toURI()); 61 | 62 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 63 | PrintStream out = new PrintStream(baos)) { 64 | 65 | System.setOut(out); 66 | System.setErr(out); 67 | 68 | ToyLanguage lang = new ToyLanguage(); 69 | lang.execute(path); 70 | 71 | assertEquals("", baos.toString()); 72 | } 73 | } 74 | 75 | @Test 76 | void bubbleSort() throws URISyntaxException, IOException { 77 | URL resource = getClass().getClassLoader().getResource("bubble_sort.toy"); 78 | Path path = Paths.get(resource.toURI()); 79 | 80 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 81 | PrintStream out = new PrintStream(baos)) { 82 | 83 | System.setOut(out); 84 | System.setErr(out); 85 | 86 | ToyLanguage lang = new ToyLanguage(); 87 | lang.execute(path); 88 | 89 | assertEquals("", baos.toString()); 90 | } 91 | } 92 | 93 | @Test 94 | void stack() throws URISyntaxException, IOException { 95 | URL resource = getClass().getClassLoader().getResource("stack.toy"); 96 | Path path = Paths.get(resource.toURI()); 97 | 98 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 99 | PrintStream out = new PrintStream(baos)) { 100 | 101 | System.setOut(out); 102 | System.setErr(out); 103 | 104 | ToyLanguage lang = new ToyLanguage(); 105 | lang.execute(path); 106 | 107 | assertEquals("", baos.toString()); 108 | } 109 | } 110 | 111 | @Test 112 | void instanceOf() throws URISyntaxException, IOException { 113 | URL resource = getClass().getClassLoader().getResource("instance_of.toy"); 114 | Path path = Paths.get(resource.toURI()); 115 | 116 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 117 | PrintStream out = new PrintStream(baos)) { 118 | 119 | System.setOut(out); 120 | System.setErr(out); 121 | 122 | ToyLanguage lang = new ToyLanguage(); 123 | lang.execute(path); 124 | 125 | assertEquals("", baos.toString()); 126 | } 127 | } 128 | 129 | @Test 130 | void castType() throws URISyntaxException, IOException { 131 | URL resource = getClass().getClassLoader().getResource("cast_type.toy"); 132 | Path path = Paths.get(resource.toURI()); 133 | 134 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 135 | PrintStream out = new PrintStream(baos)) { 136 | 137 | System.setOut(out); 138 | System.setErr(out); 139 | 140 | ToyLanguage lang = new ToyLanguage(); 141 | lang.execute(path); 142 | 143 | assertEquals("", baos.toString()); 144 | } 145 | } 146 | 147 | @Test 148 | void calculator() throws URISyntaxException, IOException { 149 | URL resource = getClass().getClassLoader().getResource("calculator.toy"); 150 | Path path = Paths.get(resource.toURI()); 151 | 152 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 153 | PrintStream out = new PrintStream(baos)) { 154 | 155 | System.setOut(out); 156 | System.setErr(out); 157 | 158 | ToyLanguage lang = new ToyLanguage(); 159 | lang.execute(path); 160 | 161 | assertEquals("", baos.toString()); 162 | } 163 | } 164 | 165 | @Test 166 | void raiseException() throws URISyntaxException, IOException { 167 | URL resource = getClass().getClassLoader().getResource("raise_exception.toy"); 168 | Path path = Paths.get(resource.toURI()); 169 | 170 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 171 | PrintStream out = new PrintStream(baos)) { 172 | 173 | System.setOut(out); 174 | System.setErr(out); 175 | 176 | ToyLanguage lang = new ToyLanguage(); 177 | lang.execute(path); 178 | 179 | assertEquals("Do something useful ...\n" + 180 | "MyBusinessException [ message = A message that describes the error. ]\n" + 181 | " at do_something_else:14\n" + 182 | " at perform_business_operation:5\n" + 183 | " at raise_exception.toy:1\n", baos.toString()); 184 | } 185 | } 186 | 187 | @Test 188 | void handleException() throws URISyntaxException, IOException { 189 | URL resource = getClass().getClassLoader().getResource("handle_exception.toy"); 190 | Path path = Paths.get(resource.toURI()); 191 | 192 | try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); 193 | PrintStream out = new PrintStream(baos)) { 194 | 195 | System.setOut(out); 196 | System.setErr(out); 197 | 198 | ToyLanguage lang = new ToyLanguage(); 199 | lang.execute(path); 200 | 201 | assertEquals("Do something useful ...\n" + 202 | "Rescuing 'A message that describes the error.'\n" + 203 | "Ensure block\n", baos.toString()); 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /src/test/java/org/example/toylanguage/package-info.java: -------------------------------------------------------------------------------- 1 | package org.example.toylanguage; -------------------------------------------------------------------------------- /src/test/resources/binary_search.toy: -------------------------------------------------------------------------------- 1 | fun binary_search [ arr, n, lo, hi, key ] 2 | if hi >= lo 3 | mid = (hi + lo) // 2 4 | if arr{mid} < key 5 | return binary_search [ arr, n, mid + 1, hi, key ] 6 | elif arr{mid} > key 7 | return binary_search [ arr, n, lo, mid - 1, key ] 8 | else 9 | return mid 10 | end 11 | else 12 | return -1 13 | end 14 | return 15 | end 16 | 17 | arr = {10, 20, 30, 50, 60, 80, 110, 130, 140, 170} 18 | n = 10 19 | 20 | assert binary_search [ arr, n, 0, n - 1, 10 ] == 0 21 | assert binary_search [ arr, n, 0, n - 1, 80 ] == 5 22 | assert binary_search [ arr, n, 0, n - 1, 170 ] == 9 23 | assert binary_search [ arr, n, 0, n - 1, 5 ] == -1 24 | assert binary_search [ arr, n, 0, n - 1, 85 ] == -1 25 | assert binary_search [ arr, n, 0, n - 1, 175 ] == -1 -------------------------------------------------------------------------------- /src/test/resources/bubble_sort.toy: -------------------------------------------------------------------------------- 1 | fun bubble_sort [ arr, n ] 2 | loop i in 0..n - 1 3 | loop j in 0..n - i - 1 4 | if arr{j+1} < arr{j} 5 | temp = arr{j} 6 | arr{j} = arr{j + 1} 7 | arr{j + 1} = temp 8 | end 9 | end 10 | end 11 | end 12 | 13 | fun is_sorted [ arr, n ] 14 | loop i in 0..n - 1 15 | if arr{i} > arr{i+1} 16 | return false 17 | end 18 | end 19 | return true 20 | end 21 | 22 | arr = {} 23 | arr_len = 20 24 | loop i in 0..arr_len by 1 25 | arr << i * 117 % 17 - 1 26 | end 27 | 28 | assert arr == {-1, 14, 12, 10, 8, 6, 4, 2, 0, 15, 13, 11, 9, 7, 5, 3, 1, -1, 14, 12} 29 | assert ! is_sorted [ arr, arr_len ] 30 | 31 | bubble_sort [ arr, arr_len ] 32 | assert arr == {-1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 13, 14, 14, 15} 33 | assert is_sorted [ arr, arr_len ] -------------------------------------------------------------------------------- /src/test/resources/calculator.toy: -------------------------------------------------------------------------------- 1 | class Add [x, y] 2 | fun sum 3 | return x + y 4 | end 5 | end 6 | 7 | class Mul [a, b] 8 | fun mul 9 | return a * b 10 | end 11 | end 12 | 13 | class Sub [a, b] 14 | fun sub 15 | return a - b 16 | end 17 | end 18 | 19 | class Div [m, n] 20 | fun div 21 | return m / n 22 | end 23 | end 24 | 25 | class Exp [m, n] 26 | fun exp 27 | return m ** n 28 | end 29 | end 30 | 31 | class Fib [ n ] 32 | fun fib 33 | return fib [ n ] 34 | end 35 | 36 | fun fib [ n ] 37 | if n < 2 38 | return n 39 | end 40 | return fib [ n - 1 ] + fib [ n - 2 ] 41 | end 42 | end 43 | 44 | class Calculator [p, q]: Add [p, q], Sub [q, p], 45 | Mul [p, q], Div [q, p], 46 | Exp [p, q], Fib [ q ] 47 | end 48 | 49 | calc = new Calculator [2, 10] 50 | assert calc :: sum [] == 12 51 | assert calc :: sub [] == 8 52 | assert calc :: mul [] == 20 53 | assert calc :: div [] == 5 54 | assert calc :: exp [] == 1024 55 | assert calc :: fib [] == 55 -------------------------------------------------------------------------------- /src/test/resources/cast_type.toy: -------------------------------------------------------------------------------- 1 | class User [email] 2 | end 3 | 4 | class Person [name] 5 | end 6 | 7 | class Student [user_email, person_name]: User [user_email], Person [person_name] 8 | end 9 | 10 | student = new Student ["test@email"] 11 | 12 | # check email property 13 | assert student :: user_email == "test@email" 14 | assert student as User :: email == "test@email" 15 | 16 | # check name property 17 | assert student :: person_name == null 18 | assert student as Person :: name == null 19 | 20 | # set student's property used as a reference in user's property 21 | student :: person_name = "Randy" 22 | assert student :: person_name == "Randy" 23 | assert student as Person :: name == "Randy" 24 | 25 | # set person's property used as a reference in person's property 26 | student as Person :: name = "Stan" 27 | assert student :: person_name == "Stan" 28 | assert student as Person :: name == "Stan" -------------------------------------------------------------------------------- /src/test/resources/fibonacci_number.toy: -------------------------------------------------------------------------------- 1 | # 2 | # Define the Fibonacci number of the given element index 3 | # 4 | 5 | fun fibonacci_number [ n ] 6 | if n < 2 7 | return n 8 | end 9 | return fibonacci_number [ n - 1 ] + fibonacci_number [ n - 2 ] 10 | end 11 | 12 | input index_number 13 | print "fibonacci number is " + fibonacci_number [ index_number ] -------------------------------------------------------------------------------- /src/test/resources/handle_exception.toy: -------------------------------------------------------------------------------- 1 | begin 2 | perform_business_operation [] 3 | rescue err 4 | print "Rescuing '" + err :: message + "'" 5 | ensure 6 | print "Ensure block" 7 | end 8 | 9 | fun perform_business_operation [] 10 | do_something [] 11 | do_something_else [] 12 | do_even_more [] 13 | end 14 | 15 | fun do_something 16 | print "Do something useful ..." 17 | end 18 | 19 | fun do_something_else 20 | raise new MyBusinessException ["A message that describes the error."] 21 | print "Do something else ..." 22 | end 23 | 24 | fun do_even_more 25 | print "Do even more ..." 26 | end 27 | 28 | class MyBusinessException [message] 29 | end -------------------------------------------------------------------------------- /src/test/resources/instance_of.toy: -------------------------------------------------------------------------------- 1 | class Animal 2 | end 3 | 4 | class Mammal: Animal 5 | end 6 | 7 | class Cat: Mammal 8 | end 9 | 10 | class Reptile 11 | end 12 | 13 | class Lizard: Animal, Reptile 14 | end 15 | 16 | cat = new Cat 17 | assert cat is Cat 18 | assert cat is Mammal 19 | assert cat is Animal 20 | assert !(cat is Reptile) 21 | assert !(cat is Lizard) 22 | 23 | lizard = new Lizard 24 | assert lizard is Lizard 25 | assert lizard is Reptile 26 | assert lizard is Animal 27 | assert !(lizard is Mammal) 28 | assert !(lizard is Cat) -------------------------------------------------------------------------------- /src/test/resources/is_same_tree.toy: -------------------------------------------------------------------------------- 1 | # 2 | # Determine if two binary trees are identical following these rules: 3 | # - root nodes have the same value 4 | # - left sub trees are identical 5 | # - right sub trees are identical 6 | # 7 | 8 | class TreeNode [ value, left, right ] 9 | end 10 | 11 | fun is_same_tree [ node1, node2 ] 12 | if node1 == null and node2 == null 13 | return true 14 | end 15 | if node1 != null and node2 != null 16 | return node1 :: value == node2 :: value and is_same_tree [ node1 :: left, node2 :: left ] and is_same_tree [ node1 :: right, node2 :: right ] 17 | end 18 | return false 19 | end 20 | 21 | # tree-s example 22 | # 23 | # tree1 tree2 tree3 tree4 24 | # 3 3 3 3 25 | # / \ / \ / \ / \ 26 | # 4 5 4 5 4 5 4 5 27 | # / \ / \ / \ / \ / \ / \ / \ \ 28 | # 6 7 8 9 6 7 8 9 6 7 9 8 6 7 9 29 | 30 | # tree1 nodes 31 | tree1_node9 = new TreeNode [ 9, null, null ] 32 | tree1_node8 = new TreeNode [ 8, null ] 33 | tree1_node7 = new TreeNode [ 7 ] 34 | tree1_node6 = new TreeNode [ 6 ] 35 | tree1_node5 = new TreeNode [ 5, tree1_node8, tree1_node9 ] 36 | tree1_node4 = new TreeNode [ 4, tree1_node6, tree1_node7 ] 37 | tree1 = new TreeNode [ 3, tree1_node4, tree1_node5 ] 38 | 39 | tree2 = new TreeNode [ 3, new TreeNode [ 4, new TreeNode [ 6 ], new TreeNode [ 7 ] ], new TreeNode [ 5, new TreeNode [ 8 ], new TreeNode [ 9 ] ] ] 40 | tree3 = new TreeNode [ 3, new TreeNode [ 4, new TreeNode [ 6 ], new TreeNode [ 7 ] ], new TreeNode [ 5, new TreeNode [ 9 ], new TreeNode [ 8 ] ] ] 41 | tree4 = new TreeNode [ 3, new TreeNode [ 4, new TreeNode [ 6 ], new TreeNode [ 7 ] ], new TreeNode [ 5, null, new TreeNode [ 9 ] ] ] 42 | 43 | assert is_same_tree [ tree1, tree2 ] 44 | assert !is_same_tree [ tree1, tree3 ] 45 | assert !is_same_tree [ tree2, tree3 ] 46 | assert !is_same_tree [ tree1, tree4 ] 47 | assert !is_same_tree [ tree2, tree4 ] 48 | assert !is_same_tree [ tree3, tree4 ] -------------------------------------------------------------------------------- /src/test/resources/raise_exception.toy: -------------------------------------------------------------------------------- 1 | perform_business_operation [] 2 | 3 | fun perform_business_operation [] 4 | do_something [] 5 | do_something_else [] 6 | do_even_more [] 7 | end 8 | 9 | fun do_something 10 | print "Do something useful ..." 11 | end 12 | 13 | fun do_something_else 14 | raise new MyBusinessException ["A message that describes the error."] 15 | print "Do something else ..." 16 | end 17 | 18 | fun do_even_more 19 | print "Do even more ..." 20 | end 21 | 22 | class MyBusinessException [message] 23 | end -------------------------------------------------------------------------------- /src/test/resources/stack.toy: -------------------------------------------------------------------------------- 1 | main [] 2 | 3 | fun main [] 4 | stack = new Stack [] 5 | loop num in 0..5 6 | # push 0,1,2,3,4 7 | stack :: push [ num ] 8 | end 9 | 10 | assert stack :: arr == {0, 1, 2, 3, 4} 11 | 12 | size = stack :: size [] 13 | assert size == 5 14 | 15 | loop i in 0..size 16 | # should return 4,3,2,1,0 17 | popped_value = stack :: pop [] 18 | assert popped_value == size - i - 1 19 | end 20 | end 21 | 22 | class Stack [] 23 | arr = {} 24 | n = 0 25 | 26 | fun push [ item ] 27 | this :: arr << item 28 | n = n + 1 29 | end 30 | 31 | fun pop [] 32 | n = n - 1 33 | item = arr { n } 34 | arr { n } = null 35 | return item 36 | end 37 | 38 | fun size [] 39 | return this :: n 40 | end 41 | end --------------------------------------------------------------------------------