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/Canvas/CanvasPanel.java b/src/main/java/com/lemms/Canvas/CanvasPanel.java new file mode 100644 index 0000000..dca409c --- /dev/null +++ b/src/main/java/com/lemms/Canvas/CanvasPanel.java @@ -0,0 +1,59 @@ +package com.lemms.Canvas; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class CanvasPanel extends JPanel implements KeyListener { + 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); + } + + public void removeElement(Drawable d) { + elements.remove(d); + } + + 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); + for (Drawable d : elements) { + d.draw(g); + } + } + + @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/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/Canvas/Drawable.java b/src/main/java/com/lemms/Canvas/Drawable.java new file mode 100644 index 0000000..bf3a33d --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Drawable.java @@ -0,0 +1,7 @@ +package com.lemms.Canvas; + +import java.awt.*; + +public interface Drawable { + void draw(Graphics g); +} 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/Canvas/ScriptCallback.java b/src/main/java/com/lemms/Canvas/ScriptCallback.java new file mode 100644 index 0000000..2e0c5a1 --- /dev/null +++ b/src/main/java/com/lemms/Canvas/ScriptCallback.java @@ -0,0 +1,5 @@ +package com.lemms.Canvas; + +public interface ScriptCallback { + void call(); +} diff --git a/src/main/java/com/lemms/Canvas/Snake.java b/src/main/java/com/lemms/Canvas/Snake.java new file mode 100644 index 0000000..7798243 --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Snake.java @@ -0,0 +1,105 @@ +package com.lemms.Canvas; + +import java.awt.*; +import java.awt.event.KeyEvent; +import java.util.ArrayList; +import java.util.Random; + +public class Snake { + 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.run(); + canvas.add(snake.get(0)); + canvas.add(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.update = () -> { + // Compute next head position + Rect oldHead = snake.get(0); + 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 + if (!canvas.getBounds().contains(newHead)) { + GameOver(canvas, snake, false); + return; + } + + // Self‐collision + for (Rect segment : snake) { + if (segment.getX() == newX && segment.getY() == newY) { + // game over + GameOver(canvas, snake, false); + return; + } + } + + // Add head to front + snake.add(0, newHead); + canvas.add(newHead); + + boolean ate = newHead.intersects(food); + if (ate) { + // move the food + food.setX(random.nextInt(gridWidth-1)*20); + food.setY(random.nextInt(gridHeight-1)*20); + + if(snake.size() == gridHeight*gridWidth) { + GameOver(canvas, snake, true); + } + } + + if (!ate) { + Rect tail = snake.remove(snake.size() - 1); + canvas.remove(tail); + } + + canvas.repaint(); + }; + } + + private static void GameOver(Canvas canvas, ArrayList snake, boolean won) { + 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.add(GameOver); + canvas.repaint(); + canvas.quit(); + } + +} diff --git a/src/main/java/com/lemms/Canvas/Text.java b/src/main/java/com/lemms/Canvas/Text.java new file mode 100644 index 0000000..00bbf86 --- /dev/null +++ b/src/main/java/com/lemms/Canvas/Text.java @@ -0,0 +1,73 @@ +package com.lemms.Canvas; + +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; + } + + // Getter and Setter + + 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/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/Lemms.java b/src/main/java/com/lemms/Lemms.java index 021911a..699fc50 100644 --- a/src/main/java/com/lemms/Lemms.java +++ b/src/main/java/com/lemms/Lemms.java @@ -41,6 +41,7 @@ public static void main(String[] args) { exit(1); } + //Verknüpfung: Tokenizer + Parser + Interpreter Tokenizer t = new Tokenizer(sourceFile); Parser p = new Parser(t.getTokens()); 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/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/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/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/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/MemberAccessNode.java b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java new file mode 100644 index 0000000..afc075c --- /dev/null +++ b/src/main/java/com/lemms/SyntaxNode/MemberAccessNode.java @@ -0,0 +1,21 @@ +package com.lemms.SyntaxNode; + +import com.lemms.interpreter.ValueVisitor; +import com.lemms.interpreter.object.LemmsData; + +public class MemberAccessNode extends ExpressionNode { + + 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.child = child; + } + + @Override + public LemmsData accept(ValueVisitor visitor) { + return visitor.visitMemberAccessValue(this); + } +} \ No newline at end of file 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 22712f3..be9e8f0 100644 --- a/src/main/java/com/lemms/SyntaxNode/VariableNode.java +++ b/src/main/java/com/lemms/SyntaxNode/VariableNode.java @@ -3,26 +3,17 @@ 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 Token token; //Line - - 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; } - + @Override - public Object accept(ValueVisitor visitor) { + public LemmsData accept(ValueVisitor visitor) { return visitor.visitVariableValue(this); } } 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/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 d1c0e8a..e82d82c 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()) diff --git a/src/main/java/com/lemms/api/LemmsAPI.java b/src/main/java/com/lemms/api/LemmsAPI.java index d983ebf..f2c6cbc 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/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/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 b3759ae..4889574 100644 --- a/src/main/java/com/lemms/interpreter/Interpreter.java +++ b/src/main/java/com/lemms/interpreter/Interpreter.java @@ -10,7 +10,12 @@ 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.LemmsFunction; +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 +25,20 @@ 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 numericComparisonOperators = List.of( + TokenType.GEQ, + TokenType.LEQ, + TokenType.GT, + TokenType.LT); + + private boolean useClassEnvironmentSignal = false; + public Interpreter(List program) { this.program = program; nativeFunctions = new HashMap<>(); @@ -42,6 +61,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); } @@ -97,71 +120,143 @@ public FlowSignal visitBlockStatement(BlockNode blockNode) { @Override public FlowSignal visitAssignmentNode(AssignmentNode assignmentNode) { - Object value = assignmentNode.rightHandSide.accept(this); - environment.assign(assignmentNode.leftHandSide.name, value); + 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; } - @Override - 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 + "'."); - else return value; + 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 Object visitLiteralValue(LiteralNode literalNode) { + 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; + } - return literalNode.value; + @Override + 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); + @Override + public LemmsData visitOperatorValue(OperatorNode operatorNode) { - private static List booleanOperators = List.of(TokenType.AND, - TokenType.OR, TokenType.NOT); + LemmsData leftValue = operatorNode.leftOperand.accept(this); + LemmsData rightValue = operatorNode.rightOperand.accept(this); + TokenType operatorType = operatorNode.operator.getType(); - private static List comparisonOperators = List.of(TokenType.EQ, - NEQ, - TokenType.GEQ, - TokenType.LEQ, - TokenType.GT, - TokenType.LT); + 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); + } - @Override - public Object 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); } 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: @@ -174,13 +269,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: @@ -188,14 +281,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: @@ -204,59 +296,66 @@ 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); } } - 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 - 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() + LemmsData functionValue = environment.get(functionNode.functionName); + if (!(functionValue instanceof LemmsFunction)) { + throw new LemmsRuntimeException( + "Function '" + functionNode.functionName + "' is not defined or not a function."); + } + LemmsFunction lemmsFunction = (LemmsFunction) functionValue; + if (lemmsFunction.isNative) { + NativeFunction nativeFunction = lemmsFunction.nativeFunction; + List args = functionNode.params.stream() .map(param -> param.accept(this)) .toList(); return nativeFunction.apply(args); - } - - Object functionValue = environment.get(functionNode.functionName); - if (functionValue instanceof FunctionDeclarationNode) { - List args = functionNode.params.stream() + } else { + FunctionDeclarationNode functionDeclaration = lemmsFunction.functionDeclaration; + List args = functionNode.params.stream() .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 = ((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; @@ -265,33 +364,82 @@ public Object visitFunctionCallValue(FunctionCallNode functionNode) { } } - throw new RuntimeException("Unknown function: " + 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; } @Override public void visitFunctionDeclarationStatement(FunctionDeclarationNode functionDeclarationNode) { - environment.assign(functionDeclarationNode.functionName, functionDeclarationNode); + environment.assign(functionDeclarationNode.functionName, new LemmsFunction(functionDeclarationNode)); } @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); } @Override public void visitClassDeclarationStatement(ClassDeclarationNode classDeclarationNode) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'visitClassDeclarationStatement'"); + + NativeFunction constructor = (args) -> { + 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); + lemmsObject.set(paramName, paramValue); + } + for (var functionDeclaration : classDeclarationNode.localFunctions) { + lemmsObject.set(functionDeclaration.functionName, new LemmsFunction(functionDeclaration)); + } + + return lemmsObject; + }; + + globalEnvironment.assign(classDeclarationNode.className, + new LemmsFunction(constructor)); } + + @Override + public LemmsData visitMemberAccessValue(MemberAccessNode node) { + // 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; + } + + // 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; + if (node.child.object instanceof FunctionCallNode) { + useClassEnvironmentSignal = true; + } + LemmsData result = visitMemberAccessValue(node.child); + environment = previousEnvironment; + return result; + } + } \ No newline at end of file diff --git a/src/main/java/com/lemms/interpreter/PredefinedFunctionLibrary.java b/src/main/java/com/lemms/interpreter/PredefinedFunctionLibrary.java index 50c8a46..3e14147 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 { @@ -21,15 +23,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/ValueVisitor.java b/src/main/java/com/lemms/interpreter/ValueVisitor.java index 192b953..584970f 100644 --- a/src/main/java/com/lemms/interpreter/ValueVisitor.java +++ b/src/main/java/com/lemms/interpreter/ValueVisitor.java @@ -1,12 +1,14 @@ 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); + public LemmsData visitMemberAccessValue(MemberAccessNode 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..4f7d27c --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsBool.java @@ -0,0 +1,14 @@ +package com.lemms.interpreter.object; + +public class LemmsBool extends LemmsData { + public boolean value; + + 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 new file mode 100644 index 0000000..6161d6b --- /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 { + 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 new file mode 100644 index 0000000..6aef7c6 --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsFunction.java @@ -0,0 +1,26 @@ +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; + } + + @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 new file mode 100644 index 0000000..f51cb12 --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsInt.java @@ -0,0 +1,13 @@ +package com.lemms.interpreter.object; + +public class LemmsInt extends LemmsData { + public final int value; + 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 new file mode 100644 index 0000000..997fa7d --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsObject.java @@ -0,0 +1,36 @@ +package com.lemms.interpreter.object; + +import java.util.Map; + +import com.lemms.SyntaxNode.ClassDeclarationNode; +import com.lemms.interpreter.Environment; + +public class LemmsObject extends LemmsData { + public final Environment environment; + public final ClassDeclarationNode classDeclaration; + + public LemmsObject(ClassDeclarationNode classDeclaration, Environment globalEnvironment) { + this.classDeclaration = classDeclaration; + this.environment = new Environment(globalEnvironment); + } + + public LemmsData get(String name) { + return environment.get(name); + } + + public void set(String name, LemmsData value) { + environment.assign(name, value); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(classDeclaration.className).append("{\n"); + for (Map.Entry entry : environment.getValues().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 new file mode 100644 index 0000000..c17ec0f --- /dev/null +++ b/src/main/java/com/lemms/interpreter/object/LemmsString.java @@ -0,0 +1,14 @@ +package com.lemms.interpreter.object; + +public class LemmsString extends LemmsData { + public String value; + + public LemmsString(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/com/lemms/parser/ExpressionParser.java b/src/main/java/com/lemms/parser/ExpressionParser.java index a340894..bf0126d 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; @@ -202,6 +203,80 @@ 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() { + ExpressionNode base = parseBaseFactor(); + + // If no dot follows, just return the base factor + if (peek() == null || peek().getType() != TokenType.DOT) { + return base; + } + + // Build the member access chain + MemberAccessNode root = null; + MemberAccessNode current = null; + + while (peek() != null && peek().getType() == TokenType.DOT) { + consume(); // consume '.' + + Token memberToken = consume(); + if (memberToken.getType() != TokenType.IDENTIFIER) { + throw new UnexpectedToken("Expected identifier after '.'"); + } + + ExpressionNode memberExpression; + if (peek() != null && peek().getType() == TokenType.BRACKET_OPEN) { + // Backtrack and parse as function call + pos--; + memberExpression = parseFunctionCall(); + } else { + memberExpression = new VariableNode(memberToken.getValue()); + } + + // Create new MemberAccessNode for this access + MemberAccessNode newAccess = new MemberAccessNode(memberExpression, null); + + if (root == null) { + // First member access - base becomes the object + root = new MemberAccessNode(base, newAccess); + current = newAccess; // Fix: current should point to the newAccess, not root + } else { + // Chain subsequent accesses + current.child = newAccess; + current = newAccess; // Move current to the new access + } + } + + return root; + } + private ExpressionNode parseUnaryFactor() { logger.info("\n+++++ FACTOR PARSING +++++"); Token current = peek(); @@ -247,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); - } + return parseMemberAccess(); } // if bracketed, then parse new Expression (recursive call) @@ -279,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); } } diff --git a/src/main/java/com/lemms/parser/Parser.java b/src/main/java/com/lemms/parser/Parser.java index 703f358..8574a7a 100644 --- a/src/main/java/com/lemms/parser/Parser.java +++ b/src/main/java/com/lemms/parser/Parser.java @@ -35,18 +35,82 @@ 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)) { return parseFunctionDeclaration(); } - // ... other statement types ... - throw new LemmsParseError(peek(),"Unexpected token: " + peek()); + if (match(CLASS)) { + return parseClassDeclaration(); + } + throw new LemmsParseError(peek(), "Unexpected token: " + peek()); } private ReturnNode parseReturnStatement() { @@ -85,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), 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) { @@ -137,6 +204,36 @@ 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(); @@ -164,30 +261,23 @@ 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 FunctionCallStatementNode stmt = new FunctionCallStatementNode(); - stmt.functionCall = (FunctionCallNode) expr; + + if (expr instanceof FunctionCallNode) { + // Simple function call like print("hello") + stmt.functionCall = (FunctionCallNode) expr; + } else { + // Member access function call like h.doSomething() - preserve the full + // expression + stmt.expression = expr; + } + return stmt; } 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(); +} + diff --git a/src/main/resources/successTests/classTest.lemms b/src/main/resources/successTests/classTest.lemms new file mode 100644 index 0000000..0808d34 --- /dev/null +++ b/src/main/resources/successTests/classTest.lemms @@ -0,0 +1,15 @@ +print("hallo"); + +class Human { + age; + name; + + function printHallo(){ + print("hallo"); + } +} + +h = Human(27, "Hon"); +h.age = h.age + 1; +h.printHallo(); +print(h.age); \ No newline at end of file diff --git a/src/main/resources/successTests/functionTest.lemms b/src/main/resources/successTests/functionTest.lemms new file mode 100644 index 0000000..f17d44d --- /dev/null +++ b/src/main/resources/successTests/functionTest.lemms @@ -0,0 +1,18 @@ +function multiply( x ) +{ + return x * x; +} + +function halve( x ) +{ + return x / 2 ; +} + + +function applyFunctions(fA, fB, x) { + print(fA(x)+fB(x)); +} + +applyFunctions(multiply, halve, 8); +applyFunctions(multiply, multiply, 8); +applyFunctions(halve, halve, 500); \ No newline at end of file diff --git a/src/test/java/com/lemms/InterpreterTest.java b/src/test/java/com/lemms/InterpreterTest.java index c603274..044bbcb 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,44 +110,42 @@ 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); + assertEquals(FIBONACCI_NUMBER_20TH, ((LemmsInt)interpreter.environment.get(variableName1)).value); } } \ No newline at end of file