-
Notifications
You must be signed in to change notification settings - Fork 0
Implement the when expression #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,14 +1,17 @@ | ||||||||||||||||||
| package org.piccode.ast; | ||||||||||||||||||
|
|
||||||||||||||||||
| import java.util.List; | ||||||||||||||||||
| import org.piccode.rt.PiccodeException; | ||||||||||||||||||
| import org.piccode.rt.PiccodeValue; | ||||||||||||||||||
|
|
||||||||||||||||||
| import org.piccode.rt.*; | ||||||||||||||||||
| import java.util.*; | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * | ||||||||||||||||||
| * @author hexaredecimal | ||||||||||||||||||
| */ | ||||||||||||||||||
| public class WhenAst implements Ast { | ||||||||||||||||||
|
|
||||||||||||||||||
| public Ast cond; | ||||||||||||||||||
| public List<WhenCase> cases; | ||||||||||||||||||
| public Ast else_case; | ||||||||||||||||||
|
|
@@ -22,11 +25,10 @@ public WhenAst(Ast cond, List<WhenCase> cases, Ast else_case) { | |||||||||||||||||
| @Override | ||||||||||||||||||
| public String toString() { | ||||||||||||||||||
| StringBuilder sb = new StringBuilder(); | ||||||||||||||||||
| sb.append("when ").append(cond).append("{\n"); | ||||||||||||||||||
| for (var when_c: cases) { | ||||||||||||||||||
| sb.append("when ").append(cond).append(" {\n"); | ||||||||||||||||||
| for (var when_c : cases) { | ||||||||||||||||||
| sb.append(when_c.toString().indent(4)); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (else_case != null) { | ||||||||||||||||||
| sb.append(String.format("else %s", else_case).indent(4)); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
@@ -38,31 +40,103 @@ public String toString() { | |||||||||||||||||
| public PiccodeValue execute() { | ||||||||||||||||||
| var cond_value = cond.execute(); | ||||||||||||||||||
|
|
||||||||||||||||||
| for (var match_case: cases) { | ||||||||||||||||||
| if (isMatching(match_case.match, cond_value)) { | ||||||||||||||||||
| return match_case.value.execute(); | ||||||||||||||||||
| for (var match_case : cases) { | ||||||||||||||||||
| var tempSymtable = new HashMap<String, PiccodeValue>(); | ||||||||||||||||||
| if (isMatching(match_case.match, cond_value, tempSymtable)) { | ||||||||||||||||||
| Context.top.pushStack(); | ||||||||||||||||||
| for (var entry : tempSymtable.entrySet()) { | ||||||||||||||||||
| Context.top.putLocal(entry.getKey(), entry.getValue()); | ||||||||||||||||||
| } | ||||||||||||||||||
| var result = match_case.value.execute(); | ||||||||||||||||||
| Context.top.dropStackFrame(); | ||||||||||||||||||
| return result; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (else_case == null) { | ||||||||||||||||||
| throw new PiccodeException("Inexhaustive when expression has hit an unexpected state where no pattern was matched: when " + cond + " { ..."); | ||||||||||||||||||
| throw new PiccodeException("Inexhaustive when expression: no pattern matched: when " + cond + " { ... }"); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return else_case.execute(); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| private boolean isMatching(List<Ast> match, PiccodeValue cond_value) { | ||||||||||||||||||
| for (var node: match) { | ||||||||||||||||||
| if (node instanceof IdentifierAst id) { | ||||||||||||||||||
| continue; // TODO: Add Ids to the symtable | ||||||||||||||||||
| private boolean isMatching(List<Ast> patterns, PiccodeValue cond_value, Map<String, PiccodeValue> temp) { | ||||||||||||||||||
| for (Ast pattern : patterns) { | ||||||||||||||||||
| if (matchPattern(pattern, cond_value, temp)) { | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| var value = node.execute(); | ||||||||||||||||||
| if (node.equals(cond_value)) { | ||||||||||||||||||
| return true; | ||||||||||||||||||
| private boolean matchPattern(Ast pattern, PiccodeValue value, Map<String, PiccodeValue> temp) { | ||||||||||||||||||
| if (pattern instanceof IdentifierAst id) { | ||||||||||||||||||
| temp.put(id.text, value); | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (pattern instanceof NumberAst lit) { | ||||||||||||||||||
| var litVal = lit.execute(); | ||||||||||||||||||
| return Objects.equals(litVal, value); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (pattern instanceof StringAst lit) { | ||||||||||||||||||
| var litVal = lit.execute(); | ||||||||||||||||||
| return Objects.equals(litVal, value); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (pattern instanceof TupleAst tup && value instanceof PiccodeTuple vTup) { | ||||||||||||||||||
| var items = tup.nodes; | ||||||||||||||||||
| var vItems = vTup.nodes; | ||||||||||||||||||
| if (items.size() != vItems.size()) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| for (int i = 0; i < items.size(); i++) { | ||||||||||||||||||
| if (!matchPattern(items.get(i), vItems.get(i), temp)) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (pattern instanceof ArrayAst listPat && value instanceof PiccodeArray listVal) { | ||||||||||||||||||
| var patItems = listPat.nodes; | ||||||||||||||||||
| var valItems = listVal.nodes; | ||||||||||||||||||
| if (patItems.size() != valItems.size()) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| for (int i = 0; i < patItems.size(); i++) { | ||||||||||||||||||
| if (!matchPattern(patItems.get(i), valItems.get(i), temp)) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (pattern instanceof ListConstAst cons && value instanceof PiccodeArray listVal2) { | ||||||||||||||||||
| if (listVal2.nodes.isEmpty()) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| var head = listVal2.nodes.getFirst(); | ||||||||||||||||||
| var tail = new PiccodeArray(listVal2.nodes.subList(1, listVal2.nodes.size())); | ||||||||||||||||||
|
|
||||||||||||||||||
| return matchPattern(cons.lhs, head, temp) && matchPattern(cons.rhs, tail, temp); | ||||||||||||||||||
| } | ||||||||||||||||||
| if (pattern instanceof ObjectAst objPat && value instanceof PiccodeObject objVal) { | ||||||||||||||||||
| for (var entry : objPat.objs.entrySet()) { | ||||||||||||||||||
| if (!objVal.obj.containsKey(entry.getKey())) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| if (!matchPattern(entry.getValue(), objVal.obj.get(entry.getKey()), temp)) { | ||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (pattern instanceof ArrayAst && value instanceof PiccodeArray vList && vList.nodes.isEmpty()) { | ||||||||||||||||||
| return true; | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+138
to
+139
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Array pattern matches even when the pattern is non-empty The catch-all branch accepts any Add a check that the pattern contains zero elements: -if (pattern instanceof ArrayAst && value instanceof PiccodeArray vList && vList.nodes.isEmpty()) {
- return true;
+if (pattern instanceof ArrayAst arrPat
+ && value instanceof PiccodeArray vList
+ && arrPat.nodes.isEmpty()
+ && vList.nodes.isEmpty()) {
+ return true;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| return false; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| package org.piccode.ast; | ||
|
|
||
| import org.antlr.v4.runtime.CharStreams; | ||
| import org.antlr.v4.runtime.CommonTokenStream; | ||
| import org.editor.AccessFrame; | ||
| import org.editor.errors.IDEErrorListener; | ||
| import org.junit.jupiter.api.Assertions; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
| import org.junit.jupiter.api.Test; | ||
| import org.piccode.antlr4.PiccodeScriptLexer; | ||
| import org.piccode.antlr4.PiccodeScriptParser; | ||
|
|
||
| /** | ||
| * | ||
| * @author hexaredecimal | ||
| */ | ||
| public class TopLevel { | ||
| @Test | ||
| public void function() { | ||
| var code = "function add(x, y) = x + y"; | ||
| var ast = compile(code); | ||
|
|
||
| assertEquals(ast.nodes.size(), 1); | ||
| var func = ast.nodes.getFirst(); | ||
|
|
||
| assertFalse(!(func instanceof FunctionAst)); | ||
| var node = (FunctionAst) func; | ||
| assertEquals(node.name, "add"); | ||
| assertEquals(node.arg.size(), 2); | ||
| assertTrue(node.body instanceof BinOpAst); | ||
| } | ||
|
|
||
| @Test | ||
| public void variable() { | ||
| var code = "let foo = 1"; | ||
| var ast = compile(code); | ||
|
|
||
| assertEquals(ast.nodes.size(), 1); | ||
| var let = ast.nodes.getFirst(); | ||
|
|
||
| assertFalse(!(let instanceof VarDecl)); | ||
| var node = (VarDecl) let; | ||
| assertEquals(node.name, "foo"); | ||
| assertTrue(node.value instanceof NumberAst num && num.text.equals("1")); | ||
| } | ||
|
|
||
| @Test | ||
| public void module() { | ||
| var code = | ||
| """ | ||
| module Foo { | ||
| function bar () = () | ||
| } | ||
| """; | ||
| var ast = compile(code); | ||
|
|
||
| assertEquals(ast.nodes.size(), 1); | ||
| var mod = ast.nodes.getFirst(); | ||
|
|
||
| assertFalse(!(mod instanceof ModuleAst)); | ||
| var node = (ModuleAst) mod; | ||
| assertEquals(node.name, "Foo"); | ||
| assertEquals(node.nodes.size(), 1); | ||
|
|
||
| var inner = node.nodes.getFirst(); | ||
| assertTrue(inner instanceof FunctionAst); | ||
| } | ||
|
|
||
| @Test | ||
| public void importModule() { | ||
| var code = "import pkg:io"; | ||
| var ast = compile(code); | ||
| assertEquals(ast.nodes.size(), 1); | ||
| var import_ = ast.nodes.getFirst(); | ||
| assertFalse(!(import_ instanceof ImportAst)); | ||
| var node = (ImportAst) import_; | ||
| assertEquals(node.pkg, "pkg"); | ||
| assertEquals(node.module, "io"); | ||
| } | ||
|
|
||
| private static StatementList compile(String code) { | ||
| var lexer = new PiccodeScriptLexer(CharStreams.fromString(code)); | ||
| var parser = new PiccodeScriptParser(new CommonTokenStream(lexer)); | ||
| lexer.removeErrorListeners(); | ||
| parser.removeErrorListeners(); | ||
|
|
||
| IDEErrorListener errorListener = new IDEErrorListener(); | ||
| lexer.addErrorListener(errorListener); | ||
| parser.addErrorListener(errorListener); | ||
|
|
||
| var visitor = new PiccodeVisitor(); | ||
|
|
||
| return (StatementList) visitor.visit(parser.stmts()); | ||
| } | ||
|
Comment on lines
+81
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion The compile method should handle exceptions gracefully The Consider adding exception handling to provide more informative test failures: private static StatementList compile(String code) {
+ try {
var lexer = new PiccodeScriptLexer(CharStreams.fromString(code));
var parser = new PiccodeScriptParser(new CommonTokenStream(lexer));
lexer.removeErrorListeners();
parser.removeErrorListeners();
IDEErrorListener errorListener = new IDEErrorListener();
lexer.addErrorListener(errorListener);
parser.addErrorListener(errorListener);
var visitor = new PiccodeVisitor();
return (StatementList) visitor.visit(parser.stmts());
+ } catch (Exception e) {
+ fail("Failed to compile code: " + code + " - " + e.getMessage());
+ return null; // This line won't be reached due to fail() above
+ }
}🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,75 @@ | ||||||||||||||||||||||
| package org.piccode.rt; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import org.antlr.v4.runtime.CharStreams; | ||||||||||||||||||||||
| import org.antlr.v4.runtime.CommonTokenStream; | ||||||||||||||||||||||
| import org.editor.AccessFrame; | ||||||||||||||||||||||
| import org.editor.errors.IDEErrorListener; | ||||||||||||||||||||||
| import org.junit.jupiter.api.Assertions; | ||||||||||||||||||||||
| import static org.junit.jupiter.api.Assertions.*; | ||||||||||||||||||||||
| import org.junit.jupiter.api.Test; | ||||||||||||||||||||||
| import org.piccode.antlr4.PiccodeScriptLexer; | ||||||||||||||||||||||
| import org.piccode.antlr4.PiccodeScriptParser; | ||||||||||||||||||||||
| import org.piccode.ast.*; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * | ||||||||||||||||||||||
| * @author hexaredecimal | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| public class Runtime { | ||||||||||||||||||||||
| @Test | ||||||||||||||||||||||
| public void function() { | ||||||||||||||||||||||
| var code = "function add(x, y) = x + y"; | ||||||||||||||||||||||
| var ast = compile(code); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| assertEquals(ast.nodes.size(), 1); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Swap expected/actual arguments in JUnit’s signature is -assertEquals(ast.nodes.size(), 1);
+assertEquals(1, ast.nodes.size());Apply the same change in the other two occurrences. Also applies to: 38-38, 53-53 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| var func = ast.nodes.getFirst(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| assertFalse(!(func instanceof FunctionAst)); | ||||||||||||||||||||||
| var node = func.execute(); | ||||||||||||||||||||||
| assertTrue(node instanceof PiccodeClosure); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Test | ||||||||||||||||||||||
| public void variable() { | ||||||||||||||||||||||
| var code = "let foo = 1"; | ||||||||||||||||||||||
| var ast = compile(code); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| assertEquals(ast.nodes.size(), 1); | ||||||||||||||||||||||
| var let = ast.nodes.getFirst(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| assertFalse(!(let instanceof VarDecl)); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Context.top.pushStack(); | ||||||||||||||||||||||
| var node = let.execute(); | ||||||||||||||||||||||
| Context.top.dropStackFrame(); | ||||||||||||||||||||||
| assertTrue(node instanceof PiccodeNumber num && num.toString().equals("1")); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| @Test | ||||||||||||||||||||||
| public void importModule() { | ||||||||||||||||||||||
| var code = "import pkg:io"; | ||||||||||||||||||||||
| var ast = compile(code); | ||||||||||||||||||||||
| assertEquals(ast.nodes.size(), 1); | ||||||||||||||||||||||
| var import_ = ast.nodes.getFirst(); | ||||||||||||||||||||||
| assertFalse(!(import_ instanceof ImportAst)); | ||||||||||||||||||||||
| var node = import_.execute(); | ||||||||||||||||||||||
| assertTrue(node instanceof PiccodeBoolean bool && bool.toString().equals("true")); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| private static StatementList compile(String code) { | ||||||||||||||||||||||
| var lexer = new PiccodeScriptLexer(CharStreams.fromString(code)); | ||||||||||||||||||||||
| var parser = new PiccodeScriptParser(new CommonTokenStream(lexer)); | ||||||||||||||||||||||
| lexer.removeErrorListeners(); | ||||||||||||||||||||||
| parser.removeErrorListeners(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| IDEErrorListener errorListener = new IDEErrorListener(); | ||||||||||||||||||||||
| lexer.addErrorListener(errorListener); | ||||||||||||||||||||||
| parser.addErrorListener(errorListener); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
Comment on lines
+66
to
+69
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Check and assert on syntax errors during compilation An var visitor = new PiccodeVisitor();
var stmtList = (StatementList) visitor.visit(parser.stmts());
+assertTrue(errorListener.getSyntaxErrorInfos().isEmpty(),
+ () -> "Unexpected syntax errors: " + errorListener.getSyntaxErrorInfos());
+
return stmtList;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| var visitor = new PiccodeVisitor(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return (StatementList) visitor.visit(parser.stmts()); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure stack-frame cleanup with
try/finallyIf
match_case.value.execute()throws,dropStackFrame()is skipped, leaking the pushed scope and corrupting subsequent evaluation.📝 Committable suggestion
🤖 Prompt for AI Agents