From df943197e5ea4fba38bda4a1218425bffbcc4cb8 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 16:16:54 +0200 Subject: [PATCH 01/30] variable nesting --- .../lemms/Exceptions/LemmsRuntimeException.java | 5 +++++ .../java/com/lemms/SyntaxNode/VariableNode.java | 14 +++++--------- .../java/com/lemms/interpreter/Interpreter.java | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/lemms/Exceptions/LemmsRuntimeException.java b/src/main/java/com/lemms/Exceptions/LemmsRuntimeException.java index 085ebf0..de19a2d 100644 --- a/src/main/java/com/lemms/Exceptions/LemmsRuntimeException.java +++ b/src/main/java/com/lemms/Exceptions/LemmsRuntimeException.java @@ -10,6 +10,11 @@ public LemmsRuntimeException(Token token, String message) { this.token = token; } + public LemmsRuntimeException(String message) { + super(message); + token = null; + } + public Token getToken() { return token; } diff --git a/src/main/java/com/lemms/SyntaxNode/VariableNode.java b/src/main/java/com/lemms/SyntaxNode/VariableNode.java index 22712f3..342c3d6 100644 --- a/src/main/java/com/lemms/SyntaxNode/VariableNode.java +++ b/src/main/java/com/lemms/SyntaxNode/VariableNode.java @@ -6,19 +6,15 @@ public class VariableNode extends ExpressionNode{ public String name; - public Token token; //Line + public VariableNode child; - public VariableNode(Token identifierToken) { - if (identifierToken == null || identifierToken.getType() != TokenType.IDENTIFIER) { - throw new IllegalArgumentException("VariableNode muss mit einem IDENTIFIER-Token erstellt werden."); - } - - this.name = identifierToken.getValue(); - this.token = identifierToken; //Line + public VariableNode(String identifierName) { + this.name = identifierName; } - public VariableNode(String identifierName) { + public VariableNode(String identifierName, VariableNode child) { this.name = identifierName; + this.child = child; } @Override diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index b3759ae..e9659d8 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -106,7 +106,7 @@ public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { public Object visitVariableValue(VariableNode variableNode) { Object value = environment.get(variableNode.name); if (value == null) //undefined, optional dedicated isDefined if needed? - throw new LemmsRuntimeException(variableNode.token, "Undefined variable '" + variableNode.name + "'."); + throw new LemmsRuntimeException("Undefined variable '" + variableNode.name + "'."); else return value; } From d83f897a1af80180f838453f573c64e6aba667a3 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 16:18:48 +0200 Subject: [PATCH 02/30] fix compile error --- src/main/java/com/lemms/parser/ExpressionParser.java | 2 +- src/main/java/com/lemms/parser/Parser.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/lemms/parser/ExpressionParser.java b/src/main/java/com/lemms/parser/ExpressionParser.java index a340894..80d94ed 100644 --- a/src/main/java/com/lemms/parser/ExpressionParser.java +++ b/src/main/java/com/lemms/parser/ExpressionParser.java @@ -255,7 +255,7 @@ private ExpressionNode parseUnaryFactor() { // Just a variable consume(); logger.info(identifierToken + "\n----- IDENTIFIER NODE CREATED -----"); - return new VariableNode(identifierToken); + return new VariableNode(identifierToken.getValue()); } } diff --git a/src/main/java/com/lemms/parser/Parser.java b/src/main/java/com/lemms/parser/Parser.java index 41f5ff7..05dcdf3 100644 --- a/src/main/java/com/lemms/parser/Parser.java +++ b/src/main/java/com/lemms/parser/Parser.java @@ -93,10 +93,9 @@ private IfNode parseIfStatement() { private AssignmentNode parseAssignment() { Token identifier = previous(); - Token equalsToken = consume(TokenType.ASSIGNMENT, "Expected '=' after identifier."); ExpressionNode expr = parseExpression(); consume(TokenType.SEMICOLON, "Expected ';' after assignment."); - return new AssignmentNode(new VariableNode(identifier), expr); + return new AssignmentNode(new VariableNode(identifier.getValue()), expr); } private ExpressionNode parseExpression() { From 203f2ac34fd6d3840dc356cbeaacfbf420bedc33 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 16:32:57 +0200 Subject: [PATCH 03/30] lemms object initial --- .../lemms/interpreter/object/LemmsData.java | 5 ++++ .../lemms/interpreter/object/LemmsInt.java | 5 ++++ .../lemms/interpreter/object/LemmsObject.java | 24 +++++++++++++++++++ .../lemms/interpreter/object/LemmsString.java | 5 ++++ 4 files changed, 39 insertions(+) create mode 100644 src/main/java/com/lemms/interpreter/object/LemmsData.java create mode 100644 src/main/java/com/lemms/interpreter/object/LemmsInt.java create mode 100644 src/main/java/com/lemms/interpreter/object/LemmsObject.java create mode 100644 src/main/java/com/lemms/interpreter/object/LemmsString.java diff --git a/src/main/java/com/lemms/interpreter/object/LemmsData.java b/src/main/java/com/lemms/interpreter/object/LemmsData.java new file mode 100644 index 0000000..9f0db8c --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsData.java @@ -0,0 +1,5 @@ +package com.lemms.interpreter.object; + +public abstract class LemmsData { + +} diff --git a/src/main/java/com/lemms/interpreter/object/LemmsInt.java b/src/main/java/com/lemms/interpreter/object/LemmsInt.java new file mode 100644 index 0000000..4f7f908 --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsInt.java @@ -0,0 +1,5 @@ +package com.lemms.interpreter.object; + +public class LemmsInt { + public int value; +} diff --git a/src/main/java/com/lemms/interpreter/object/LemmsObject.java b/src/main/java/com/lemms/interpreter/object/LemmsObject.java new file mode 100644 index 0000000..fd817e3 --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsObject.java @@ -0,0 +1,24 @@ +package com.lemms.interpreter.object; + +import java.util.Map; + +import com.lemms.SyntaxNode.ClassDeclarationNode; + +public class LemmsObject extends LemmsData { + private final Map properties; + public final ClassDeclarationNode classDeclaration; + + public LemmsObject(ClassDeclarationNode classDeclaration, Map properties) { + this.classDeclaration = classDeclaration; + this.properties = properties; + } + + public LemmsData get(String name) { + return properties.get(name); + } + + public void set(String name, LemmsData value) { + properties.put(name, value); + } + +} diff --git a/src/main/java/com/lemms/interpreter/object/LemmsString.java b/src/main/java/com/lemms/interpreter/object/LemmsString.java new file mode 100644 index 0000000..a4a8c89 --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsString.java @@ -0,0 +1,5 @@ +package com.lemms.interpreter.object; + +public class LemmsString { + public String value; +} From fa31dd6b9430055d190b09a04fefdcd1d3486313 Mon Sep 17 00:00:00 2001 From: malu Date: Tue, 17 Jun 2025 16:51:57 +0200 Subject: [PATCH 04/30] added parseClassDeclaration to parser --- src/main/java/com/lemms/Lemms.java | 2 +- .../SyntaxNode/ClassDeclarationNode.java | 3 +- src/main/java/com/lemms/parser/Parser.java | 32 +++++++++++++++++++ .../resources/successTests/classTest.lemms | 10 ++++++ 4 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/successTests/classTest.lemms diff --git a/src/main/java/com/lemms/Lemms.java b/src/main/java/com/lemms/Lemms.java index 2caef96..f7e83bd 100644 --- a/src/main/java/com/lemms/Lemms.java +++ b/src/main/java/com/lemms/Lemms.java @@ -44,7 +44,7 @@ public class Lemms { public static void main(String[] args) { try { String sourcePath = "foo/bar/sourcePath.example (später mit command-line-args ersetzen)"; - sourcePath = "src/main/resources/successTests/functionWithReturnValue.lemms"; //String sourcePath = args[0]; + sourcePath = "src/main/resources/successTests/classTest.lemms"; //String sourcePath = args[0]; File sourceFile = new File(sourcePath); //Verknüpfung: Tokenizer + Parser + Interpreter diff --git a/src/main/java/com/lemms/SyntaxNode/ClassDeclarationNode.java b/src/main/java/com/lemms/SyntaxNode/ClassDeclarationNode.java index d4b9572..0db1a6b 100644 --- a/src/main/java/com/lemms/SyntaxNode/ClassDeclarationNode.java +++ b/src/main/java/com/lemms/SyntaxNode/ClassDeclarationNode.java @@ -11,9 +11,10 @@ public class ClassDeclarationNode extends StatementNode{ public String className; // list of the names of the local variables in this class public List localVariables; + public List localFunctions; public FlowSignal accept(StatementVisitor visitor) { visitor.visitClassDeclarationStatement(this); - return null; // Function declarations do not return a FlowSignal + return null; // Class declarations do not return a FlowSignal } } diff --git a/src/main/java/com/lemms/parser/Parser.java b/src/main/java/com/lemms/parser/Parser.java index 05dcdf3..b1f7a5f 100644 --- a/src/main/java/com/lemms/parser/Parser.java +++ b/src/main/java/com/lemms/parser/Parser.java @@ -51,6 +51,9 @@ private StatementNode parseStatement() throws LemmsParseError { if (match(FUNCTION)) { return parseFunctionDeclaration(); } + if (match(CLASS)) { + return parseClassDeclaration(); + } // ... other statement types ... throw new LemmsParseError(peek(),"Unexpected token: " + peek()); } @@ -142,6 +145,35 @@ private WhileNode parseWhileStatement() { return whileNode; } + private ClassDeclarationNode parseClassDeclaration(){ + + ClassDeclarationNode c = new ClassDeclarationNode(); + List vars = new ArrayList<>(); + List funcs = new ArrayList<>(); + + // read class name + c.className = consume(IDENTIFIER, "Expected className IDENTIFIER after class keyword").getValue(); + + // read class localVariables (localVariables have to be first and after them, only function declarations can exist) + consume(BRACES_OPEN, "Expected '{' after className IDENTIFIER"); + while (!check(BRACES_CLOSED) && !check(FUNCTION)){ + vars.add(consume(IDENTIFIER, "Expected IDENTIFIER after ';'").getValue()); + consume(SEMICOLON, "Expected ';' after IDENTIFIER"); + } + + // read class localFunctions + while (!check(BRACES_CLOSED)){ + consume(FUNCTION, "Expected function keyword or '}' after localVariables or previous function"); + funcs.add(parseFunctionDeclaration()); + } + + consume(BRACES_CLOSED, "Expected '}' class Body"); + + c.localVariables = vars; + c.localFunctions = funcs; + return c; + } + private FunctionDeclarationNode parseFunctionDeclaration() { FunctionDeclarationNode func = new FunctionDeclarationNode(); diff --git a/src/main/resources/successTests/classTest.lemms b/src/main/resources/successTests/classTest.lemms new file mode 100644 index 0000000..7a61aae --- /dev/null +++ b/src/main/resources/successTests/classTest.lemms @@ -0,0 +1,10 @@ +print("hallo"); + +class Human { + age; + name; + + function printHallo(){ + print("hallo"); + } +} \ No newline at end of file From 9b601b52056bf83a42f99896f55d95b1186725be Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 17:04:06 +0200 Subject: [PATCH 05/30] initial interpreter changes --- .../com/lemms/SyntaxNode/ExpressionNode.java | 3 +- .../lemms/SyntaxNode/FunctionCallNode.java | 3 +- .../com/lemms/SyntaxNode/LiteralNode.java | 3 +- .../com/lemms/SyntaxNode/OperatorNode.java | 3 +- .../com/lemms/SyntaxNode/VariableNode.java | 5 +- .../com/lemms/interpreter/Environment.java | 10 ++- .../com/lemms/interpreter/Interpreter.java | 73 +++++++++++++------ .../com/lemms/interpreter/ValueVisitor.java | 9 ++- .../lemms/interpreter/object/LemmsBool.java | 9 +++ .../lemms/interpreter/object/LemmsInt.java | 8 +- .../lemms/interpreter/object/LemmsString.java | 8 +- 11 files changed, 92 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/lemms/interpreter/object/LemmsBool.java diff --git a/src/main/java/com/lemms/SyntaxNode/ExpressionNode.java b/src/main/java/com/lemms/SyntaxNode/ExpressionNode.java index a75316c..6bac83a 100644 --- a/src/main/java/com/lemms/SyntaxNode/ExpressionNode.java +++ b/src/main/java/com/lemms/SyntaxNode/ExpressionNode.java @@ -1,9 +1,10 @@ package com.lemms.SyntaxNode; import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; public abstract class ExpressionNode extends Node { - public abstract Object accept(ValueVisitor visitor); + public abstract LemmsData accept(ValueVisitor visitor); } diff --git a/src/main/java/com/lemms/SyntaxNode/FunctionCallNode.java b/src/main/java/com/lemms/SyntaxNode/FunctionCallNode.java index 4dd0436..cd6a5c9 100644 --- a/src/main/java/com/lemms/SyntaxNode/FunctionCallNode.java +++ b/src/main/java/com/lemms/SyntaxNode/FunctionCallNode.java @@ -3,6 +3,7 @@ import java.util.List; import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; public class FunctionCallNode extends ExpressionNode { @@ -10,7 +11,7 @@ public class FunctionCallNode extends ExpressionNode { public List params; @Override - public Object accept(ValueVisitor visitor) { + public LemmsData accept(ValueVisitor visitor) { return visitor.visitFunctionCallValue(this); } diff --git a/src/main/java/com/lemms/SyntaxNode/LiteralNode.java b/src/main/java/com/lemms/SyntaxNode/LiteralNode.java index 37f8c9d..6e1092b 100644 --- a/src/main/java/com/lemms/SyntaxNode/LiteralNode.java +++ b/src/main/java/com/lemms/SyntaxNode/LiteralNode.java @@ -2,6 +2,7 @@ import com.lemms.Token; import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; public class LiteralNode extends ExpressionNode { public final Object value; @@ -19,7 +20,7 @@ public LiteralNode(boolean value) { } @Override - public Object accept(ValueVisitor visitor) { + public LemmsData accept(ValueVisitor visitor) { return visitor.visitLiteralValue(this); } } diff --git a/src/main/java/com/lemms/SyntaxNode/OperatorNode.java b/src/main/java/com/lemms/SyntaxNode/OperatorNode.java index 45f7b45..dffc41a 100644 --- a/src/main/java/com/lemms/SyntaxNode/OperatorNode.java +++ b/src/main/java/com/lemms/SyntaxNode/OperatorNode.java @@ -2,6 +2,7 @@ import com.lemms.Token; import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; public class OperatorNode extends ExpressionNode { @@ -17,7 +18,7 @@ public OperatorNode(ExpressionNode left, Token operator, ExpressionNode right) { } @Override - public Object accept(ValueVisitor visitor) { + public LemmsData accept(ValueVisitor visitor) { return visitor.visitOperatorValue(this); } } diff --git a/src/main/java/com/lemms/SyntaxNode/VariableNode.java b/src/main/java/com/lemms/SyntaxNode/VariableNode.java index 342c3d6..ef5b827 100644 --- a/src/main/java/com/lemms/SyntaxNode/VariableNode.java +++ b/src/main/java/com/lemms/SyntaxNode/VariableNode.java @@ -3,8 +3,9 @@ import com.lemms.Token; import com.lemms.TokenType; import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; -public class VariableNode extends ExpressionNode{ +public class VariableNode extends ExpressionNode { public String name; public VariableNode child; @@ -18,7 +19,7 @@ public VariableNode(String identifierName, VariableNode child) { } @Override - public Object accept(ValueVisitor visitor) { + public LemmsData accept(ValueVisitor visitor) { return visitor.visitVariableValue(this); } } diff --git a/src/main/java/com/lemms/interpreter/Environment.java b/src/main/java/com/lemms/interpreter/Environment.java index 3c1a933..7b142e8 100644 --- a/src/main/java/com/lemms/interpreter/Environment.java +++ b/src/main/java/com/lemms/interpreter/Environment.java @@ -3,8 +3,10 @@ import java.util.HashMap; import java.util.Map; +import com.lemms.interpreter.object.LemmsData; + public class Environment { - private final Map values = new HashMap<>(); + private final Map values = new HashMap<>(); public final Environment enclosing; // Default constructor for global scope @@ -17,7 +19,7 @@ public Environment(Environment enclosing) { this.enclosing = enclosing; } - public Map getValues() { + public Map getValues() { return values; } @@ -32,7 +34,7 @@ private Environment findVariableEnv(String name) { } } - public void assign(String name, Object value) { + public void assign(String name, LemmsData value) { Environment env = findVariableEnv(name); if (env == null) { @@ -42,7 +44,7 @@ public void assign(String name, Object value) { } } - public Object get(String name) { + public LemmsData get(String name) { if (values.containsKey(name)) { return values.get(name); } else if (enclosing != null) { diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index e9659d8..7536157 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -3,6 +3,8 @@ //für Exceptions import com.lemms.Exceptions.LemmsRuntimeException; +import static java.lang.Character.valueOf; + import java.util.HashMap; import java.util.List; import java.util.Map; @@ -10,7 +12,11 @@ import com.lemms.SyntaxNode.*; import com.lemms.api.NativeFunction; import com.lemms.interpreter.FlowSignal.SignalType; - +import com.lemms.interpreter.object.LemmsBool; +import com.lemms.interpreter.object.LemmsData; +import com.lemms.interpreter.object.LemmsInt; +import com.lemms.interpreter.object.LemmsObject; +import com.lemms.interpreter.object.LemmsString; import com.lemms.TokenType; import static com.lemms.TokenType.*; @@ -20,6 +26,22 @@ public class Interpreter implements StatementVisitor, ValueVisitor { public List program; private final Map nativeFunctions; + private static List numericOperators = List.of(TokenType.PLUS, + TokenType.MINUS, + TokenType.MULTIPLICATION, + TokenType.DIVISION, + TokenType.MODULO); + + private static List booleanOperators = List.of(TokenType.AND, + TokenType.OR, TokenType.NOT); + + private static List comparisonOperators = List.of(TokenType.EQ, + NEQ, + TokenType.GEQ, + TokenType.LEQ, + TokenType.GT, + TokenType.LT); + public Interpreter(List program) { this.program = program; nativeFunctions = new HashMap<>(); @@ -98,42 +120,45 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { Object value = assignmentNode.rightHandSide.accept(this); - environment.assign(assignmentNode.leftHandSide.name, value); + LemmsData dataValue = null; + if(value instanceof Integer) { + dataValue = new LemmsInt(((Integer)value)); + } + else if(value instanceof String) { + dataValue = new LemmsString(((String)value)); + } + else if(value instanceof LemmsObject) { + dataValue = (LemmsObject)value; + } + + environment.assign(assignmentNode.leftHandSide.name, dataValue); return FlowSignal.NORMAL; } @Override - public Object visitVariableValue(VariableNode variableNode) { - Object value = environment.get(variableNode.name); + public LemmsData visitVariableValue(VariableNode variableNode) { + LemmsData value = environment.get(variableNode.name); if (value == null) //undefined, optional dedicated isDefined if needed? throw new LemmsRuntimeException("Undefined variable '" + variableNode.name + "'."); else return value; } @Override - public Object visitLiteralValue(LiteralNode literalNode) { - - return literalNode.value; + public LemmsData visitLiteralValue(LiteralNode literalNode) { + if(literalNode.value instanceof Integer) { + return new LemmsInt((Integer) literalNode.value); + } else if(literalNode.value instanceof String) { + return new LemmsString((String) literalNode.value); + } else if(literalNode.value instanceof Boolean) { + return new LemmsBool((Boolean) literalNode.value); + } + throw new LemmsRuntimeException("Unknown literal type: " + literalNode.value.getClass().getSimpleName()); } - private static List numericOperators = List.of(TokenType.PLUS, - TokenType.MINUS, - TokenType.MULTIPLICATION, - TokenType.DIVISION, - TokenType.MODULO); - - private static List booleanOperators = List.of(TokenType.AND, - TokenType.OR, TokenType.NOT); - - private static List comparisonOperators = List.of(TokenType.EQ, - NEQ, - TokenType.GEQ, - TokenType.LEQ, - TokenType.GT, - TokenType.LT); - @Override - public Object visitOperatorValue(OperatorNode operatorNode) { + public LemmsData visitOperatorValue(OperatorNode operatorNode) { + + if (numericOperators.contains(operatorNode.operator.getType())) { return evaluateNumericOperator(operatorNode); } else if (booleanOperators.contains(operatorNode.operator.getType())) { diff --git a/src/main/java/com/lemms/interpreter/ValueVisitor.java b/src/main/java/com/lemms/interpreter/ValueVisitor.java index 192b953..dad225b 100644 --- a/src/main/java/com/lemms/interpreter/ValueVisitor.java +++ b/src/main/java/com/lemms/interpreter/ValueVisitor.java @@ -1,12 +1,13 @@ package com.lemms.interpreter; import com.lemms.SyntaxNode.*; +import com.lemms.interpreter.object.LemmsData; public interface ValueVisitor { - public Object visitVariableValue(VariableNode variableNode); - public Object visitLiteralValue(LiteralNode literalNode); - public Object visitOperatorValue(OperatorNode operatorNode); - public Object visitFunctionCallValue(FunctionCallNode functionNode); + public LemmsData visitVariableValue(VariableNode variableNode); + public LemmsData visitLiteralValue(LiteralNode literalNode); + public LemmsData visitOperatorValue(OperatorNode operatorNode); + public LemmsData visitFunctionCallValue(FunctionCallNode functionNode); } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsBool.java b/src/main/java/com/lemms/interpreter/object/LemmsBool.java new file mode 100644 index 0000000..34be41d --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsBool.java @@ -0,0 +1,9 @@ +package com.lemms.interpreter.object; + +public class LemmsBool extends LemmsData { + private boolean value; + + public LemmsBool(boolean value) { + this.value = value; + } +} diff --git a/src/main/java/com/lemms/interpreter/object/LemmsInt.java b/src/main/java/com/lemms/interpreter/object/LemmsInt.java index 4f7f908..d2bf47d 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsInt.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsInt.java @@ -1,5 +1,9 @@ package com.lemms.interpreter.object; -public class LemmsInt { - public int value; +public class LemmsInt extends LemmsData { + private final int value; + public LemmsInt(int value) { + this.value = value; + } + } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsString.java b/src/main/java/com/lemms/interpreter/object/LemmsString.java index a4a8c89..a0812a7 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsString.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsString.java @@ -1,5 +1,9 @@ package com.lemms.interpreter.object; -public class LemmsString { - public String value; +public class LemmsString extends LemmsData { + private String value; + + public LemmsString(String value) { + this.value = value; + } } From 96faa5af9d84b483420a518f38002eb616285535 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 20:29:26 +0200 Subject: [PATCH 06/30] new operator logic --- .../com/lemms/interpreter/FlowSignal.java | 9 +- .../com/lemms/interpreter/Interpreter.java | 153 +++++++++++------- .../lemms/interpreter/object/LemmsBool.java | 2 +- .../lemms/interpreter/object/LemmsInt.java | 2 +- .../lemms/interpreter/object/LemmsString.java | 2 +- 5 files changed, 104 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/lemms/interpreter/FlowSignal.java b/src/main/java/com/lemms/interpreter/FlowSignal.java index 894cfa2..593a7ca 100644 --- a/src/main/java/com/lemms/interpreter/FlowSignal.java +++ b/src/main/java/com/lemms/interpreter/FlowSignal.java @@ -1,7 +1,10 @@ package com.lemms.interpreter; + +import com.lemms.interpreter.object.LemmsData; + public class FlowSignal { public final SignalType signal; - public final Object value; // only meaningful when signal==RETURN + public final LemmsData value; // only meaningful when signal==RETURN public static FlowSignal NORMAL = new FlowSignal(SignalType.NORMAL, null); @@ -9,12 +12,12 @@ public enum SignalType { NORMAL, RETURN, BREAK, CONTINUE } - private FlowSignal(SignalType s, Object v) { + private FlowSignal(SignalType s, LemmsData v) { this.signal = s; this.value = v; } - public static FlowSignal returned(Object v) { + public static FlowSignal returned(LemmsData v) { return new FlowSignal(SignalType.RETURN, v); } diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 7536157..1db9643 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -26,7 +26,7 @@ public class Interpreter implements StatementVisitor, ValueVisitor { public List program; private final Map nativeFunctions; - private static List numericOperators = List.of(TokenType.PLUS, + private static List numericOperators = List.of(TokenType.PLUS, TokenType.MINUS, TokenType.MULTIPLICATION, TokenType.DIVISION, @@ -35,8 +35,7 @@ public class Interpreter implements StatementVisitor, ValueVisitor { private static List booleanOperators = List.of(TokenType.AND, TokenType.OR, TokenType.NOT); - private static List comparisonOperators = List.of(TokenType.EQ, - NEQ, + private static List numericComparisonOperators = List.of( TokenType.GEQ, TokenType.LEQ, TokenType.GT, @@ -121,14 +120,12 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { Object value = assignmentNode.rightHandSide.accept(this); LemmsData dataValue = null; - if(value instanceof Integer) { - dataValue = new LemmsInt(((Integer)value)); - } - else if(value instanceof String) { - dataValue = new LemmsString(((String)value)); - } - else if(value instanceof LemmsObject) { - dataValue = (LemmsObject)value; + if (value instanceof Integer) { + dataValue = new LemmsInt(((Integer) value)); + } else if (value instanceof String) { + dataValue = new LemmsString(((String) value)); + } else if (value instanceof LemmsObject) { + dataValue = (LemmsObject) value; } environment.assign(assignmentNode.leftHandSide.name, dataValue); @@ -137,19 +134,20 @@ else if(value instanceof LemmsObject) { @Override public LemmsData visitVariableValue(VariableNode variableNode) { - LemmsData value = environment.get(variableNode.name); - if (value == null) //undefined, optional dedicated isDefined if needed? + LemmsData value = environment.get(variableNode.name); + if (value == null) // undefined, optional dedicated isDefined if needed? throw new LemmsRuntimeException("Undefined variable '" + variableNode.name + "'."); - else return value; + else + return value; } @Override public LemmsData visitLiteralValue(LiteralNode literalNode) { - if(literalNode.value instanceof Integer) { + if (literalNode.value instanceof Integer) { return new LemmsInt((Integer) literalNode.value); - } else if(literalNode.value instanceof String) { + } else if (literalNode.value instanceof String) { return new LemmsString((String) literalNode.value); - } else if(literalNode.value instanceof Boolean) { + } else if (literalNode.value instanceof Boolean) { return new LemmsBool((Boolean) literalNode.value); } throw new LemmsRuntimeException("Unknown literal type: " + literalNode.value.getClass().getSimpleName()); @@ -157,36 +155,78 @@ public LemmsData visitLiteralValue(LiteralNode literalNode) { @Override public LemmsData visitOperatorValue(OperatorNode operatorNode) { - - if (numericOperators.contains(operatorNode.operator.getType())) { - return evaluateNumericOperator(operatorNode); - } else if (booleanOperators.contains(operatorNode.operator.getType())) { - return evaluateBooleanOperator(operatorNode); - } else if (comparisonOperators.contains(operatorNode.operator.getType())) { - return evaluateComparisonOperators(operatorNode); + LemmsData leftValue = operatorNode.leftOperand.accept(this); + LemmsData rightValue = operatorNode.rightOperand.accept(this); + TokenType operatorType = operatorNode.operator.getType(); + + if (operatorType == EQ || operatorType == NEQ) { + boolean result = evaluateEqualityOperator(leftValue, rightValue, operatorType); + return new LemmsBool(result); + } + + if (leftValue instanceof LemmsBool && rightValue instanceof LemmsBool) { + boolean result = evaluateBooleanOperator(((LemmsBool) leftValue).value, + ((LemmsBool) rightValue).value, operatorType); + return new LemmsBool(result); + } else if (leftValue instanceof LemmsInt && rightValue instanceof LemmsInt) { + + if (numericOperators.contains(operatorType)) { + int result = evaluateNumericOperator(((LemmsInt) leftValue).value, + ((LemmsInt) rightValue).value, operatorType); + return new LemmsInt(result); + } else if (numericComparisonOperators.contains(operatorType)) { + boolean result = evaluateNumericComparisonOperator(((LemmsInt) leftValue).value, + ((LemmsInt) rightValue).value, operatorType); + return new LemmsBool(result); + } + } else { throw new RuntimeException("Unknown operator: " + operatorNode.operator); } + + return new LemmsBool(false); + } - private boolean evaluateComparisonOperators(OperatorNode operatorNode) { - Object leftValue = operatorNode.leftOperand.accept(this); - Object rightValue = operatorNode.rightOperand.accept(this); + private boolean evaluateEqualityOperator(LemmsData leftValue, LemmsData rightValue, TokenType operator) { - switch (operatorNode.operator.getType()) { - case EQ: - return leftValue.equals(rightValue); - case NEQ: - return !leftValue.equals(rightValue); - default: - break; + boolean result = false; + if(leftValue.getClass() != rightValue.getClass()) { + result = false; } + if(leftValue instanceof LemmsObject && rightValue instanceof LemmsObject) { + result = leftValue.equals(rightValue); + } else if (leftValue instanceof LemmsInt && rightValue instanceof LemmsInt) { + int leftValueInt = ((LemmsInt) leftValue).value; + int rightValueInt = ((LemmsInt) rightValue).value; + result = leftValueInt == rightValueInt; + } else if (leftValue instanceof LemmsString && rightValue instanceof LemmsString) { + String leftString = ((LemmsString) leftValue).value; + String rightString = ((LemmsString) rightValue).value; + result = leftString.equals(rightString); + + } else if (leftValue instanceof LemmsBool && rightValue instanceof LemmsBool) { + boolean leftBool = ((LemmsBool) leftValue).value; + boolean rightBool = ((LemmsBool) rightValue).value; + result = leftBool == rightBool; + } else { + throw new RuntimeException("Unknown equality check for: " + + leftValue.getClass().getSimpleName() + " and " + rightValue.getClass().getSimpleName()); + } + if (operator == EQ) { + return result; + } else if (operator == NEQ) { + return !result; + } else { + throw new RuntimeException("Unknown equality operator: " + operator); + } + + } - int leftValueInt = Integer.parseInt(operatorNode.leftOperand.accept(this).toString()); - int rightValueInt = Integer.parseInt(operatorNode.rightOperand.accept(this).toString()); + private boolean evaluateNumericComparisonOperator(int leftValueInt, int rightValueInt, TokenType operator) { - switch (operatorNode.operator.getType()) { + switch (operator) { case GT: return leftValueInt > rightValueInt; case LT: @@ -199,13 +239,11 @@ private boolean evaluateComparisonOperators(OperatorNode operatorNode) { break; } - throw new RuntimeException("Unknown comparison operator: " + operatorNode.operator); + throw new RuntimeException("Unknown comparison operator: " + operator); } - private Object evaluateBooleanOperator(OperatorNode operatorNode) { - boolean leftValue = isTrue(operatorNode.leftOperand.accept(this)); - boolean rightValue = isTrue(operatorNode.rightOperand.accept(this)); - switch (operatorNode.operator.getType()) { + private boolean evaluateBooleanOperator(boolean leftValue, boolean rightValue, TokenType operator) { + switch (operator) { case AND: return leftValue && rightValue; case OR: @@ -213,14 +251,13 @@ private Object evaluateBooleanOperator(OperatorNode operatorNode) { case NOT: return !rightValue; default: - throw new RuntimeException("Unknown operator: " + operatorNode.operator); + throw new RuntimeException("Unknown operator: " + operator); } } - private Object evaluateNumericOperator(OperatorNode operatorNode) { - int leftValue = Integer.parseInt(operatorNode.leftOperand.accept(this).toString()); - int rightValue = Integer.parseInt(operatorNode.rightOperand.accept(this).toString()); - switch (operatorNode.operator.getType()) { + private int evaluateNumericOperator(int leftValue, int rightValue, TokenType operator) { + + switch (operator) { case PLUS: return leftValue + rightValue; case MINUS: @@ -229,19 +266,19 @@ private Object evaluateNumericOperator(OperatorNode operatorNode) { return leftValue * rightValue; case DIVISION: if (rightValue == 0) { - //throw new RuntimeException("Division by zero"); - throw new LemmsRuntimeException(operatorNode.operator, "Division by zero."); + // throw new RuntimeException("Division by zero"); + throw new LemmsRuntimeException("Division by zero."); } return leftValue / rightValue; case MODULO: if (rightValue == 0) { - //throw new RuntimeException("Division by zero"); - throw new LemmsRuntimeException(operatorNode.operator, "Division by zero."); + // throw new RuntimeException("Division by zero"); + throw new LemmsRuntimeException("Division by zero."); } return leftValue % rightValue; default: - //throw new RuntimeException("Unknown operator: " + operatorNode.operator); - throw new LemmsRuntimeException(operatorNode.operator, "Unknown operator: " + operatorNode.operator); + // throw new RuntimeException("Unknown operator: " + operatorNode.operator); + throw new LemmsRuntimeException("Unknown operator: " + operator); } } @@ -255,11 +292,11 @@ private boolean isTrue(Object object) { } @Override - public Object visitFunctionCallValue(FunctionCallNode functionNode) { + public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { if (nativeFunctions.containsKey((functionNode.functionName))) { NativeFunction nativeFunction = nativeFunctions.get(functionNode.functionName); - List args = functionNode.params.stream() + List args = functionNode.params.stream() .map(param -> param.accept(this)) .toList(); return nativeFunction.apply(args); @@ -267,7 +304,7 @@ public Object visitFunctionCallValue(FunctionCallNode functionNode) { Object functionValue = environment.get(functionNode.functionName); if (functionValue instanceof FunctionDeclarationNode) { - List args = functionNode.params.stream() + List args = functionNode.params.stream() .map(param -> param.accept(this)) .toList(); @@ -307,10 +344,10 @@ public void visitFunctionDeclarationStatement(FunctionDeclarationNode functionDe @Override public FlowSignal visitReturnNode(ReturnNode returnNode) { - if(returnNode.value == null) { + if (returnNode.value == null) { return FlowSignal.returned(null); } - Object returnValue = returnNode.value.accept(this); + LemmsData returnValue = returnNode.value.accept(this); return FlowSignal.returned(returnValue); } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsBool.java b/src/main/java/com/lemms/interpreter/object/LemmsBool.java index 34be41d..f4e1539 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsBool.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsBool.java @@ -1,7 +1,7 @@ package com.lemms.interpreter.object; public class LemmsBool extends LemmsData { - private boolean value; + public boolean value; public LemmsBool(boolean value) { this.value = value; diff --git a/src/main/java/com/lemms/interpreter/object/LemmsInt.java b/src/main/java/com/lemms/interpreter/object/LemmsInt.java index d2bf47d..209f04f 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsInt.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsInt.java @@ -1,7 +1,7 @@ package com.lemms.interpreter.object; public class LemmsInt extends LemmsData { - private final int value; + public final int value; public LemmsInt(int value) { this.value = value; } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsString.java b/src/main/java/com/lemms/interpreter/object/LemmsString.java index a0812a7..c91cfab 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsString.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsString.java @@ -1,7 +1,7 @@ package com.lemms.interpreter.object; public class LemmsString extends LemmsData { - private String value; + public String value; public LemmsString(String value) { this.value = value; From ede82681ddbe6fa5e0a660ef596a037a6b73c7fb Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 20:44:04 +0200 Subject: [PATCH 07/30] initial compiling code --- src/main/java/com/lemms/api/LemmsAPI.java | 3 +- .../java/com/lemms/api/NativeFunction.java | 4 +- .../com/lemms/interpreter/Interpreter.java | 54 +++++++++++-------- .../PredefinedFunctionLibrary.java | 12 +++-- .../interpreter/object/LemmsFunction.java | 21 ++++++++ 5 files changed, 65 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/lemms/interpreter/object/LemmsFunction.java diff --git a/src/main/java/com/lemms/api/LemmsAPI.java b/src/main/java/com/lemms/api/LemmsAPI.java index df3e7cd..d60cce2 100644 --- a/src/main/java/com/lemms/api/LemmsAPI.java +++ b/src/main/java/com/lemms/api/LemmsAPI.java @@ -11,12 +11,13 @@ import com.lemms.Tokenizer; import com.lemms.SyntaxNode.StatementNode; import com.lemms.interpreter.Interpreter; +import com.lemms.interpreter.object.LemmsData; import com.lemms.parser.Parser; public class LemmsAPI { private HashMap nativeFunctions; private List program; - public void registerFunction(String name, Function, Object> function) { + public void registerFunction(String name, Function, LemmsData> function) { nativeFunctions.put(name, function::apply); } diff --git a/src/main/java/com/lemms/api/NativeFunction.java b/src/main/java/com/lemms/api/NativeFunction.java index 87885ac..ec82668 100644 --- a/src/main/java/com/lemms/api/NativeFunction.java +++ b/src/main/java/com/lemms/api/NativeFunction.java @@ -2,6 +2,8 @@ import java.util.List; +import com.lemms.interpreter.object.LemmsData; + @FunctionalInterface public interface NativeFunction { /** @@ -10,5 +12,5 @@ public interface NativeFunction { * @return A result object (or null) to return back into the script. * @throws RuntimeException if something goes wrong in the host side. */ - Object apply(List args); + LemmsData apply(List args); } \ No newline at end of file diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 1db9643..51f4a4a 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -14,6 +14,7 @@ import com.lemms.interpreter.FlowSignal.SignalType; import com.lemms.interpreter.object.LemmsBool; import com.lemms.interpreter.object.LemmsData; +import com.lemms.interpreter.object.LemmsFunction; import com.lemms.interpreter.object.LemmsInt; import com.lemms.interpreter.object.LemmsObject; import com.lemms.interpreter.object.LemmsString; @@ -167,7 +168,7 @@ public LemmsData visitOperatorValue(OperatorNode operatorNode) { if (leftValue instanceof LemmsBool && rightValue instanceof LemmsBool) { boolean result = evaluateBooleanOperator(((LemmsBool) leftValue).value, - ((LemmsBool) rightValue).value, operatorType); + ((LemmsBool) rightValue).value, operatorType); return new LemmsBool(result); } else if (leftValue instanceof LemmsInt && rightValue instanceof LemmsInt) { @@ -186,16 +187,16 @@ public LemmsData visitOperatorValue(OperatorNode operatorNode) { } return new LemmsBool(false); - + } private boolean evaluateEqualityOperator(LemmsData leftValue, LemmsData rightValue, TokenType operator) { boolean result = false; - if(leftValue.getClass() != rightValue.getClass()) { - result = false; + if (leftValue.getClass() != rightValue.getClass()) { + result = false; } - if(leftValue instanceof LemmsObject && rightValue instanceof LemmsObject) { + if (leftValue instanceof LemmsObject && rightValue instanceof LemmsObject) { result = leftValue.equals(rightValue); } else if (leftValue instanceof LemmsInt && rightValue instanceof LemmsInt) { int leftValueInt = ((LemmsInt) leftValue).value; @@ -205,14 +206,14 @@ private boolean evaluateEqualityOperator(LemmsData leftValue, LemmsData rightVal String leftString = ((LemmsString) leftValue).value; String rightString = ((LemmsString) rightValue).value; result = leftString.equals(rightString); - + } else if (leftValue instanceof LemmsBool && rightValue instanceof LemmsBool) { boolean leftBool = ((LemmsBool) leftValue).value; boolean rightBool = ((LemmsBool) rightValue).value; - result = leftBool == rightBool; + result = leftBool == rightBool; } else { - throw new RuntimeException("Unknown equality check for: " - + leftValue.getClass().getSimpleName() + " and " + rightValue.getClass().getSimpleName()); + throw new RuntimeException("Unknown equality check for: " + + leftValue.getClass().getSimpleName() + " and " + rightValue.getClass().getSimpleName()); } if (operator == EQ) { return result; @@ -242,7 +243,7 @@ private boolean evaluateNumericComparisonOperator(int leftValueInt, int rightVal throw new RuntimeException("Unknown comparison operator: " + operator); } - private boolean evaluateBooleanOperator(boolean leftValue, boolean rightValue, TokenType operator) { + private boolean evaluateBooleanOperator(boolean leftValue, boolean rightValue, TokenType operator) { switch (operator) { case AND: return leftValue && rightValue; @@ -294,31 +295,38 @@ private boolean isTrue(Object object) { @Override public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { - if (nativeFunctions.containsKey((functionNode.functionName))) { - NativeFunction nativeFunction = nativeFunctions.get(functionNode.functionName); - List args = functionNode.params.stream() - .map(param -> param.accept(this)) - .toList(); - return nativeFunction.apply(args); + LemmsData functionValue = environment.get(functionNode.functionName); + if (!(functionValue instanceof LemmsFunction)) { + throw new LemmsRuntimeException( + "Function '" + functionNode.functionName + "' is not defined or not a function."); } - - Object functionValue = environment.get(functionNode.functionName); - if (functionValue instanceof FunctionDeclarationNode) { + LemmsFunction lemmsFunction = (LemmsFunction) functionValue; + if (lemmsFunction.isNative) { + if (nativeFunctions.containsKey((functionNode.functionName))) { + NativeFunction nativeFunction = lemmsFunction.nativeFunction; + List args = functionNode.params.stream() + .map(param -> param.accept(this)) + .toList(); + return nativeFunction.apply(args); + } + } + else { + FunctionDeclarationNode functionDeclaration = lemmsFunction.functionDeclaration; List args = functionNode.params.stream() .map(param -> param.accept(this)) .toList(); Environment functionEnvironment = new Environment(globalEnvironment); for (int i = 0; i < args.size(); i++) { - String argName = ((FunctionDeclarationNode) functionValue).paramNames.get(i); - Object argValue = args.get(i); + String argName = functionDeclaration.paramNames.get(i); + LemmsData argValue = args.get(i); functionEnvironment.assign(argName, argValue); } Environment previousEnvironment = environment; environment = functionEnvironment; - FlowSignal result = ((FunctionDeclarationNode) functionValue).functionBody.accept(this); + FlowSignal result = functionDeclaration.functionBody.accept(this); if (result.signal == SignalType.RETURN || result.signal == SignalType.NORMAL) { environment = previousEnvironment; // Restore the previous environment return result.value; @@ -339,7 +347,7 @@ public FlowSignal visitFunctionCallStatement(FunctionCallStatementNode functionN @Override public void visitFunctionDeclarationStatement(FunctionDeclarationNode functionDeclarationNode) { - environment.assign(functionDeclarationNode.functionName, functionDeclarationNode); + environment.assign(functionDeclarationNode.functionName, new LemmsFunction(functionDeclarationNode)); } @Override diff --git a/src/main/java/com/lemms/interpreter/PredefinedFunctionLibrary.java b/src/main/java/com/lemms/interpreter/PredefinedFunctionLibrary.java index 1065525..3823622 100644 --- a/src/main/java/com/lemms/interpreter/PredefinedFunctionLibrary.java +++ b/src/main/java/com/lemms/interpreter/PredefinedFunctionLibrary.java @@ -4,6 +4,8 @@ import java.util.Map; import com.lemms.api.NativeFunction; +import com.lemms.interpreter.object.LemmsInt; +import com.lemms.interpreter.object.LemmsString; public class PredefinedFunctionLibrary { @@ -16,15 +18,17 @@ public static Map getPredefinedFunctions() { }); functions.put("len", args -> { - if (args.get(0) instanceof String) { - return ((String) args.get(0)).length(); + if (args.get(0) instanceof LemmsString) { + int length = ((LemmsString)args.get(0)).value.length(); + return new LemmsInt(length); } throw new RuntimeException("Unsupported type for len function."); }); functions.put("exit", args -> { - if (!args.isEmpty() && args.get(0) instanceof Integer) { - System.exit((Integer) args.get(0)); + if (!args.isEmpty() && args.get(0) instanceof LemmsInt) { + + System.exit(((LemmsInt) args.get(0)).value); } else { System.exit(0); } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsFunction.java b/src/main/java/com/lemms/interpreter/object/LemmsFunction.java new file mode 100644 index 0000000..699e5d1 --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsFunction.java @@ -0,0 +1,21 @@ +package com.lemms.interpreter.object; + +import com.lemms.SyntaxNode.FunctionDeclarationNode; +import com.lemms.api.NativeFunction; + +public class LemmsFunction extends LemmsData { + public final FunctionDeclarationNode functionDeclaration; + public final NativeFunction nativeFunction; + public final boolean isNative; + public LemmsFunction(FunctionDeclarationNode value) { + this.functionDeclaration = value; + this.nativeFunction = null; + isNative = false; + } + + public LemmsFunction(NativeFunction value) { + this.functionDeclaration = null; + this.nativeFunction = value; + isNative = true; + } +} From 1040109b59044e41d3f44120a1f7994c26e87f32 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 20:52:02 +0200 Subject: [PATCH 08/30] fix first 3 tests --- .../com/lemms/interpreter/Interpreter.java | 11 +-- src/test/java/com/lemms/InterpreterTest.java | 70 ++++++++++--------- 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 51f4a4a..6714776 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -119,16 +119,7 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { - Object value = assignmentNode.rightHandSide.accept(this); - LemmsData dataValue = null; - if (value instanceof Integer) { - dataValue = new LemmsInt(((Integer) value)); - } else if (value instanceof String) { - dataValue = new LemmsString(((String) value)); - } else if (value instanceof LemmsObject) { - dataValue = (LemmsObject) value; - } - + LemmsData dataValue = assignmentNode.rightHandSide.accept(this); environment.assign(assignmentNode.leftHandSide.name, dataValue); return FlowSignal.NORMAL; } diff --git a/src/test/java/com/lemms/InterpreterTest.java b/src/test/java/com/lemms/InterpreterTest.java index c603274..0f2b37e 100644 --- a/src/test/java/com/lemms/InterpreterTest.java +++ b/src/test/java/com/lemms/InterpreterTest.java @@ -3,27 +3,32 @@ import org.junit.jupiter.api.Test; import com.lemms.SyntaxNode.*; import com.lemms.interpreter.Interpreter; +import com.lemms.interpreter.object.LemmsData; +import com.lemms.interpreter.object.LemmsInt; + import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; // filepath: src/test/java/com/lemms/MainTest.java class InterpreterTest { - @Test void testAssignment() { String variableName = "meaningOfLife"; int value = 9; - //meaningOfLife = 0 + // meaningOfLife = 0 VariableNode variableNode = new VariableNode(variableName); ExpressionNode expressionNode = new LiteralNode(value); AssignmentNode assignmentNode = new AssignmentNode(variableNode, expressionNode); Interpreter interpreter = new Interpreter(List.of(assignmentNode)); - interpreter.interpret(); + interpreter.interpret(); - assertEquals(interpreter.environment.get(variableName), value); + LemmsData result = interpreter.environment.get(variableName); + assertInstanceOf(LemmsInt.class, result); + assertEquals(value, ((LemmsInt) result).value); } @Test @@ -32,20 +37,20 @@ void testAssignmentWithOperator() { int value1 = 9; int value2 = 18; - Token operator = new Token(TokenType.PLUS); ExpressionNode leftOperand = new LiteralNode(value1); ExpressionNode rightOperand = new LiteralNode(value2); ExpressionNode expressionNode = new OperatorNode(leftOperand, operator, rightOperand); - VariableNode variableNode= new VariableNode(variableName); + VariableNode variableNode = new VariableNode(variableName); AssignmentNode assignmentNode = new AssignmentNode(variableNode, expressionNode); - Interpreter interpreter = new Interpreter(List.of(assignmentNode)); - interpreter.interpret(); + interpreter.interpret(); - assertEquals(interpreter.environment.get(variableName), value1 + value2); + LemmsData result = interpreter.environment.get(variableName); + assertInstanceOf(LemmsInt.class, result); + assertEquals(value1 + value2, ((LemmsInt)result).value); } @Test @@ -56,24 +61,23 @@ void testDoubleAssignment() { VariableNode variableNode = new VariableNode(variableName1); ExpressionNode expressionNode = new LiteralNode(value); - AssignmentNode assignmentNode = new AssignmentNode(variableNode,expressionNode); - + AssignmentNode assignmentNode = new AssignmentNode(variableNode, expressionNode); VariableNode variableNode2 = new VariableNode(variableName2); ExpressionNode expressionNode2 = new LiteralNode(value); - AssignmentNode assignmentNode2 = new AssignmentNode(variableNode2,expressionNode2); + AssignmentNode assignmentNode2 = new AssignmentNode(variableNode2, expressionNode2); - List program = List.of(assignmentNode, assignmentNode2); Interpreter interpreter = new Interpreter(program); - interpreter.interpret(); + interpreter.interpret(); - assertEquals(interpreter.environment.get(variableName1), interpreter.environment.get(variableName2)); + assertEquals(((LemmsInt)interpreter.environment.get(variableName1)).value, + ((LemmsInt)interpreter.environment.get(variableName2)).value); } - + private static final int FIBONACCI_NUMBER_20TH = 6765; - + @Test void testFibonacci() { String variableNameN = "n"; @@ -82,22 +86,22 @@ void testFibonacci() { int n = 20; int value1 = 0; int value2 = 1; - + // n = 20 VariableNode n1 = new VariableNode(variableNameN); ExpressionNode number20 = new LiteralNode(n); AssignmentNode assignmentNodeN = new AssignmentNode(n1, number20); // a = 0 - VariableNode a0 = new VariableNode(variableName1); - ExpressionNode nummber0 = new LiteralNode(value1); + VariableNode a0 = new VariableNode(variableName1); + ExpressionNode nummber0 = new LiteralNode(value1); AssignmentNode assignmentNode1 = new AssignmentNode(a0, nummber0); // b = 1 - VariableNode b0 = new VariableNode(variableName2); + VariableNode b0 = new VariableNode(variableName2); ExpressionNode number1 = new LiteralNode(value2); AssignmentNode assignmentNode2 = new AssignmentNode(b0, number1); - + // while n >= 1 VariableNode n2 = new VariableNode(variableNameN); ExpressionNode number2 = new LiteralNode(1); @@ -106,43 +110,41 @@ void testFibonacci() { // temp = a + b VariableNode temp1 = new VariableNode("temp"); - Token plus = new Token(TokenType.PLUS); + Token plus = new Token(TokenType.PLUS); VariableNode a1 = new VariableNode(variableName1); VariableNode b1 = new VariableNode(variableName2); ExpressionNode expressionNode3 = new OperatorNode(a1, plus, b1); - AssignmentNode assignmentNodeTemp = new AssignmentNode(temp1,expressionNode3); - + AssignmentNode assignmentNodeTemp = new AssignmentNode(temp1, expressionNode3); // a = b VariableNode a2 = new VariableNode(variableName1); VariableNode b2 = new VariableNode(variableName2); - AssignmentNode assignmentNode3 = new AssignmentNode(a2,b2); + AssignmentNode assignmentNode3 = new AssignmentNode(a2, b2); - // b = temp VariableNode b3 = new VariableNode(variableName2); VariableNode temp3 = new VariableNode("temp"); - AssignmentNode assignmentNode4 = new AssignmentNode(b3,temp3); + AssignmentNode assignmentNode4 = new AssignmentNode(b3, temp3); // n = n - 1 - - VariableNode n5 = new VariableNode(variableNameN); ExpressionNode expressionNode5 = new LiteralNode(1); OperatorNode decrementN = new OperatorNode(n5, new Token(TokenType.MINUS), expressionNode5); VariableNode n4 = new VariableNode(variableNameN); - AssignmentNode assignmentNode5 = new AssignmentNode(n4,decrementN); + AssignmentNode assignmentNode5 = new AssignmentNode(n4, decrementN); - BlockNode blockNode = new BlockNode(List.of(assignmentNodeTemp, assignmentNode3, assignmentNode4, assignmentNode5)); + BlockNode blockNode = new BlockNode( + List.of(assignmentNodeTemp, assignmentNode3, assignmentNode4, assignmentNode5)); WhileNode whileBlock = new WhileNode(); whileBlock.condition = condition; whileBlock.whileBody = blockNode; - Interpreter interpreter = new Interpreter(List.of(assignmentNodeN, assignmentNode1, assignmentNode2, whileBlock)); + Interpreter interpreter = new Interpreter( + List.of(assignmentNodeN, assignmentNode1, assignmentNode2, whileBlock)); - interpreter.interpret(); + interpreter.interpret(); assertEquals(interpreter.environment.get(variableName1), FIBONACCI_NUMBER_20TH); } From b14b6c91bab44a61a955fb9cbd7cdf5b03768efc Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 20:56:25 +0200 Subject: [PATCH 09/30] environmentalize global functions --- .../com/lemms/interpreter/Interpreter.java | 36 ++++++++----------- src/test/java/com/lemms/InterpreterTest.java | 2 +- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 6714776..e0ff8d5 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -3,8 +3,6 @@ //für Exceptions import com.lemms.Exceptions.LemmsRuntimeException; -import static java.lang.Character.valueOf; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -33,9 +31,6 @@ public class Interpreter implements StatementVisitor, ValueVisitor { TokenType.DIVISION, TokenType.MODULO); - private static List booleanOperators = List.of(TokenType.AND, - TokenType.OR, TokenType.NOT); - private static List numericComparisonOperators = List.of( TokenType.GEQ, TokenType.LEQ, @@ -64,6 +59,10 @@ private void addPredefinedFunctions() { public void interpret() { globalEnvironment = new Environment(); environment = globalEnvironment; + for (var entry : nativeFunctions.entrySet()) { + globalEnvironment.assign(entry.getKey(), new LemmsFunction(entry.getValue())); + } + for (StatementNode i : program) { i.accept(this); } @@ -119,7 +118,7 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { - LemmsData dataValue = assignmentNode.rightHandSide.accept(this); + LemmsData dataValue = assignmentNode.rightHandSide.accept(this); environment.assign(assignmentNode.leftHandSide.name, dataValue); return FlowSignal.NORMAL; } @@ -275,12 +274,12 @@ private int evaluateNumericOperator(int leftValue, int rightValue, TokenType ope } } - private boolean isTrue(Object object) { + private boolean isTrue(LemmsData object) { if (object == null) return false; - if (object instanceof Boolean) - return (boolean) object; - return true; + if (object instanceof LemmsBool) + return ((LemmsBool) object).value; + throw new LemmsRuntimeException("Condition must be a boolean, but was: " + object.getClass().getSimpleName()); } @Override @@ -293,15 +292,12 @@ public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { } LemmsFunction lemmsFunction = (LemmsFunction) functionValue; if (lemmsFunction.isNative) { - if (nativeFunctions.containsKey((functionNode.functionName))) { - NativeFunction nativeFunction = lemmsFunction.nativeFunction; - List args = functionNode.params.stream() - .map(param -> param.accept(this)) - .toList(); - return nativeFunction.apply(args); - } - } - else { + NativeFunction nativeFunction = lemmsFunction.nativeFunction; + List args = functionNode.params.stream() + .map(param -> param.accept(this)) + .toList(); + return nativeFunction.apply(args); + } else { FunctionDeclarationNode functionDeclaration = lemmsFunction.functionDeclaration; List args = functionNode.params.stream() .map(param -> param.accept(this)) @@ -325,8 +321,6 @@ public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { throw new RuntimeException("Function did not return a value: " + functionNode.functionName); } } - - throw new RuntimeException("Unknown function: " + functionNode.functionName); } @Override diff --git a/src/test/java/com/lemms/InterpreterTest.java b/src/test/java/com/lemms/InterpreterTest.java index 0f2b37e..044bbcb 100644 --- a/src/test/java/com/lemms/InterpreterTest.java +++ b/src/test/java/com/lemms/InterpreterTest.java @@ -146,6 +146,6 @@ void testFibonacci() { interpreter.interpret(); - assertEquals(interpreter.environment.get(variableName1), FIBONACCI_NUMBER_20TH); + assertEquals(FIBONACCI_NUMBER_20TH, ((LemmsInt)interpreter.environment.get(variableName1)).value); } } \ No newline at end of file From 58c6d2fa8f303315d94166558f17623d47474f9a Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 21:04:15 +0200 Subject: [PATCH 10/30] can do higher order functions --- src/main/java/com/lemms/Lemms.java | 2 +- .../java/com/lemms/interpreter/object/LemmsBool.java | 5 +++++ .../java/com/lemms/interpreter/object/LemmsData.java | 2 +- .../com/lemms/interpreter/object/LemmsFunction.java | 5 +++++ .../java/com/lemms/interpreter/object/LemmsInt.java | 4 ++++ .../com/lemms/interpreter/object/LemmsObject.java | 11 +++++++++++ .../com/lemms/interpreter/object/LemmsString.java | 5 +++++ src/main/resources/successTests/functionTest.lemms | 10 ++++++++++ 8 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/successTests/functionTest.lemms diff --git a/src/main/java/com/lemms/Lemms.java b/src/main/java/com/lemms/Lemms.java index f7e83bd..1e37d1f 100644 --- a/src/main/java/com/lemms/Lemms.java +++ b/src/main/java/com/lemms/Lemms.java @@ -44,7 +44,7 @@ public class Lemms { public static void main(String[] args) { try { String sourcePath = "foo/bar/sourcePath.example (später mit command-line-args ersetzen)"; - sourcePath = "src/main/resources/successTests/classTest.lemms"; //String sourcePath = args[0]; + sourcePath = "src/main/resources/successTests/functionTest.lemms"; //String sourcePath = args[0]; File sourceFile = new File(sourcePath); //Verknüpfung: Tokenizer + Parser + Interpreter diff --git a/src/main/java/com/lemms/interpreter/object/LemmsBool.java b/src/main/java/com/lemms/interpreter/object/LemmsBool.java index f4e1539..4f7d27c 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsBool.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsBool.java @@ -6,4 +6,9 @@ public class LemmsBool extends LemmsData { public LemmsBool(boolean value) { this.value = value; } + + @Override + public String toString() { + return value ? "true" : "false"; + } } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsData.java b/src/main/java/com/lemms/interpreter/object/LemmsData.java index 9f0db8c..6161d6b 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsData.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsData.java @@ -1,5 +1,5 @@ package com.lemms.interpreter.object; public abstract class LemmsData { - + public abstract String toString(); } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsFunction.java b/src/main/java/com/lemms/interpreter/object/LemmsFunction.java index 699e5d1..6aef7c6 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsFunction.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsFunction.java @@ -18,4 +18,9 @@ public LemmsFunction(NativeFunction value) { this.nativeFunction = value; isNative = true; } + + @Override + public String toString() { + return isNative ? "NativeFunction" : functionDeclaration.functionName; + } } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsInt.java b/src/main/java/com/lemms/interpreter/object/LemmsInt.java index 209f04f..f51cb12 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsInt.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsInt.java @@ -5,5 +5,9 @@ public class LemmsInt extends LemmsData { public LemmsInt(int value) { this.value = value; } + @Override + public String toString() { + return String.valueOf(value); + } } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsObject.java b/src/main/java/com/lemms/interpreter/object/LemmsObject.java index fd817e3..9aa54fc 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsObject.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsObject.java @@ -20,5 +20,16 @@ public LemmsData get(String name) { public void set(String name, LemmsData value) { properties.put(name, value); } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(classDeclaration.className).append("{\n"); + for (Map.Entry entry : properties.entrySet()) { + sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); + } + sb.append("}"); + return sb.toString(); + } } diff --git a/src/main/java/com/lemms/interpreter/object/LemmsString.java b/src/main/java/com/lemms/interpreter/object/LemmsString.java index c91cfab..c17ec0f 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsString.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsString.java @@ -6,4 +6,9 @@ public class LemmsString extends LemmsData { public LemmsString(String value) { this.value = value; } + + @Override + public String toString() { + return value; + } } diff --git a/src/main/resources/successTests/functionTest.lemms b/src/main/resources/successTests/functionTest.lemms new file mode 100644 index 0000000..6830b34 --- /dev/null +++ b/src/main/resources/successTests/functionTest.lemms @@ -0,0 +1,10 @@ +function printStuff() +{ + print("hello!"); +} + +function higherOrder(argumentFunction) { + argumentFunction(); +} + +higherOrder(printStuff); \ No newline at end of file From 4f8c0eb9eb0236696ebde5d8227bd3696c8f354f Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 21:08:12 +0200 Subject: [PATCH 11/30] different example --- .../resources/successTests/functionTest.lemms | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/resources/successTests/functionTest.lemms b/src/main/resources/successTests/functionTest.lemms index 6830b34..f17d44d 100644 --- a/src/main/resources/successTests/functionTest.lemms +++ b/src/main/resources/successTests/functionTest.lemms @@ -1,10 +1,18 @@ -function printStuff() +function multiply( x ) { - print("hello!"); + return x * x; } -function higherOrder(argumentFunction) { - argumentFunction(); +function halve( x ) +{ + return x / 2 ; +} + + +function applyFunctions(fA, fB, x) { + print(fA(x)+fB(x)); } -higherOrder(printStuff); \ No newline at end of file +applyFunctions(multiply, halve, 8); +applyFunctions(multiply, multiply, 8); +applyFunctions(halve, halve, 500); \ No newline at end of file From e89fddbbdba6cf4d318f8ba409d36e9e24b8db25 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 17 Jun 2025 21:13:08 +0200 Subject: [PATCH 12/30] add constructor --- .../com/lemms/interpreter/Interpreter.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index e0ff8d5..072b02a 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -346,7 +346,22 @@ public FlowSignal visitReturnNode(ReturnNode returnNode) { @Override public void visitClassDeclarationStatement(ClassDeclarationNode classDeclarationNode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'visitClassDeclarationStatement'"); + + NativeFunction constructor = (args) -> { + HashMap properties = new HashMap(); + for (int i = 0; i < classDeclarationNode.localVariables.size(); i++) { + String paramName = classDeclarationNode.localVariables.get(i); + LemmsData paramValue = args.get(i); + properties.put(paramName, paramValue); + } + for (var functionDeclaration : classDeclarationNode.localFunctions) { + properties.put(functionDeclaration.functionName, new LemmsFunction(functionDeclaration)); + } + + return new LemmsObject(classDeclarationNode, properties); + }; + + globalEnvironment.assign(classDeclarationNode.className, + new LemmsFunction(constructor)); } } \ No newline at end of file From 1450b9e9fad6ea08cb356a40ac0c061a4b160a7f Mon Sep 17 00:00:00 2001 From: notkminq Date: Tue, 24 Jun 2025 11:30:47 +0200 Subject: [PATCH 13/30] test program --- .../java/com/lemms/Canvas/CanvasTest.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/lemms/Canvas/CanvasTest.java diff --git a/src/main/java/com/lemms/Canvas/CanvasTest.java b/src/main/java/com/lemms/Canvas/CanvasTest.java new file mode 100644 index 0000000..5b4fd25 --- /dev/null +++ b/src/main/java/com/lemms/Canvas/CanvasTest.java @@ -0,0 +1,32 @@ +package com.lemms.Canvas; + + +import java.awt.*; +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Frame; + +public class CanvasTest { + public static void main(String[] args) { + Frame frame = new Frame("Mein Canvas Fenster"); + frame.setSize(400, 300); //Fenster + + MeinCanvas canvas = new MeinCanvas(); + canvas.setSize(400, 300); + canvas.setBackground(Color.WHITE); + + frame.add(canvas); + frame.setVisible(true); + } + + static class MeinCanvas extends Canvas { + @Override + public void paint(Graphics g) { + g.setColor(Color.BLUE); + g.fillRect(50, 50, 100, 100); // Ein blaues Quadrat zeichnen + } + } +} + + From d03bc317488b196cb6d56cf59e72ad33861500c2 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 15:17:31 +0200 Subject: [PATCH 14/30] lemms object has environment --- .../java/com/lemms/interpreter/Interpreter.java | 16 ++++++++++++++-- .../lemms/interpreter/object/LemmsObject.java | 13 +++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 072b02a..623c90c 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -119,7 +119,19 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { LemmsData dataValue = assignmentNode.rightHandSide.accept(this); - environment.assign(assignmentNode.leftHandSide.name, dataValue); + Environment targetEnvironment = environment; + VariableNode targetNode = assignmentNode.leftHandSide; + while (targetNode.child != null) { + LemmsData data = environment.get(targetNode.name); + if(data instanceof LemmsObject lo) { + targetEnvironment = lo.environment; + targetNode = targetNode.child; + } + else { + throw new LemmsRuntimeException("Cannot assign to non-object variable '" + targetNode.name + "'."); + } + } + targetEnvironment.assign(targetNode.name, dataValue); return FlowSignal.NORMAL; } @@ -358,7 +370,7 @@ public void visitClassDeclarationStatement(ClassDeclarationNode classDeclaration properties.put(functionDeclaration.functionName, new LemmsFunction(functionDeclaration)); } - return new LemmsObject(classDeclarationNode, properties); + return new LemmsObject(classDeclarationNode, globalEnvironment); }; globalEnvironment.assign(classDeclarationNode.className, diff --git a/src/main/java/com/lemms/interpreter/object/LemmsObject.java b/src/main/java/com/lemms/interpreter/object/LemmsObject.java index 9aa54fc..997fa7d 100644 --- a/src/main/java/com/lemms/interpreter/object/LemmsObject.java +++ b/src/main/java/com/lemms/interpreter/object/LemmsObject.java @@ -3,29 +3,30 @@ import java.util.Map; import com.lemms.SyntaxNode.ClassDeclarationNode; +import com.lemms.interpreter.Environment; public class LemmsObject extends LemmsData { - private final Map properties; + public final Environment environment; public final ClassDeclarationNode classDeclaration; - public LemmsObject(ClassDeclarationNode classDeclaration, Map properties) { + public LemmsObject(ClassDeclarationNode classDeclaration, Environment globalEnvironment) { this.classDeclaration = classDeclaration; - this.properties = properties; + this.environment = new Environment(globalEnvironment); } public LemmsData get(String name) { - return properties.get(name); + return environment.get(name); } public void set(String name, LemmsData value) { - properties.put(name, value); + environment.assign(name, value); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(classDeclaration.className).append("{\n"); - for (Map.Entry entry : properties.entrySet()) { + for (Map.Entry entry : environment.getValues().entrySet()) { sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n"); } sb.append("}"); From a0c6d5c5eb36563ccfa75e40b802e4d1e585267f Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 15:29:19 +0200 Subject: [PATCH 15/30] add member access node --- .../lemms/SyntaxNode/MemberAccessNode.java | 20 +++++++++++++++++++ .../com/lemms/interpreter/Interpreter.java | 6 ++++++ .../com/lemms/interpreter/ValueVisitor.java | 1 + 3 files changed, 27 insertions(+) create mode 100644 src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java diff --git a/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java new file mode 100644 index 0000000..d12f278 --- /dev/null +++ b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java @@ -0,0 +1,20 @@ +package com.lemms.SyntaxNode; + +import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; + +public class MemberAccessNode extends ExpressionNode { + public ExpressionNode object; + public String memberName; + + public MemberAccessNode(ExpressionNode object, String memberName) { + this.object = object; + this.memberName = memberName; + } + + @Override + public LemmsData accept(ValueVisitor visitor) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'accept'"); + } +} \ No newline at end of file diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 623c90c..3c7f9c8 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -376,4 +376,10 @@ public void visitClassDeclarationStatement(ClassDeclarationNode classDeclaration globalEnvironment.assign(classDeclarationNode.className, new LemmsFunction(constructor)); } + + @Override + public LemmsData visitMemberAccessValue(MemberAccessNode functionNode) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'visitMemberAccessValue'"); + } } \ No newline at end of file diff --git a/src/main/java/com/lemms/interpreter/ValueVisitor.java b/src/main/java/com/lemms/interpreter/ValueVisitor.java index dad225b..584970f 100644 --- a/src/main/java/com/lemms/interpreter/ValueVisitor.java +++ b/src/main/java/com/lemms/interpreter/ValueVisitor.java @@ -8,6 +8,7 @@ public interface ValueVisitor { public LemmsData visitLiteralValue(LiteralNode literalNode); public LemmsData visitOperatorValue(OperatorNode operatorNode); public LemmsData visitFunctionCallValue(FunctionCallNode functionNode); + public LemmsData visitMemberAccessValue(MemberAccessNode functionNode); } From 564a0668062b6d60945ca81af9e77abcd898ca5c Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 15:30:16 +0200 Subject: [PATCH 16/30] implement visit member access node --- .../lemms/SyntaxNode/MemberAccessNode.java | 3 +-- .../com/lemms/interpreter/Interpreter.java | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java index d12f278..b06cec0 100644 --- a/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java +++ b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java @@ -14,7 +14,6 @@ public MemberAccessNode(ExpressionNode object, String memberName) { @Override public LemmsData accept(ValueVisitor visitor) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'accept'"); + return visitor.visitMemberAccessValue(this); } } \ No newline at end of file diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 3c7f9c8..8ebba1c 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -123,11 +123,10 @@ public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { VariableNode targetNode = assignmentNode.leftHandSide; while (targetNode.child != null) { LemmsData data = environment.get(targetNode.name); - if(data instanceof LemmsObject lo) { + if (data instanceof LemmsObject lo) { targetEnvironment = lo.environment; targetNode = targetNode.child; - } - else { + } else { throw new LemmsRuntimeException("Cannot assign to non-object variable '" + targetNode.name + "'."); } } @@ -358,7 +357,7 @@ public FlowSignal visitReturnNode(ReturnNode returnNode) { @Override public void visitClassDeclarationStatement(ClassDeclarationNode classDeclarationNode) { - + NativeFunction constructor = (args) -> { HashMap properties = new HashMap(); for (int i = 0; i < classDeclarationNode.localVariables.size(); i++) { @@ -377,9 +376,17 @@ public void visitClassDeclarationStatement(ClassDeclarationNode classDeclaration new LemmsFunction(constructor)); } - @Override - public LemmsData visitMemberAccessValue(MemberAccessNode functionNode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'visitMemberAccessValue'"); + // Add this method to Interpreter.java + + public LemmsData visitMemberAccessValue(MemberAccessNode node) { + LemmsData obj = node.object.accept(this); + if (obj instanceof LemmsObject lo) { + LemmsData value = lo.environment.get(node.memberName); + if (value == null) { + throw new LemmsRuntimeException("Undefined member '" + node.memberName + "'."); + } + return value; + } + throw new LemmsRuntimeException("Cannot access member '" + node.memberName + "' of non-object."); } } \ No newline at end of file From bb447182346e0ce81d6dcbfc2e549940ab487c48 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 15:56:51 +0200 Subject: [PATCH 17/30] visit member access node adjustments --- .../lemms/SyntaxNode/MemberAccessNode.java | 10 +-- .../com/lemms/interpreter/Interpreter.java | 63 ++++++++++++++++--- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java index b06cec0..afc075c 100644 --- a/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java +++ b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java @@ -4,12 +4,14 @@ import com.lemms.interpreter.object.LemmsData; public class MemberAccessNode extends ExpressionNode { - public ExpressionNode object; - public String memberName; - public MemberAccessNode(ExpressionNode object, String memberName) { + public ExpressionNode object; // The base object (could be another MemberAccessNode, FunctionCallNode, + // VariableNode, etc.) + public MemberAccessNode child; // The next access/call in the chain, or null + + public MemberAccessNode(ExpressionNode object, MemberAccessNode child) { this.object = object; - this.memberName = memberName; + this.child = child; } @Override diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 8ebba1c..ec22709 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -116,6 +116,38 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { return FlowSignal.NORMAL; } + private static class LValue { + public Environment environment; + public String propertyName; + + public LValue(Environment env, String name) { + this.environment = env; + this.propertyName = name; + } + } +/* + private LValue resolveLValue(ExpressionNode node) { + if (node instanceof VariableNode varNode && varNode.child == null) { + return new LValue(environment, varNode.name); + } else if (node instanceof MemberAccessNode memberNode) { + LemmsData obj = memberNode.object.accept(this); + if (obj instanceof LemmsObject lo) { + return new LValue(lo.environment, memberNode.memberName); + } else { + throw new LemmsRuntimeException("Cannot assign to non-object member: " + memberNode.memberName); + } + } else if (node instanceof FunctionCallNode callNode) { + LemmsData result = callNode.accept(this); + // Next node in chain should be a MemberAccessNode + // (e.g., ee().ff) + // You may need to store the next node in CallNode or handle accordingly + // Example: + // return resolveLValue(new MemberAccessNode(result, ...)); + throw new LemmsRuntimeException("Assignment to function call result not supported directly."); + } + throw new LemmsRuntimeException("Invalid assignment target."); + } +*/ @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { LemmsData dataValue = assignmentNode.rightHandSide.accept(this); @@ -376,17 +408,28 @@ public void visitClassDeclarationStatement(ClassDeclarationNode classDeclaration new LemmsFunction(constructor)); } - // Add this method to Interpreter.java - + @Override public LemmsData visitMemberAccessValue(MemberAccessNode node) { - LemmsData obj = node.object.accept(this); - if (obj instanceof LemmsObject lo) { - LemmsData value = lo.environment.get(node.memberName); - if (value == null) { - throw new LemmsRuntimeException("Undefined member '" + node.memberName + "'."); - } - return value; + // Evaluate the current member (could be a variable or function call) + LemmsData current = node.object.accept(this); + + // If there is no further child, return the resolved value + if (node.child == null) { + return current; } - throw new LemmsRuntimeException("Cannot access member '" + node.memberName + "' of non-object."); + + // If the current value is not an object, we cannot access further members + if (!(current instanceof LemmsObject lo)) { + throw new LemmsRuntimeException("Cannot access member of non-object."); + } + + // Set the environment to the object's environment for the next access in the + // chain + Environment previousEnvironment = environment; + environment = lo.environment; + LemmsData result = visitMemberAccessValue(node.child); + environment = previousEnvironment; + return result; } + } \ No newline at end of file From 955a68c41f73ea81ff527b9f3f2130a89f046a2b Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 16:04:41 +0200 Subject: [PATCH 18/30] use class environment signal --- src/main/java/com/lemms/Lemms.java | 2 +- src/main/java/com/lemms/interpreter/Interpreter.java | 10 +++++++++- src/main/resources/successTests/classTest.lemms | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/lemms/Lemms.java b/src/main/java/com/lemms/Lemms.java index 1e37d1f..f7e83bd 100644 --- a/src/main/java/com/lemms/Lemms.java +++ b/src/main/java/com/lemms/Lemms.java @@ -44,7 +44,7 @@ public class Lemms { public static void main(String[] args) { try { String sourcePath = "foo/bar/sourcePath.example (später mit command-line-args ersetzen)"; - sourcePath = "src/main/resources/successTests/functionTest.lemms"; //String sourcePath = args[0]; + sourcePath = "src/main/resources/successTests/classTest.lemms"; //String sourcePath = args[0]; File sourceFile = new File(sourcePath); //Verknüpfung: Tokenizer + Parser + Interpreter diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index ec22709..2bc77f4 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -37,6 +37,8 @@ public class Interpreter implements StatementVisitor, ValueVisitor { TokenType.GT, TokenType.LT); + private boolean useClassEnvironmentSignal = false; + public Interpreter(List program) { this.program = program; nativeFunctions = new HashMap<>(); @@ -346,7 +348,9 @@ public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { .map(param -> param.accept(this)) .toList(); - Environment functionEnvironment = new Environment(globalEnvironment); + Environment functionEnvironment = new Environment(useClassEnvironmentSignal ? environment : globalEnvironment); + useClassEnvironmentSignal = false; + for (int i = 0; i < args.size(); i++) { String argName = functionDeclaration.paramNames.get(i); LemmsData argValue = args.get(i); @@ -364,6 +368,7 @@ public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { throw new RuntimeException("Function did not return a value: " + functionNode.functionName); } } + } @Override @@ -427,6 +432,9 @@ public LemmsData visitMemberAccessValue(MemberAccessNode node) { // chain Environment previousEnvironment = environment; environment = lo.environment; + if(node.child.object instanceof FunctionCallNode) { + useClassEnvironmentSignal = true; + } LemmsData result = visitMemberAccessValue(node.child); environment = previousEnvironment; return result; diff --git a/src/main/resources/successTests/classTest.lemms b/src/main/resources/successTests/classTest.lemms index 7a61aae..1618a53 100644 --- a/src/main/resources/successTests/classTest.lemms +++ b/src/main/resources/successTests/classTest.lemms @@ -7,4 +7,7 @@ class Human { function printHallo(){ print("hallo"); } -} \ No newline at end of file +} + +h = Human(27, "Hon"); +h.printHallo(); \ No newline at end of file From 8332c87b30d5e50d869865e38495cd2a6f4714e2 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 16:07:47 +0200 Subject: [PATCH 19/30] dot tokenizer --- src/main/java/com/lemms/TokenType.java | 3 ++- src/main/java/com/lemms/Tokenizer.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/lemms/TokenType.java b/src/main/java/com/lemms/TokenType.java index e834b7d..a6a2a83 100644 --- a/src/main/java/com/lemms/TokenType.java +++ b/src/main/java/com/lemms/TokenType.java @@ -40,5 +40,6 @@ public enum TokenType { IF, ELSE, - COMMA + COMMA, + DOT } \ No newline at end of file diff --git a/src/main/java/com/lemms/Tokenizer.java b/src/main/java/com/lemms/Tokenizer.java index 55e6b08..754bfd1 100644 --- a/src/main/java/com/lemms/Tokenizer.java +++ b/src/main/java/com/lemms/Tokenizer.java @@ -74,6 +74,7 @@ private void scanToken() { case '/': index++; addToken(DIVISION); return; case '%': index++; addToken(MODULO); return; case ',': index++; addToken(COMMA); return; + case '.': index++; addToken(DOT); return; case '#': index++; //evtl 2x incremte while (index + 1 <= input_file.length()) From 6ce795ec388308ae7a13b4d738583e1b313ef800 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Tue, 24 Jun 2025 16:21:02 +0200 Subject: [PATCH 20/30] initial proper member access expression parsing --- .../com/lemms/parser/ExpressionParser.java | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/lemms/parser/ExpressionParser.java b/src/main/java/com/lemms/parser/ExpressionParser.java index 80d94ed..60ce110 100644 --- a/src/main/java/com/lemms/parser/ExpressionParser.java +++ b/src/main/java/com/lemms/parser/ExpressionParser.java @@ -5,6 +5,7 @@ import com.lemms.SyntaxNode.ExpressionNode; import com.lemms.SyntaxNode.FunctionCallNode; import com.lemms.SyntaxNode.LiteralNode; +import com.lemms.SyntaxNode.MemberAccessNode; import com.lemms.SyntaxNode.OperatorNode; import com.lemms.SyntaxNode.VariableNode; import com.lemms.Token; @@ -184,7 +185,7 @@ private ExpressionNode parseAdditiveExpression() { private ExpressionNode parseMultiplicativeTerm() { EnumSet multiplicationTokenTypes = EnumSet.of(MULTIPLICATION, DIVISION, MODULO); logger.info("\n+++++ TERM PARSING +++++"); - ExpressionNode leftFactor = parseUnaryFactor(); + ExpressionNode leftFactor = parseMemberAccess(); Token current = peek(); while (current != null && multiplicationTokenTypes.contains(current.getType())) { @@ -202,6 +203,47 @@ private ExpressionNode parseMultiplicativeTerm() { return leftFactor; } + private ExpressionNode parseMemberAccess() { + // Parse the base object (variable, function call, or parenthesized expression) + ExpressionNode base = parseUnaryFactor(); + + MemberAccessNode head = null; + MemberAccessNode current = null; + + while (peek() != null && peek().getType() == TokenType.DOT) { + consume(); // consume '.' + + // Next must be identifier (possibly a function call) + Token next = peek(); + if (next == null || next.getType() != TokenType.IDENTIFIER) { + throw new UnexpectedToken("Expected identifier after '.'"); + } + + ExpressionNode member; + if (pos + 1 < tokens.size() && tokens.get(pos + 1).getType() == TokenType.BRACKET_OPEN) { + member = parseFunctionCall(); + } else { + Token memberToken = consume(); + member = new VariableNode(memberToken.getValue()); + } + + MemberAccessNode node = new MemberAccessNode(member, null); + + if (head == null) { + // First member access: head is created, base is the object + head = new MemberAccessNode(base, node); + current = head.child; + } else { + // Chain further accesses + current.child = node; + current = current.child; + } + } + + // If there was at least one dot, return the head node, else just the base + return head != null ? head : base; + } + private ExpressionNode parseUnaryFactor() { logger.info("\n+++++ FACTOR PARSING +++++"); Token current = peek(); @@ -256,7 +298,7 @@ private ExpressionNode parseUnaryFactor() { consume(); logger.info(identifierToken + "\n----- IDENTIFIER NODE CREATED -----"); return new VariableNode(identifierToken.getValue()); - } + } } // if bracketed, then parse new Expression (recursive call) From 06e70fb5e7a12214ab85e21fb9786bfc5f205dc2 Mon Sep 17 00:00:00 2001 From: Simone Esposito Date: Tue, 24 Jun 2025 22:09:19 +0200 Subject: [PATCH 21/30] added canvas functionalities including snake example with .lemms equivalent --- src/main/java/com/lemms/GUI/Canvas.java | 157 ++++++++++++++++++ src/main/java/com/lemms/GUI/CanvasPanel.java | 59 +++++++ src/main/java/com/lemms/GUI/Drawable.java | 7 + src/main/java/com/lemms/GUI/Pixel.java | 18 ++ src/main/java/com/lemms/GUI/Rect.java | 23 +++ src/main/java/com/lemms/GUI/RigidBody.java | 40 +++++ .../java/com/lemms/GUI/ScriptCallback.java | 5 + src/main/java/com/lemms/GUI/Text.java | 71 ++++++++ src/main/resources/canvas.lemms | 91 ++++++++++ 9 files changed, 471 insertions(+) create mode 100644 src/main/java/com/lemms/GUI/Canvas.java create mode 100644 src/main/java/com/lemms/GUI/CanvasPanel.java create mode 100644 src/main/java/com/lemms/GUI/Drawable.java create mode 100644 src/main/java/com/lemms/GUI/Pixel.java create mode 100644 src/main/java/com/lemms/GUI/Rect.java create mode 100644 src/main/java/com/lemms/GUI/RigidBody.java create mode 100644 src/main/java/com/lemms/GUI/ScriptCallback.java create mode 100644 src/main/java/com/lemms/GUI/Text.java create mode 100644 src/main/resources/canvas.lemms diff --git a/src/main/java/com/lemms/GUI/Canvas.java b/src/main/java/com/lemms/GUI/Canvas.java new file mode 100644 index 0000000..1a6f3e1 --- /dev/null +++ b/src/main/java/com/lemms/GUI/Canvas.java @@ -0,0 +1,157 @@ +package com.lemms.GUI; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Random; + +public class Canvas implements ActionListener { + + public final CanvasPanel panel; + public ScriptCallback onTick; + + private final JFrame frame; + private final Timer timer; + + + public Canvas(int tickRate, int width, int height) { + frame = new JFrame(); + panel = new CanvasPanel(); + frame.add(panel); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setSize(width, height); + frame.setVisible(true); + onTick = panel::repaint; + timer = new Timer(tickRate, this); + } + + public int getWidth() { + return frame.getWidth(); + } + + public int getHeight() { + return frame.getHeight(); + } + + public void start() { + timer.start(); + } + + public void stop() { + timer.stop(); + } + + public void onKeyPress(int key, ScriptCallback callback) { + panel.addKeyEvent(key, callback); + } + + @Override + public void actionPerformed(ActionEvent e) { + if ( e.getSource() == timer) + { + onTick.call(); + } + } + + public static void main(String[] args) { + //create canvas with single snake cell + Canvas canvas = new Canvas(500, 500,500); + ArrayList snake = new ArrayList<>(){}; + snake.add(new Rect(0,0, 20,20, Color.BLUE)); + + //generate food at random location + int gridWidth = canvas.getWidth()/20; + int gridHeight = canvas.getHeight()/20; + + Random random = new Random(); + int food_x = random.nextInt(gridWidth-1)*20; + int food_y = random.nextInt(gridHeight-1)*20; + Rect food = new Rect(food_x, food_y, 20, 20, Color.RED); + + canvas.start(); + canvas.panel.addElement(snake.get(0)); + canvas.panel.addElement(food); + + int[] dir = {1, 0}; + + canvas.onKeyPress(KeyEvent.VK_W, ()->{ + dir[0] = 0; + dir[1] = -1; + }); + canvas.onKeyPress(KeyEvent.VK_A, ()->{ + dir[0] = -1; + dir[1] = 0; + }); + canvas.onKeyPress(KeyEvent.VK_S, ()->{ + dir[0] = 0; + dir[1] = 1; + }); + canvas.onKeyPress(KeyEvent.VK_D, ()->{ + dir[0] = 1; + dir[1] = 0; + }); + + canvas.onTick = () -> { + // Compute next head position + Rect oldHead = snake.get(0); + int newX = oldHead.x + dir[0]*20; + int newY = oldHead.y + dir[1]*20; + Rect newHead = new Rect(newX, newY, 20, 20, Color.BLUE); + + // Check wall collision + RigidBody bounds = new RigidBody(0, 0, gridWidth*20, gridHeight*20); + if (!bounds.contains(newHead)) { + GameOver(canvas, snake, false); + return; + } + + // Self‐collision + for (Rect segment : snake) { + if (segment.x == newX && segment.y == newY) { + // game over + GameOver(canvas, snake, false); + return; + } + } + + // Add head to front + snake.add(0, newHead); + canvas.panel.addElement(newHead); + + boolean ate = newHead.intersects(food); + if (ate) { + // move the food + food.x = random.nextInt(gridWidth-1)*20; + food.y = random.nextInt(gridHeight-1)*20; + + if(snake.size() == gridHeight*gridWidth) { + GameOver(canvas, snake, true); + } + } + + if (!ate) { + Rect tail = snake.remove(snake.size() - 1); + canvas.panel.removeElement(tail); + } + + canvas.panel.repaint(); + }; + } + + private static void GameOver(Canvas canvas, ArrayList snake, boolean won) { + canvas.panel.clearElements(); + snake.clear(); + String text = won ? "Game Won!" : "Game Over!"; + Text GameOver = new Text(text,canvas.getWidth()/2, canvas.getHeight()/2, 72, + Color.BLACK); + GameOver.align(0,0); + canvas.panel.addElement(GameOver); + canvas.panel.repaint(); + canvas.stop(); + } +} diff --git a/src/main/java/com/lemms/GUI/CanvasPanel.java b/src/main/java/com/lemms/GUI/CanvasPanel.java new file mode 100644 index 0000000..ff97f52 --- /dev/null +++ b/src/main/java/com/lemms/GUI/CanvasPanel.java @@ -0,0 +1,59 @@ +package com.lemms.GUI; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class CanvasPanel extends JPanel implements KeyListener { + private List elements = new ArrayList<>(); + private HashMap keyEvents = new HashMap<>(); + + public CanvasPanel() { + setFocusable(true); + requestFocusInWindow(); + addKeyListener(this); + } + public void addElement(Drawable d) { + elements.add(d); + } + + public void removeElement(Drawable d) { + elements.remove(d); + } + + public void clearElements() { + elements.clear(); + } + + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + for (Drawable d : elements) { + d.draw(g); + } + } + + public void addKeyEvent(int key, ScriptCallback callback) { + keyEvents.put(key, callback); + } + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if(keyEvents.containsKey(e.getKeyCode())) { + keyEvents.get(e.getKeyCode()).call(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } +} diff --git a/src/main/java/com/lemms/GUI/Drawable.java b/src/main/java/com/lemms/GUI/Drawable.java new file mode 100644 index 0000000..d4b9d25 --- /dev/null +++ b/src/main/java/com/lemms/GUI/Drawable.java @@ -0,0 +1,7 @@ +package com.lemms.GUI; + +import java.awt.*; + +public interface Drawable { + void draw(Graphics g); +} diff --git a/src/main/java/com/lemms/GUI/Pixel.java b/src/main/java/com/lemms/GUI/Pixel.java new file mode 100644 index 0000000..55a0e30 --- /dev/null +++ b/src/main/java/com/lemms/GUI/Pixel.java @@ -0,0 +1,18 @@ +package com.lemms.GUI; + +import java.awt.*; + +public class Pixel implements Drawable { + int x, y; + Color color; + + public Pixel(int x, int y, Color color) { + this.x = x; this.y = y; this.color = color; + } + + @Override + public void draw(Graphics g) { + g.setColor(color); + g.fillRect(x, y, 1, 1); // Or larger if needed + } +} \ No newline at end of file diff --git a/src/main/java/com/lemms/GUI/Rect.java b/src/main/java/com/lemms/GUI/Rect.java new file mode 100644 index 0000000..37b2f61 --- /dev/null +++ b/src/main/java/com/lemms/GUI/Rect.java @@ -0,0 +1,23 @@ +package com.lemms.GUI; + +import java.awt.*; + +public class Rect extends RigidBody implements Drawable { + Color color; + + public Rect(int x, int y, int w, int h, Color color) { + super(x, y, w, h); + this.color = color; + } + + @Override + public void draw(Graphics g) { + g.setColor(color); + g.fillRect(getX(), getY(), getWidth(), getHeight()); + } + + public int setX(int x) { + this.x = x; + return x; + } +} diff --git a/src/main/java/com/lemms/GUI/RigidBody.java b/src/main/java/com/lemms/GUI/RigidBody.java new file mode 100644 index 0000000..1eab486 --- /dev/null +++ b/src/main/java/com/lemms/GUI/RigidBody.java @@ -0,0 +1,40 @@ +package com.lemms.GUI; + +public class RigidBody { + public int x; + public int y; + public int width; + public int height; + + public RigidBody(int x, int y, int width, int height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } + + public int getX() {return x;} + public int getY() {return y;} + public int getWidth() {return width;} + public int getHeight() {return height;} + + public boolean intersects(RigidBody other) { + if (other == null) { + return false; + } + return this.x < other.x + other.width + && this.x + this.width > other.x + && this.y < other.y + other.height + && this.y + this.height > other.y; + } + + public boolean contains(RigidBody other) { + if (other == null) { + return false; + } + return other.x >= this.x + && other.y >= this.y + && other.x + other.width <= this.x + this.width + && other.y + other.height <= this.y + this.height; + } +} diff --git a/src/main/java/com/lemms/GUI/ScriptCallback.java b/src/main/java/com/lemms/GUI/ScriptCallback.java new file mode 100644 index 0000000..0fd0bbb --- /dev/null +++ b/src/main/java/com/lemms/GUI/ScriptCallback.java @@ -0,0 +1,5 @@ +package com.lemms.GUI; + +public interface ScriptCallback { + void call(); +} diff --git a/src/main/java/com/lemms/GUI/Text.java b/src/main/java/com/lemms/GUI/Text.java new file mode 100644 index 0000000..dc88c65 --- /dev/null +++ b/src/main/java/com/lemms/GUI/Text.java @@ -0,0 +1,71 @@ +package com.lemms.GUI; + +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; + +public class Text implements Drawable { + private String text; + private int x; + private int y; + private int size; + private Color color; + + // Alignment factors: -1 = top/left, 0 = center, +1 = bottom/right + private float alignX = -1f; + private float alignY = -1f; + + public Text(String text, int x, int y, int size, Color color) { + this.text = text; + this.x = x; + this.y = y; + this.size = size; + this.color = color; + } + + public void align(float alignX, float alignY) { + this.alignX = alignX; + this.alignY = alignY; + } + + public String getText() { return text; } + public int getX() { return x; } + public int getY() { return y; } + public int getSize() { return size; } + public Color getColor() { return color; } + + public void setText(String text) { this.text = text; } + public void setX(int x) { this.x = x; } + public void setY(int y) { this.y = y; } + public void setSize(int size) { this.size = size; } + public void setColor(Color color) { this.color = color; } + + public void draw(Graphics g) { + Font oldFont = g.getFont(); + Color oldColor = g.getColor(); + + // Apply font size + Font newFont = oldFont.deriveFont((float) size); + g.setFont(newFont); + g.setColor(color); + + // Measure text + FontMetrics fm = g.getFontMetrics(newFont); + int textWidth = fm.stringWidth(text); + int textHeight = fm.getHeight(); + + // Compute offsets: (-1 -> 0, 0 -> 0.5, +1 -> 1) + float fx = (alignX + 1f) / 2f; + float fy = (alignY + 1f) / 2f; + int offsetX = Math.round(fx * textWidth); + int offsetY = Math.round(fy * textHeight); + + // Draw at reference point minus offset + g.drawString(text, x - offsetX, y - offsetY); + + // Restore graphics state + g.setFont(oldFont); + g.setColor(oldColor); + } +} \ No newline at end of file diff --git a/src/main/resources/canvas.lemms b/src/main/resources/canvas.lemms new file mode 100644 index 0000000..c1b78de --- /dev/null +++ b/src/main/resources/canvas.lemms @@ -0,0 +1,91 @@ +canvas = Canvas(500, 500, 500); +snake = [Rect(0,0,20,20,"blue")]; + +grid_w = canvas.width/20; +grid_h = canvas.height/20; + +food_x = randint(grid_w-1)*20; +food_y = randint(grid_h-1)*20; +food = Rect(food_x, food_y, 20, 20, "red"); + +canvas.run(); +canvas.add(snake[0]) +canvas.add(food) + +dir = [1, 0] + +canvas.on_key_pressed("W", { + dir[0] = -1; + dir[1] = 0; +}); + +canvas.on_key_pressed("A", { + dir[0] = -1; + dir[1] = 0; +}); + +canvas.on_key_pressed("S", { + dir[0] = -1; + dir[1] = 0; +}); + +canvas.on_key_pressed("D", { + dir[0] = -1; + dir[1] = 0; +}); + +canvas.update = { + Rect old_head = snake[0]; + new_x = old_head.x + dir[0]*20; + new_y = old_head.y + dir[1]*20; + new_head = Rect(new_x, new_y, 20, 20, "blue"); + + if(!new_head.inside(canvas.bounds)){ + game_over(); + } + + i = 0 + while(i < snake.size()){ + if(snake[i].x == new_x && snake[i].x == new_y){ + game_over(); + } + } + + snake.add(0, new_head) + bool ate = new_head.touches(food); + + if(ate) { + food_x = randint(grid_w-1)*20; + food_y = randint(grid_h-1)*20; + + if(snake.size == grid_h*grid_w){ + game_won(); + } + } + + if(!ate){ + tail = snake.remove(snake.size - 1); + canvas.remove(tail) + } + + canvas.redraw(); +}; + +function game_over(){ + canvas.clear(); + snake.clear(); + text = Text("Game Over", canvas.width/2, canvas.height/2, 72, "black"); + canvas.add(text); + canvas.redraw(); + canvas.stop(); +} + +function game_won(){ + canvas.clear(); + snake.clear(); + text = Text("Game Won", canvas.width/2, canvas.height/2, 72, "black"); + canvas.add(text); + canvas.redraw(); + canvas.stop(); +} + From 862bb094b47da85f20de7d558702d6ec769aa8fe Mon Sep 17 00:00:00 2001 From: Simone Esposito Date: Wed, 25 Jun 2025 01:24:55 +0200 Subject: [PATCH 22/30] Canvas API organisation and Package rename --- .idea/misc.xml | 3 +- src/main/java/com/lemms/Canvas/Canvas.java | 68 ++++++++++++++ .../lemms/{GUI => Canvas}/CanvasPanel.java | 16 ++-- .../java/com/lemms/Canvas/CanvasTest.java | 32 ------- .../java/com/lemms/Canvas/Collidable.java | 11 +++ .../com/lemms/{GUI => Canvas}/Drawable.java | 2 +- src/main/java/com/lemms/Canvas/Pixel.java | 26 ++++++ src/main/java/com/lemms/Canvas/Rect.java | 60 ++++++++++++ .../lemms/{GUI => Canvas}/ScriptCallback.java | 2 +- .../{GUI/Canvas.java => Canvas/Snake.java} | 92 ++++--------------- .../java/com/lemms/{GUI => Canvas}/Text.java | 4 +- src/main/java/com/lemms/GUI/Pixel.java | 18 ---- src/main/java/com/lemms/GUI/Rect.java | 23 ----- src/main/java/com/lemms/GUI/RigidBody.java | 40 -------- 14 files changed, 200 insertions(+), 197 deletions(-) create mode 100644 src/main/java/com/lemms/Canvas/Canvas.java rename src/main/java/com/lemms/{GUI => Canvas}/CanvasPanel.java (83%) delete mode 100644 src/main/java/com/lemms/Canvas/CanvasTest.java create mode 100644 src/main/java/com/lemms/Canvas/Collidable.java rename src/main/java/com/lemms/{GUI => Canvas}/Drawable.java (75%) create mode 100644 src/main/java/com/lemms/Canvas/Pixel.java create mode 100644 src/main/java/com/lemms/Canvas/Rect.java rename src/main/java/com/lemms/{GUI => Canvas}/ScriptCallback.java (67%) rename src/main/java/com/lemms/{GUI/Canvas.java => Canvas/Snake.java} (54%) rename src/main/java/com/lemms/{GUI => Canvas}/Text.java (97%) delete mode 100644 src/main/java/com/lemms/GUI/Pixel.java delete mode 100644 src/main/java/com/lemms/GUI/Rect.java delete mode 100644 src/main/java/com/lemms/GUI/RigidBody.java diff --git a/.idea/misc.xml b/.idea/misc.xml index ed2bc3c..0a13117 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,3 +1,4 @@ + @@ -7,7 +8,7 @@ - + \ No newline at end of file diff --git a/src/main/java/com/lemms/Canvas/Canvas.java b/src/main/java/com/lemms/Canvas/Canvas.java new file mode 100644 index 0000000..eccf57c --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Canvas.java @@ -0,0 +1,68 @@ +package com.lemms.Canvas; + +import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class Canvas implements ActionListener { + + public ScriptCallback update; + + private final CanvasPanel panel; + private final JFrame frame; + private final Timer timer; + + public Canvas(int tickRate, int width, int height) { + frame = new JFrame(); + panel = new CanvasPanel(); + frame.add(panel); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setSize(width, height); + frame.setVisible(true); + update = panel::repaint; + timer = new Timer(tickRate, this); + } + + public int getWidth() { + return frame.getWidth(); + } + + public int getHeight() { + return frame.getHeight(); + } + + public Rect getBounds() {return new Rect(0,0, getWidth(), getHeight(), null);} + + public void run() { + timer.start(); + } + + public void quit() { + timer.stop(); + } + + public void add(Drawable drawable) { + panel.addElement(drawable); + } + + public void remove(Drawable drawable) {panel.removeElement(drawable);} + + public void clear() {panel.clearElements();} + + public void repaint() {panel.repaint();} + + public void onKeyPress(int key, ScriptCallback callback) { + panel.addKeyEvent(key, callback); + } + + @Override + public void actionPerformed(ActionEvent e) { + if ( e.getSource() == timer) + { + update.call(); + } + } + +} diff --git a/src/main/java/com/lemms/GUI/CanvasPanel.java b/src/main/java/com/lemms/Canvas/CanvasPanel.java similarity index 83% rename from src/main/java/com/lemms/GUI/CanvasPanel.java rename to src/main/java/com/lemms/Canvas/CanvasPanel.java index ff97f52..dca409c 100644 --- a/src/main/java/com/lemms/GUI/CanvasPanel.java +++ b/src/main/java/com/lemms/Canvas/CanvasPanel.java @@ -1,24 +1,23 @@ -package com.lemms.GUI; +package com.lemms.Canvas; import javax.swing.*; import java.awt.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public class CanvasPanel extends JPanel implements KeyListener { - private List elements = new ArrayList<>(); - private HashMap keyEvents = new HashMap<>(); + private final List elements = new ArrayList<>(); + private final HashMap keyEvents = new HashMap<>(); public CanvasPanel() { setFocusable(true); requestFocusInWindow(); addKeyListener(this); } + public void addElement(Drawable d) { elements.add(d); } @@ -31,6 +30,10 @@ public void clearElements() { elements.clear(); } + public void addKeyEvent(int key, ScriptCallback callback) { + keyEvents.put(key, callback); + } + @Override protected void paintComponent(Graphics g) { super.paintComponent(g); @@ -39,9 +42,6 @@ protected void paintComponent(Graphics g) { } } - public void addKeyEvent(int key, ScriptCallback callback) { - keyEvents.put(key, callback); - } @Override public void keyTyped(KeyEvent e) { } diff --git a/src/main/java/com/lemms/Canvas/CanvasTest.java b/src/main/java/com/lemms/Canvas/CanvasTest.java deleted file mode 100644 index 5b4fd25..0000000 --- a/src/main/java/com/lemms/Canvas/CanvasTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.lemms.Canvas; - - -import java.awt.*; -import java.awt.Canvas; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Frame; - -public class CanvasTest { - public static void main(String[] args) { - Frame frame = new Frame("Mein Canvas Fenster"); - frame.setSize(400, 300); //Fenster - - MeinCanvas canvas = new MeinCanvas(); - canvas.setSize(400, 300); - canvas.setBackground(Color.WHITE); - - frame.add(canvas); - frame.setVisible(true); - } - - static class MeinCanvas extends Canvas { - @Override - public void paint(Graphics g) { - g.setColor(Color.BLUE); - g.fillRect(50, 50, 100, 100); // Ein blaues Quadrat zeichnen - } - } -} - - diff --git a/src/main/java/com/lemms/Canvas/Collidable.java b/src/main/java/com/lemms/Canvas/Collidable.java new file mode 100644 index 0000000..0c4a2bd --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Collidable.java @@ -0,0 +1,11 @@ +package com.lemms.Canvas; + +public interface Collidable { + int getX(); + int getY(); + int getWidth(); + int getHeight(); + + boolean intersects(Collidable other); + boolean contains(Collidable other); +} diff --git a/src/main/java/com/lemms/GUI/Drawable.java b/src/main/java/com/lemms/Canvas/Drawable.java similarity index 75% rename from src/main/java/com/lemms/GUI/Drawable.java rename to src/main/java/com/lemms/Canvas/Drawable.java index d4b9d25..bf3a33d 100644 --- a/src/main/java/com/lemms/GUI/Drawable.java +++ b/src/main/java/com/lemms/Canvas/Drawable.java @@ -1,4 +1,4 @@ -package com.lemms.GUI; +package com.lemms.Canvas; import java.awt.*; diff --git a/src/main/java/com/lemms/Canvas/Pixel.java b/src/main/java/com/lemms/Canvas/Pixel.java new file mode 100644 index 0000000..7dcf787 --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Pixel.java @@ -0,0 +1,26 @@ +package com.lemms.Canvas; + +import java.awt.*; + +public class Pixel implements Drawable { + private int x, y; + private Color color; + + public Pixel(int x, int y, Color color) { + this.x = x; this.y = y; this.color = color; + } + + public int getX() {return x;} + public int getY() {return y;} + public Color getColor() {return color;} + + public void setX(int x) {this.x = x;} + public void setY(int y) {this.y = y;} + public void setColor(Color color) {this.color = color;} + + @Override + public void draw(Graphics g) { + g.setColor(color); + g.fillRect(x, y, 1, 1); // Or larger if needed + } +} \ No newline at end of file diff --git a/src/main/java/com/lemms/Canvas/Rect.java b/src/main/java/com/lemms/Canvas/Rect.java new file mode 100644 index 0000000..613505c --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Rect.java @@ -0,0 +1,60 @@ +package com.lemms.Canvas; + +import java.awt.*; + +public class Rect implements Drawable, Collidable { + private int x, y, width, height; + private Color color; + + public Rect(int x, int y, int w, int h, Color color) { + this.x = x; + this.y = y; + this.width = w; + this.height = h; + this.color = color; + } + + @Override + public void draw(Graphics g) { + g.setColor(color); + g.fillRect(x, y, width, height); + } + + public Color getColor() {return color;} + + @Override + public int getX() {return x;} + @Override + public int getY() {return y;} + @Override + public int getWidth() {return width;} + @Override + public int getHeight() {return height;} + + public void setX(int x) {this.x = x;} + public void setY(int y) {this.y = y;} + public void setWidth(int w) {this.width = w;} + public void setHeight(int h) {this.height = h;} + + @Override + public boolean intersects(Collidable other) { + if (other == null) { + return false; + } + return this.x < other.getX() + other.getWidth() + && this.x + this.width > other.getX() + && this.y < other.getY() + other.getHeight() + && this.y + this.height > other.getY(); + } + + @Override + public boolean contains(Collidable other) { + if (other == null) { + return false; + } + return other.getX() >= this.x + && other.getY() >= this.y + && other.getX() + other.getWidth() <= this.x + this.width + && other.getY() + other.getWidth() <= this.y + this.height; + } +} diff --git a/src/main/java/com/lemms/GUI/ScriptCallback.java b/src/main/java/com/lemms/Canvas/ScriptCallback.java similarity index 67% rename from src/main/java/com/lemms/GUI/ScriptCallback.java rename to src/main/java/com/lemms/Canvas/ScriptCallback.java index 0fd0bbb..2e0c5a1 100644 --- a/src/main/java/com/lemms/GUI/ScriptCallback.java +++ b/src/main/java/com/lemms/Canvas/ScriptCallback.java @@ -1,4 +1,4 @@ -package com.lemms.GUI; +package com.lemms.Canvas; public interface ScriptCallback { void call(); diff --git a/src/main/java/com/lemms/GUI/Canvas.java b/src/main/java/com/lemms/Canvas/Snake.java similarity index 54% rename from src/main/java/com/lemms/GUI/Canvas.java rename to src/main/java/com/lemms/Canvas/Snake.java index 1a6f3e1..7798243 100644 --- a/src/main/java/com/lemms/GUI/Canvas.java +++ b/src/main/java/com/lemms/Canvas/Snake.java @@ -1,63 +1,11 @@ -package com.lemms.GUI; +package com.lemms.Canvas; -import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Random; -public class Canvas implements ActionListener { - - public final CanvasPanel panel; - public ScriptCallback onTick; - - private final JFrame frame; - private final Timer timer; - - - public Canvas(int tickRate, int width, int height) { - frame = new JFrame(); - panel = new CanvasPanel(); - frame.add(panel); - frame.pack(); - frame.setLocationRelativeTo(null); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - frame.setSize(width, height); - frame.setVisible(true); - onTick = panel::repaint; - timer = new Timer(tickRate, this); - } - - public int getWidth() { - return frame.getWidth(); - } - - public int getHeight() { - return frame.getHeight(); - } - - public void start() { - timer.start(); - } - - public void stop() { - timer.stop(); - } - - public void onKeyPress(int key, ScriptCallback callback) { - panel.addKeyEvent(key, callback); - } - - @Override - public void actionPerformed(ActionEvent e) { - if ( e.getSource() == timer) - { - onTick.call(); - } - } - +public class Snake { public static void main(String[] args) { //create canvas with single snake cell Canvas canvas = new Canvas(500, 500,500); @@ -73,9 +21,9 @@ public static void main(String[] args) { int food_y = random.nextInt(gridHeight-1)*20; Rect food = new Rect(food_x, food_y, 20, 20, Color.RED); - canvas.start(); - canvas.panel.addElement(snake.get(0)); - canvas.panel.addElement(food); + canvas.run(); + canvas.add(snake.get(0)); + canvas.add(food); int[] dir = {1, 0}; @@ -96,23 +44,22 @@ public static void main(String[] args) { dir[1] = 0; }); - canvas.onTick = () -> { + canvas.update = () -> { // Compute next head position Rect oldHead = snake.get(0); - int newX = oldHead.x + dir[0]*20; - int newY = oldHead.y + dir[1]*20; + int newX = oldHead.getX() + dir[0]*20; + int newY = oldHead.getY() + dir[1]*20; Rect newHead = new Rect(newX, newY, 20, 20, Color.BLUE); // Check wall collision - RigidBody bounds = new RigidBody(0, 0, gridWidth*20, gridHeight*20); - if (!bounds.contains(newHead)) { + if (!canvas.getBounds().contains(newHead)) { GameOver(canvas, snake, false); return; } // Self‐collision for (Rect segment : snake) { - if (segment.x == newX && segment.y == newY) { + if (segment.getX() == newX && segment.getY() == newY) { // game over GameOver(canvas, snake, false); return; @@ -121,13 +68,13 @@ public static void main(String[] args) { // Add head to front snake.add(0, newHead); - canvas.panel.addElement(newHead); + canvas.add(newHead); boolean ate = newHead.intersects(food); if (ate) { // move the food - food.x = random.nextInt(gridWidth-1)*20; - food.y = random.nextInt(gridHeight-1)*20; + food.setX(random.nextInt(gridWidth-1)*20); + food.setY(random.nextInt(gridHeight-1)*20); if(snake.size() == gridHeight*gridWidth) { GameOver(canvas, snake, true); @@ -136,22 +83,23 @@ public static void main(String[] args) { if (!ate) { Rect tail = snake.remove(snake.size() - 1); - canvas.panel.removeElement(tail); + canvas.remove(tail); } - canvas.panel.repaint(); + canvas.repaint(); }; } private static void GameOver(Canvas canvas, ArrayList snake, boolean won) { - canvas.panel.clearElements(); + canvas.clear(); snake.clear(); String text = won ? "Game Won!" : "Game Over!"; Text GameOver = new Text(text,canvas.getWidth()/2, canvas.getHeight()/2, 72, Color.BLACK); GameOver.align(0,0); - canvas.panel.addElement(GameOver); - canvas.panel.repaint(); - canvas.stop(); + canvas.add(GameOver); + canvas.repaint(); + canvas.quit(); } + } diff --git a/src/main/java/com/lemms/GUI/Text.java b/src/main/java/com/lemms/Canvas/Text.java similarity index 97% rename from src/main/java/com/lemms/GUI/Text.java rename to src/main/java/com/lemms/Canvas/Text.java index dc88c65..00bbf86 100644 --- a/src/main/java/com/lemms/GUI/Text.java +++ b/src/main/java/com/lemms/Canvas/Text.java @@ -1,4 +1,4 @@ -package com.lemms.GUI; +package com.lemms.Canvas; import java.awt.Color; import java.awt.Font; @@ -29,6 +29,8 @@ public void align(float alignX, float alignY) { this.alignY = alignY; } + // Getter and Setter + public String getText() { return text; } public int getX() { return x; } public int getY() { return y; } diff --git a/src/main/java/com/lemms/GUI/Pixel.java b/src/main/java/com/lemms/GUI/Pixel.java deleted file mode 100644 index 55a0e30..0000000 --- a/src/main/java/com/lemms/GUI/Pixel.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.lemms.GUI; - -import java.awt.*; - -public class Pixel implements Drawable { - int x, y; - Color color; - - public Pixel(int x, int y, Color color) { - this.x = x; this.y = y; this.color = color; - } - - @Override - public void draw(Graphics g) { - g.setColor(color); - g.fillRect(x, y, 1, 1); // Or larger if needed - } -} \ No newline at end of file diff --git a/src/main/java/com/lemms/GUI/Rect.java b/src/main/java/com/lemms/GUI/Rect.java deleted file mode 100644 index 37b2f61..0000000 --- a/src/main/java/com/lemms/GUI/Rect.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.lemms.GUI; - -import java.awt.*; - -public class Rect extends RigidBody implements Drawable { - Color color; - - public Rect(int x, int y, int w, int h, Color color) { - super(x, y, w, h); - this.color = color; - } - - @Override - public void draw(Graphics g) { - g.setColor(color); - g.fillRect(getX(), getY(), getWidth(), getHeight()); - } - - public int setX(int x) { - this.x = x; - return x; - } -} diff --git a/src/main/java/com/lemms/GUI/RigidBody.java b/src/main/java/com/lemms/GUI/RigidBody.java deleted file mode 100644 index 1eab486..0000000 --- a/src/main/java/com/lemms/GUI/RigidBody.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.lemms.GUI; - -public class RigidBody { - public int x; - public int y; - public int width; - public int height; - - public RigidBody(int x, int y, int width, int height) { - this.x = x; - this.y = y; - this.width = width; - this.height = height; - } - - public int getX() {return x;} - public int getY() {return y;} - public int getWidth() {return width;} - public int getHeight() {return height;} - - public boolean intersects(RigidBody other) { - if (other == null) { - return false; - } - return this.x < other.x + other.width - && this.x + this.width > other.x - && this.y < other.y + other.height - && this.y + this.height > other.y; - } - - public boolean contains(RigidBody other) { - if (other == null) { - return false; - } - return other.x >= this.x - && other.y >= this.y - && other.x + other.width <= this.x + this.width - && other.y + other.height <= this.y + this.height; - } -} From 0ea0451eabfb3a65dc55724170775a9e67789dd3 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 12:56:34 +0200 Subject: [PATCH 23/30] basic changes for creating member access nodes --- src/main/java/com/lemms/Lemms.java | 7 ++----- src/main/java/com/lemms/SyntaxNode/AssignmentNode.java | 4 ++-- src/main/java/com/lemms/SyntaxNode/VariableNode.java | 8 +------- src/main/java/com/lemms/interpreter/Interpreter.java | 2 ++ src/main/java/com/lemms/parser/ExpressionParser.java | 2 +- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/lemms/Lemms.java b/src/main/java/com/lemms/Lemms.java index f7e83bd..bc7d931 100644 --- a/src/main/java/com/lemms/Lemms.java +++ b/src/main/java/com/lemms/Lemms.java @@ -50,11 +50,8 @@ public static void main(String[] args) { //Verknüpfung: Tokenizer + Parser + Interpreter Tokenizer t = new Tokenizer(sourceFile); Parser p = new Parser(t.getTokens()); - // System.out.println(p.parse()); - Interpreter i = new Interpreter(p.parse()); - - i.interpret(); - + + System.out.println(p.parse()); //System.out.println(p.getAST()); diff --git a/src/main/java/com/lemms/SyntaxNode/AssignmentNode.java b/src/main/java/com/lemms/SyntaxNode/AssignmentNode.java index 7e587a6..25ae3f7 100644 --- a/src/main/java/com/lemms/SyntaxNode/AssignmentNode.java +++ b/src/main/java/com/lemms/SyntaxNode/AssignmentNode.java @@ -5,10 +5,10 @@ import com.lemms.interpreter.StatementVisitor; public class AssignmentNode extends StatementNode { - public VariableNode leftHandSide; + public ExpressionNode leftHandSide; public ExpressionNode rightHandSide; - public AssignmentNode(VariableNode identifier, ExpressionNode expression) { + public AssignmentNode(ExpressionNode identifier, ExpressionNode expression) { this.leftHandSide = identifier; this.rightHandSide = expression; } diff --git a/src/main/java/com/lemms/SyntaxNode/VariableNode.java b/src/main/java/com/lemms/SyntaxNode/VariableNode.java index ef5b827..be9e8f0 100644 --- a/src/main/java/com/lemms/SyntaxNode/VariableNode.java +++ b/src/main/java/com/lemms/SyntaxNode/VariableNode.java @@ -7,17 +7,11 @@ public class VariableNode extends ExpressionNode { public String name; - public VariableNode child; public VariableNode(String identifierName) { this.name = identifierName; } - - public VariableNode(String identifierName, VariableNode child) { - this.name = identifierName; - this.child = child; - } - + @Override public LemmsData accept(ValueVisitor visitor) { return visitor.visitVariableValue(this); diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index 2bc77f4..a8dc944 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -152,6 +152,7 @@ private LValue resolveLValue(ExpressionNode node) { */ @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { + /* TODO assignment LemmsData dataValue = assignmentNode.rightHandSide.accept(this); Environment targetEnvironment = environment; VariableNode targetNode = assignmentNode.leftHandSide; @@ -165,6 +166,7 @@ public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { } } targetEnvironment.assign(targetNode.name, dataValue); + */ return FlowSignal.NORMAL; } diff --git a/src/main/java/com/lemms/parser/ExpressionParser.java b/src/main/java/com/lemms/parser/ExpressionParser.java index 60ce110..7f558ff 100644 --- a/src/main/java/com/lemms/parser/ExpressionParser.java +++ b/src/main/java/com/lemms/parser/ExpressionParser.java @@ -185,7 +185,7 @@ private ExpressionNode parseAdditiveExpression() { private ExpressionNode parseMultiplicativeTerm() { EnumSet multiplicationTokenTypes = EnumSet.of(MULTIPLICATION, DIVISION, MODULO); logger.info("\n+++++ TERM PARSING +++++"); - ExpressionNode leftFactor = parseMemberAccess(); + ExpressionNode leftFactor = parseUnaryFactor(); Token current = peek(); while (current != null && multiplicationTokenTypes.contains(current.getType())) { From aaa47ce783edb672730ab2adbf6dba92b92bb797 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 16:55:47 +0200 Subject: [PATCH 24/30] fix assignment --- src/main/java/com/lemms/parser/Parser.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/lemms/parser/Parser.java b/src/main/java/com/lemms/parser/Parser.java index b1f7a5f..7753b7b 100644 --- a/src/main/java/com/lemms/parser/Parser.java +++ b/src/main/java/com/lemms/parser/Parser.java @@ -96,6 +96,7 @@ private IfNode parseIfStatement() { private AssignmentNode parseAssignment() { Token identifier = previous(); + Token equalsToken = consume(TokenType.ASSIGNMENT, "Expected '=' after identifier."); ExpressionNode expr = parseExpression(); consume(TokenType.SEMICOLON, "Expected ';' after assignment."); return new AssignmentNode(new VariableNode(identifier.getValue()), expr); From b1573affef0e68f20fdba58d9ea0e2db03aeaa33 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 17:36:30 +0200 Subject: [PATCH 25/30] better parsing succession --- src/main/java/com/lemms/Token.java | 6 + .../com/lemms/parser/ExpressionParser.java | 124 ++++++++++-------- 2 files changed, 74 insertions(+), 56 deletions(-) diff --git a/src/main/java/com/lemms/Token.java b/src/main/java/com/lemms/Token.java index 3a9b329..26a98ee 100644 --- a/src/main/java/com/lemms/Token.java +++ b/src/main/java/com/lemms/Token.java @@ -17,6 +17,12 @@ public Token(TokenType type, String value, int line) { this.line = line; } + public Token(TokenType type, String value) { + this.type = type; + this.value = value; + this.line = 0; + } + public Token(TokenType type) { this.type = type; this.value = null; diff --git a/src/main/java/com/lemms/parser/ExpressionParser.java b/src/main/java/com/lemms/parser/ExpressionParser.java index 7f558ff..d5b76c4 100644 --- a/src/main/java/com/lemms/parser/ExpressionParser.java +++ b/src/main/java/com/lemms/parser/ExpressionParser.java @@ -203,45 +203,78 @@ private ExpressionNode parseMultiplicativeTerm() { return leftFactor; } + private ExpressionNode parseBaseFactor() { + Token current = peek(); + if (current == null) { + throw new UnexpectedToken("Unexpected end of input"); + } + + switch (current.getType()) { + case IDENTIFIER -> { + if (pos + 1 < tokens.size() && tokens.get(pos + 1).getType() == TokenType.BRACKET_OPEN) { + return parseFunctionCall(); + } else { + Token token = consume(); + return new VariableNode(token.getValue()); + } + } + case BRACKET_OPEN -> { + consume(); // consume '(' + ExpressionNode expr = parseExpression(); + if (peek() == null || peek().getType() != TokenType.BRACKET_CLOSED) { + throw new MissingTokenException("Expected ')'"); + } + consume(); // consume ')' + return expr; + } + default -> throw new UnexpectedToken("Unexpected token: " + current.getType()); + } + } + private ExpressionNode parseMemberAccess() { - // Parse the base object (variable, function call, or parenthesized expression) - ExpressionNode base = parseUnaryFactor(); + ExpressionNode base = parseBaseFactor(); + + // If no dot follows, just return the base factor + if (peek() == null || peek().getType() != TokenType.DOT) { + return base; + } - MemberAccessNode head = null; + // Build the member access chain + MemberAccessNode root = null; MemberAccessNode current = null; while (peek() != null && peek().getType() == TokenType.DOT) { consume(); // consume '.' - // Next must be identifier (possibly a function call) - Token next = peek(); - if (next == null || next.getType() != TokenType.IDENTIFIER) { + Token memberToken = consume(); + if (memberToken.getType() != TokenType.IDENTIFIER) { throw new UnexpectedToken("Expected identifier after '.'"); } - ExpressionNode member; - if (pos + 1 < tokens.size() && tokens.get(pos + 1).getType() == TokenType.BRACKET_OPEN) { - member = parseFunctionCall(); + ExpressionNode memberExpression; + if (peek() != null && peek().getType() == TokenType.BRACKET_OPEN) { + // Backtrack and parse as function call + pos--; + memberExpression = parseFunctionCall(); } else { - Token memberToken = consume(); - member = new VariableNode(memberToken.getValue()); + memberExpression = new VariableNode(memberToken.getValue()); } - MemberAccessNode node = new MemberAccessNode(member, null); + // Create new MemberAccessNode for this access + MemberAccessNode newAccess = new MemberAccessNode(memberExpression, null); - if (head == null) { - // First member access: head is created, base is the object - head = new MemberAccessNode(base, node); - current = head.child; + if (root == null) { + // First member access - base becomes the object + root = new MemberAccessNode(base, newAccess); + current = root; } else { - // Chain further accesses - current.child = node; - current = current.child; + // Chain subsequent accesses + current.child = newAccess; + current = newAccess; } } - // If there was at least one dot, return the head node, else just the base - return head != null ? head : base; + return root; } private ExpressionNode parseUnaryFactor() { @@ -289,16 +322,7 @@ private ExpressionNode parseUnaryFactor() { } case IDENTIFIER -> { - Token identifierToken = peek(); - if (pos + 1 < tokens.size() && tokens.get(pos + 1).getType() == TokenType.BRACKET_OPEN) { - // Parse as function call - return parseFunctionCall(); - } else { - // Just a variable - consume(); - logger.info(identifierToken + "\n----- IDENTIFIER NODE CREATED -----"); - return new VariableNode(identifierToken.getValue()); - } + return parseMemberAccess(); } // if bracketed, then parse new Expression (recursive call) @@ -321,30 +345,18 @@ private ExpressionNode parseUnaryFactor() { } public static void main(String[] args) { - ArrayList tokens = new ArrayList<>(); - // tokens.add(new Token(null, 1, TokenType.MINUS)); - // tokens.add(new Token("1", 1, TokenType.INT)); - // tokens.add(new Token(null, 1, TokenType.PLUS)); - // tokens.add(new Token("2", 1, TokenType.INT)); - // tokens.add(new Token(null, 1, TokenType.PLUS)); - // tokens.add(new Token("3", 1, TokenType.INT)); - // tokens.add(new Token(null, 1, TokenType.PLUS)); - // tokens.add(new Token("4", 1, TokenType.INT)); - // tokens.add(new Token(null, 1, TokenType.MULTIPLICATION)); - // tokens.add(new Token("5", 1, TokenType.INT)); - // tokens.add(new Token(null, 1, TokenType.MINUS)); - // tokens.add(new Token("6", 1, TokenType.INT)); - - // tokens.add(new Token(null, 1, TokenType.BRACKET_OPEN)); - // tokens.add(new Token("funny_var2", 1, TokenType.IDENTIFIER)); - // tokens.add(new Token(null, 1, TokenType.NEQ)); - // tokens.add(new Token("100100134", 1, TokenType.INT)); - // tokens.add(new Token(null, 1, TokenType.BRACKET_CLOSED)); - - // [BRACKET_OPEN, IDENTIFIER(funny_var2), NEQ, INT(100100134), BRACKET_CLOSED] - - ExpressionNode node = new ExpressionParser(tokens).parseExpression(); - System.out.println(node); - + // Example usage + List tokens = new ArrayList<>(); + tokens.add(new Token(TokenType.IDENTIFIER, "human")); + tokens.add(new Token(TokenType.DOT)); + tokens.add(new Token(TokenType.IDENTIFIER, "birthDate")); + tokens.add(new Token(TokenType.DOT)); + tokens.add(new Token(TokenType.IDENTIFIER, "year")); + + ExpressionParser parser = new ExpressionParser(tokens); + ExpressionNode expression = parser.parseExpression(); + + // Print or process the parsed expression + System.out.println(expression); } } From 7a60cf7e6161e660a42cca6c0d345612dda78d8a Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 17:42:03 +0200 Subject: [PATCH 26/30] working member access parser --- src/main/java/com/lemms/parser/ExpressionParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/lemms/parser/ExpressionParser.java b/src/main/java/com/lemms/parser/ExpressionParser.java index d5b76c4..bf0126d 100644 --- a/src/main/java/com/lemms/parser/ExpressionParser.java +++ b/src/main/java/com/lemms/parser/ExpressionParser.java @@ -266,11 +266,11 @@ private ExpressionNode parseMemberAccess() { if (root == null) { // First member access - base becomes the object root = new MemberAccessNode(base, newAccess); - current = root; + current = newAccess; // Fix: current should point to the newAccess, not root } else { // Chain subsequent accesses current.child = newAccess; - current = newAccess; + current = newAccess; // Move current to the new access } } From ac1c8843264a78dcdb87ea0ce2482d63ab5bf83c Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 17:59:02 +0200 Subject: [PATCH 27/30] Support member access and assignment in parser Enhanced the parser to handle member access chains and assignments, allowing statements like 'h.age = h.age + 1;' and member function calls. Updated test file to include a member assignment example. --- src/main/java/com/lemms/parser/Parser.java | 146 +++++++++++++----- .../resources/successTests/classTest.lemms | 1 + 2 files changed, 111 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/lemms/parser/Parser.java b/src/main/java/com/lemms/parser/Parser.java index e689815..b982356 100644 --- a/src/main/java/com/lemms/parser/Parser.java +++ b/src/main/java/com/lemms/parser/Parser.java @@ -35,11 +35,73 @@ private StatementNode parseStatement() throws LemmsParseError { if (match(TokenType.WHILE)) return parseWhileStatement(); if (match(TokenType.IDENTIFIER)) { - TokenType type = peek().getType(); - if (type == TokenType.ASSIGNMENT) { - return parseAssignment(); - } else if (type == TokenType.BRACKET_OPEN) { - return parseFunctionCallStatement(); + // Look ahead to determine what kind of statement this is + List exprTokens = new ArrayList<>(); + exprTokens.add(previous()); // Add the IDENTIFIER we just consumed + + // Check if it's a simple function call + if (peek().getType() == TokenType.BRACKET_OPEN) { + // Collect tokens for the function call + int parenDepth = 0; + do { + Token token = advance(); + exprTokens.add(token); + if (token.getType() == TokenType.BRACKET_OPEN) + parenDepth++; + if (token.getType() == TokenType.BRACKET_CLOSED) + parenDepth--; + } while (parenDepth > 0 && !isAtEnd()); + + // Check what follows + if (peek().getType() == TokenType.SEMICOLON) { + return parseMemberFunctionCallStatement(exprTokens); + } else if (peek().getType() == TokenType.DOT) { + // Continue collecting member access chain after function call + while (peek().getType() == TokenType.DOT) { + exprTokens.add(advance()); // Add DOT + exprTokens.add(advance()); // Add IDENTIFIER + + // If there's another function call, collect it + if (peek().getType() == TokenType.BRACKET_OPEN) { + parenDepth = 0; + do { + Token token = advance(); + exprTokens.add(token); + if (token.getType() == TokenType.BRACKET_OPEN) + parenDepth++; + if (token.getType() == TokenType.BRACKET_CLOSED) + parenDepth--; + } while (parenDepth > 0 && !isAtEnd()); + } + } + } + } else { + // Collect tokens for member access chain (no initial function call) + while (peek().getType() == TokenType.DOT) { + exprTokens.add(advance()); // Add DOT + exprTokens.add(advance()); // Add IDENTIFIER + + // If there's a function call, collect the entire call + if (peek().getType() == TokenType.BRACKET_OPEN) { + int parenDepth = 0; + do { + Token token = advance(); + exprTokens.add(token); + if (token.getType() == TokenType.BRACKET_OPEN) + parenDepth++; + if (token.getType() == TokenType.BRACKET_CLOSED) + parenDepth--; + } while (parenDepth > 0 && !isAtEnd()); + } + } + } + + // Now check what follows to determine statement type + TokenType nextType = peek().getType(); + if (nextType == TokenType.ASSIGNMENT) { + return parseMemberAssignment(exprTokens); + } else if (nextType == TokenType.SEMICOLON) { + return parseMemberFunctionCallStatement(exprTokens); } } if (match(FUNCTION)) { @@ -48,8 +110,7 @@ private StatementNode parseStatement() throws LemmsParseError { if (match(CLASS)) { return parseClassDeclaration(); } - // ... other statement types ... - throw new LemmsParseError(peek(),"Unexpected token: " + peek()); + throw new LemmsParseError(peek(), "Unexpected token: " + peek()); } private ReturnNode parseReturnStatement() { @@ -88,19 +149,22 @@ private IfNode parseIfStatement() { return ifNode; } - private AssignmentNode parseAssignment() { - Token identifier = previous(); - Token equalsToken = consume(TokenType.ASSIGNMENT, "Expected '=' after identifier."); - ExpressionNode expr = parseExpression(); + private AssignmentNode parseMemberAssignment(List leftSideTokens) { + // Parse the left side as an expression (could be member access) + ExpressionNode leftExpr = new ExpressionParser(leftSideTokens).parseExpression(); + + consume(TokenType.ASSIGNMENT, "Expected '=' after member access."); + ExpressionNode rightExpr = parseExpression(); consume(TokenType.SEMICOLON, "Expected ';' after assignment."); - return new AssignmentNode(new VariableNode(identifier.getValue()), expr); + + return new AssignmentNode(leftExpr, rightExpr); } private ExpressionNode parseExpression() { // Collect tokens for the expression (until a delimiter, e.g., ')', ';', etc.) List exprTokens = new ArrayList<>(); int parenDepth = 0; - //davor so: while (!isAtEnd() && !isExpressionTerminator(peek())) { + // davor so: while (!isAtEnd() && !isExpressionTerminator(peek())) { while (!isAtEnd()) { Token token = peek(); if (token.getType() == TokenType.BRACKET_OPEN) { @@ -140,7 +204,7 @@ private WhileNode parseWhileStatement() { return whileNode; } - private ClassDeclarationNode parseClassDeclaration(){ + private ClassDeclarationNode parseClassDeclaration() { ClassDeclarationNode c = new ClassDeclarationNode(); List vars = new ArrayList<>(); @@ -149,15 +213,16 @@ private ClassDeclarationNode parseClassDeclaration(){ // read class name c.className = consume(IDENTIFIER, "Expected className IDENTIFIER after class keyword").getValue(); - // read class localVariables (localVariables have to be first and after them, only function declarations can exist) + // read class localVariables (localVariables have to be first and after them, + // only function declarations can exist) consume(BRACES_OPEN, "Expected '{' after className IDENTIFIER"); - while (!check(BRACES_CLOSED) && !check(FUNCTION)){ + while (!check(BRACES_CLOSED) && !check(FUNCTION)) { vars.add(consume(IDENTIFIER, "Expected IDENTIFIER after ';'").getValue()); consume(SEMICOLON, "Expected ';' after IDENTIFIER"); } // read class localFunctions - while (!check(BRACES_CLOSED)){ + while (!check(BRACES_CLOSED)) { consume(FUNCTION, "Expected function keyword or '}' after localVariables or previous function"); funcs.add(parseFunctionDeclaration()); } @@ -196,33 +261,42 @@ private FunctionDeclarationNode parseFunctionDeclaration() { return func; } - private FunctionCallStatementNode parseFunctionCallStatement() { - // The IDENTIFIER was just matched - List exprTokens = new ArrayList<>(); - exprTokens.add(previous()); // Add the IDENTIFIER - - // Collect tokens until we reach ')' - int parenDepth = 0; - do { - Token token = advance(); - exprTokens.add(token); - if (token.getType() == TokenType.BRACKET_OPEN) - parenDepth++; - if (token.getType() == TokenType.BRACKET_CLOSED) - parenDepth--; - } while (parenDepth > 0 && !isAtEnd()); - - // Parse the function call as an expression + private FunctionCallStatementNode parseMemberFunctionCallStatement(List exprTokens) { + // Parse the function call expression ExpressionNode expr = new ExpressionParser(exprTokens).parseExpression(); consume(TokenType.SEMICOLON, "Expected ';' after function call."); - // Wrap in statement node + // The expression should be either a FunctionCallNode or MemberAccessNode ending + // in a function call FunctionCallStatementNode stmt = new FunctionCallStatementNode(); - stmt.functionCall = (FunctionCallNode) expr; + if (expr instanceof FunctionCallNode) { + stmt.functionCall = (FunctionCallNode) expr; + } else if (expr instanceof MemberAccessNode) { + // Extract the function call from the member access chain + stmt.functionCall = extractFunctionCallFromMemberAccess((MemberAccessNode) expr); + } else { + throw new LemmsParseError(peek(), "Expected function call in statement."); + } + return stmt; } + private FunctionCallNode extractFunctionCallFromMemberAccess(MemberAccessNode memberAccess) { + // Navigate to the end of the chain to find the function call + MemberAccessNode current = memberAccess; + while (current.child != null) { + current = current.child; + } + + // The object at the end should be a FunctionCallNode + if (current.object instanceof FunctionCallNode) { + return (FunctionCallNode) current.object; + } + + throw new RuntimeException("Expected function call at end of member access chain."); + } + // Utility methods: private boolean match(TokenType type) { if (check(type)) { diff --git a/src/main/resources/successTests/classTest.lemms b/src/main/resources/successTests/classTest.lemms index 1618a53..5317c2c 100644 --- a/src/main/resources/successTests/classTest.lemms +++ b/src/main/resources/successTests/classTest.lemms @@ -10,4 +10,5 @@ class Human { } h = Human(27, "Hon"); +h.age = h.age + 1; h.printHallo(); \ No newline at end of file From 2dfb8e3051d95d24d1102771da2bc67ab69ae200 Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 19:38:00 +0200 Subject: [PATCH 28/30] Support member access function calls in parser and interpreter Updated FunctionCallStatementNode to support both simple and member access function calls by adding an expression field. Modified the parser to preserve the full expression for member access function calls, and updated the interpreter to handle both cases in visitFunctionCallStatement. This enables correct handling of statements like h.doSomething(). --- .../SyntaxNode/FunctionCallStatementNode.java | 1 + .../com/lemms/interpreter/Interpreter.java | 99 +++++++++++-------- src/main/java/com/lemms/parser/Parser.java | 26 +---- 3 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/main/java/com/lemms/SyntaxNode/FunctionCallStatementNode.java b/src/main/java/com/lemms/SyntaxNode/FunctionCallStatementNode.java index 1eb53f6..a08ce54 100644 --- a/src/main/java/com/lemms/SyntaxNode/FunctionCallStatementNode.java +++ b/src/main/java/com/lemms/SyntaxNode/FunctionCallStatementNode.java @@ -9,6 +9,7 @@ public class FunctionCallStatementNode extends StatementNode { public FunctionCallNode functionCall; + public ExpressionNode expression; @Override public FlowSignal accept(StatementVisitor visitor) { diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index a8dc944..aa77b91 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -127,46 +127,52 @@ public LValue(Environment env, String name) { this.propertyName = name; } } -/* - private LValue resolveLValue(ExpressionNode node) { - if (node instanceof VariableNode varNode && varNode.child == null) { - return new LValue(environment, varNode.name); - } else if (node instanceof MemberAccessNode memberNode) { - LemmsData obj = memberNode.object.accept(this); - if (obj instanceof LemmsObject lo) { - return new LValue(lo.environment, memberNode.memberName); - } else { - throw new LemmsRuntimeException("Cannot assign to non-object member: " + memberNode.memberName); - } - } else if (node instanceof FunctionCallNode callNode) { - LemmsData result = callNode.accept(this); - // Next node in chain should be a MemberAccessNode - // (e.g., ee().ff) - // You may need to store the next node in CallNode or handle accordingly - // Example: - // return resolveLValue(new MemberAccessNode(result, ...)); - throw new LemmsRuntimeException("Assignment to function call result not supported directly."); - } - throw new LemmsRuntimeException("Invalid assignment target."); - } -*/ + + /* + * private LValue resolveLValue(ExpressionNode node) { + * if (node instanceof VariableNode varNode && varNode.child == null) { + * return new LValue(environment, varNode.name); + * } else if (node instanceof MemberAccessNode memberNode) { + * LemmsData obj = memberNode.object.accept(this); + * if (obj instanceof LemmsObject lo) { + * return new LValue(lo.environment, memberNode.memberName); + * } else { + * throw new LemmsRuntimeException("Cannot assign to non-object member: " + + * memberNode.memberName); + * } + * } else if (node instanceof FunctionCallNode callNode) { + * LemmsData result = callNode.accept(this); + * // Next node in chain should be a MemberAccessNode + * // (e.g., ee().ff) + * // You may need to store the next node in CallNode or handle accordingly + * // Example: + * // return resolveLValue(new MemberAccessNode(result, ...)); + * throw new + * LemmsRuntimeException("Assignment to function call result not supported directly." + * ); + * } + * throw new LemmsRuntimeException("Invalid assignment target."); + * } + */ @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { - /* TODO assignment - LemmsData dataValue = assignmentNode.rightHandSide.accept(this); - Environment targetEnvironment = environment; - VariableNode targetNode = assignmentNode.leftHandSide; - while (targetNode.child != null) { - LemmsData data = environment.get(targetNode.name); - if (data instanceof LemmsObject lo) { - targetEnvironment = lo.environment; - targetNode = targetNode.child; - } else { - throw new LemmsRuntimeException("Cannot assign to non-object variable '" + targetNode.name + "'."); - } - } - targetEnvironment.assign(targetNode.name, dataValue); - */ + /* + * TODO assignment + * LemmsData dataValue = assignmentNode.rightHandSide.accept(this); + * Environment targetEnvironment = environment; + * VariableNode targetNode = assignmentNode.leftHandSide; + * while (targetNode.child != null) { + * LemmsData data = environment.get(targetNode.name); + * if (data instanceof LemmsObject lo) { + * targetEnvironment = lo.environment; + * targetNode = targetNode.child; + * } else { + * throw new LemmsRuntimeException("Cannot assign to non-object variable '" + + * targetNode.name + "'."); + * } + * } + * targetEnvironment.assign(targetNode.name, dataValue); + */ return FlowSignal.NORMAL; } @@ -350,9 +356,10 @@ public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { .map(param -> param.accept(this)) .toList(); - Environment functionEnvironment = new Environment(useClassEnvironmentSignal ? environment : globalEnvironment); + Environment functionEnvironment = new Environment( + useClassEnvironmentSignal ? environment : globalEnvironment); useClassEnvironmentSignal = false; - + for (int i = 0; i < args.size(); i++) { String argName = functionDeclaration.paramNames.get(i); LemmsData argValue = args.get(i); @@ -370,12 +377,18 @@ public LemmsData visitFunctionCallValue(FunctionCallNode functionNode) { throw new RuntimeException("Function did not return a value: " + functionNode.functionName); } } - + } @Override public FlowSignal visitFunctionCallStatement(FunctionCallStatementNode functionNode) { - functionNode.functionCall.accept(this); + if (functionNode.functionCall != null) { + // Simple function call + functionNode.functionCall.accept(this); + } else if (functionNode.expression != null) { + // Member access function call - this preserves the object context + functionNode.expression.accept(this); + } return FlowSignal.NORMAL; } @@ -434,7 +447,7 @@ public LemmsData visitMemberAccessValue(MemberAccessNode node) { // chain Environment previousEnvironment = environment; environment = lo.environment; - if(node.child.object instanceof FunctionCallNode) { + if (node.child.object instanceof FunctionCallNode) { useClassEnvironmentSignal = true; } LemmsData result = visitMemberAccessValue(node.child); diff --git a/src/main/java/com/lemms/parser/Parser.java b/src/main/java/com/lemms/parser/Parser.java index b982356..8574a7a 100644 --- a/src/main/java/com/lemms/parser/Parser.java +++ b/src/main/java/com/lemms/parser/Parser.java @@ -267,36 +267,20 @@ private FunctionCallStatementNode parseMemberFunctionCallStatement(List e consume(TokenType.SEMICOLON, "Expected ';' after function call."); - // The expression should be either a FunctionCallNode or MemberAccessNode ending - // in a function call FunctionCallStatementNode stmt = new FunctionCallStatementNode(); + if (expr instanceof FunctionCallNode) { + // Simple function call like print("hello") stmt.functionCall = (FunctionCallNode) expr; - } else if (expr instanceof MemberAccessNode) { - // Extract the function call from the member access chain - stmt.functionCall = extractFunctionCallFromMemberAccess((MemberAccessNode) expr); } else { - throw new LemmsParseError(peek(), "Expected function call in statement."); + // Member access function call like h.doSomething() - preserve the full + // expression + stmt.expression = expr; } return stmt; } - private FunctionCallNode extractFunctionCallFromMemberAccess(MemberAccessNode memberAccess) { - // Navigate to the end of the chain to find the function call - MemberAccessNode current = memberAccess; - while (current.child != null) { - current = current.child; - } - - // The object at the end should be a FunctionCallNode - if (current.object instanceof FunctionCallNode) { - return (FunctionCallNode) current.object; - } - - throw new RuntimeException("Expected function call at end of member access chain."); - } - // Utility methods: private boolean match(TokenType type) { if (check(type)) { From 09c530fa7bedaee634b61f31e390b2c46e86548b Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 19:45:05 +0200 Subject: [PATCH 29/30] Implement assignment for variables and member access Added logic to handle assignments to both simple variables and member access chains in the interpreter. Refactored class declaration to properly set properties and functions on new objects. Removed unused LValue class and related commented code. --- .../com/lemms/interpreter/Interpreter.java | 101 ++++++++---------- 1 file changed, 44 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/lemms/interpreter/Interpreter.java b/src/main/java/com/lemms/interpreter/Interpreter.java index aa77b91..4889574 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -118,64 +118,50 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { return FlowSignal.NORMAL; } - private static class LValue { - public Environment environment; - public String propertyName; - - public LValue(Environment env, String name) { - this.environment = env; - this.propertyName = name; - } - } - - /* - * private LValue resolveLValue(ExpressionNode node) { - * if (node instanceof VariableNode varNode && varNode.child == null) { - * return new LValue(environment, varNode.name); - * } else if (node instanceof MemberAccessNode memberNode) { - * LemmsData obj = memberNode.object.accept(this); - * if (obj instanceof LemmsObject lo) { - * return new LValue(lo.environment, memberNode.memberName); - * } else { - * throw new LemmsRuntimeException("Cannot assign to non-object member: " + - * memberNode.memberName); - * } - * } else if (node instanceof FunctionCallNode callNode) { - * LemmsData result = callNode.accept(this); - * // Next node in chain should be a MemberAccessNode - * // (e.g., ee().ff) - * // You may need to store the next node in CallNode or handle accordingly - * // Example: - * // return resolveLValue(new MemberAccessNode(result, ...)); - * throw new - * LemmsRuntimeException("Assignment to function call result not supported directly." - * ); - * } - * throw new LemmsRuntimeException("Invalid assignment target."); - * } - */ @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { - /* - * TODO assignment - * LemmsData dataValue = assignmentNode.rightHandSide.accept(this); - * Environment targetEnvironment = environment; - * VariableNode targetNode = assignmentNode.leftHandSide; - * while (targetNode.child != null) { - * LemmsData data = environment.get(targetNode.name); - * if (data instanceof LemmsObject lo) { - * targetEnvironment = lo.environment; - * targetNode = targetNode.child; - * } else { - * throw new LemmsRuntimeException("Cannot assign to non-object variable '" + - * targetNode.name + "'."); - * } - * } - * targetEnvironment.assign(targetNode.name, dataValue); - */ + LemmsData rightValue = assignmentNode.rightHandSide.accept(this); + + if (assignmentNode.leftHandSide instanceof VariableNode variableNode) { + // Simple variable assignment: x = 5 + environment.assign(variableNode.name, rightValue); + } else if (assignmentNode.leftHandSide instanceof MemberAccessNode memberAccessNode) { + // Member access assignment: h.age = 25 or h.name.first = "John" + assignToMemberAccess(memberAccessNode, rightValue); + } else { + throw new LemmsRuntimeException( + "Invalid assignment target: " + assignmentNode.leftHandSide.getClass().getSimpleName()); + } + return FlowSignal.NORMAL; } + private void assignToMemberAccess(MemberAccessNode memberAccess, LemmsData value) { + if (memberAccess.child == null) { + // This is the final property to assign to + if (memberAccess.object instanceof VariableNode varNode) { + environment.assign(varNode.name, value); + } else { + throw new LemmsRuntimeException("Complex member access assignment not yet supported."); + } + } else { + // Navigate through the member access chain + LemmsData current = memberAccess.object.accept(this); + if (!(current instanceof LemmsObject lemmsObj)) { + throw new LemmsRuntimeException("Cannot access member of non-object."); + } + + // Switch to the object's environment for the rest of the assignment + Environment previousEnv = environment; + environment = lemmsObj.environment; + try { + assignToMemberAccess(memberAccess.child, value); + } finally { + environment = previousEnv; + } + } + } + @Override public LemmsData visitVariableValue(VariableNode variableNode) { LemmsData value = environment.get(variableNode.name); @@ -411,17 +397,18 @@ public FlowSignal visitReturnNode(ReturnNode returnNode) { public void visitClassDeclarationStatement(ClassDeclarationNode classDeclarationNode) { NativeFunction constructor = (args) -> { - HashMap properties = new HashMap(); + LemmsObject lemmsObject = new LemmsObject(classDeclarationNode, globalEnvironment); + for (int i = 0; i < classDeclarationNode.localVariables.size(); i++) { String paramName = classDeclarationNode.localVariables.get(i); LemmsData paramValue = args.get(i); - properties.put(paramName, paramValue); + lemmsObject.set(paramName, paramValue); } for (var functionDeclaration : classDeclarationNode.localFunctions) { - properties.put(functionDeclaration.functionName, new LemmsFunction(functionDeclaration)); + lemmsObject.set(functionDeclaration.functionName, new LemmsFunction(functionDeclaration)); } - return new LemmsObject(classDeclarationNode, globalEnvironment); + return lemmsObject; }; globalEnvironment.assign(classDeclarationNode.className, From 39b5079308e728213eb6c9b9e0b4c94b5ffb4c9e Mon Sep 17 00:00:00 2001 From: erdemt09 Date: Wed, 25 Jun 2025 19:47:54 +0200 Subject: [PATCH 30/30] Update classTest.lemms --- src/main/resources/successTests/classTest.lemms | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/successTests/classTest.lemms b/src/main/resources/successTests/classTest.lemms index 5317c2c..0808d34 100644 --- a/src/main/resources/successTests/classTest.lemms +++ b/src/main/resources/successTests/classTest.lemms @@ -11,4 +11,5 @@ class Human { h = Human(27, "Hon"); h.age = h.age + 1; -h.printHallo(); \ No newline at end of file +h.printHallo(); +print(h.age); \ No newline at end of file