diff --git a/examples/network/github_timeline.own b/examples/network/github_timeline.own index 41dfc0a..59089fc 100644 --- a/examples/network/github_timeline.own +++ b/examples/network/github_timeline.own @@ -27,37 +27,18 @@ def show_github_events(event) { } def github_event_type(event) { - type = event.type repo = "https://github.com/" + event.repo.name payload = event.payload - - if (type == "CommitCommentEvent") { - return "commented commit in " + repo + "\n" + payload.comment.body + return match event.type { + case "CommitCommentEvent": "commented commit in " + repo + "\n" + payload.comment.body + case "CreateEvent": "created " + payload.ref_type + " on " + repo + case "DeleteEvent": "deleted " + payload.ref_type + " on " + repo + case "ForkEvent": "forked repository " + repo + case "IssueCommentEvent": "commented issue " + payload.issue.title + " on " + repo + "\n" + payload.comment.body + case "IssuesEvent": payload.action + " issue '" + payload.issue.title + "' on " + repo + case "PullRequestEvent": payload.action + " pull request #" + payload.number + " '" + payload.pull_request.title + "' on " + repo + case "PushEvent": "pushed " + length(payload.commits) + " commits to " + repo + case "WatchEvent": "start watching repository " + repo + case type : type + " on " + repo } - if (type == "CreateEvent") { - return "created " + payload.ref_type + " on " + repo - } - if (type == "DeleteEvent") { - return "deleted " + payload.ref_type + " on " + repo - } - if (type == "ForkEvent") { - return "forked repository " + repo - } - if (type == "IssueCommentEvent") { - return "commented issue " + payload.issue.title + " on " + repo + "\n" + payload.comment.body - } - if (type == "IssuesEvent") { - return payload.action + " issue '" + payload.issue.title + "' on " + repo - } - if (type == "PullRequestEvent") { - pr = payload.pull_request - return payload.action + " pull request #" + payload.number + " '" + pr.title + "' on " + repo - } - if (type == "PushEvent") { - return "pushed " + length(payload.commits) + " commits to " + repo - } - if (type == "WatchEvent") { - return "start watching repository " + repo - } - return type + " on " + repo } diff --git a/program.own b/program.own index 7ac6f02..bc1bbb5 100644 --- a/program.own +++ b/program.own @@ -81,7 +81,7 @@ for i = 0, i < 4, i = i + 1 { } // map -map = {"+" : add, "-" : sub. "*" : mul, "/" : div} +map = {"+" : add, "-" : sub, "*" : mul, "/" : div} map["%"] = def(x,y) = x % y map["pow"] = def(x,y) = pow(x, y) println map["+"] @@ -149,4 +149,19 @@ println jsonencode({ "key": "value", 10: "1000" } -}) \ No newline at end of file +}) + +println "" + + +def fact1(n) = match n { + case 0: 1 + case n: n * fact1(n - 1) +} +def fact2(n) = match n { + case n if n == 0: 1 + case _: n * fact2(n - 1) +} + +println fact1(6) +println fact2(6) \ No newline at end of file diff --git a/src/com/annimon/ownlang/lib/Variables.java b/src/com/annimon/ownlang/lib/Variables.java index 543fd37..e11d3ff 100644 --- a/src/com/annimon/ownlang/lib/Variables.java +++ b/src/com/annimon/ownlang/lib/Variables.java @@ -40,4 +40,8 @@ public final class Variables { public static void set(String key, Value value) { variables.put(key, value); } + + public static void remove(String key) { + variables.remove(key); + } } diff --git a/src/com/annimon/ownlang/parser/Lexer.java b/src/com/annimon/ownlang/parser/Lexer.java index 455f8e8..516a503 100644 --- a/src/com/annimon/ownlang/parser/Lexer.java +++ b/src/com/annimon/ownlang/parser/Lexer.java @@ -173,6 +173,8 @@ public final class Lexer { case "def": addToken(TokenType.DEF); break; case "return": addToken(TokenType.RETURN); break; case "use": addToken(TokenType.USE); break; + case "match": addToken(TokenType.MATCH); break; + case "case": addToken(TokenType.CASE); break; default: addToken(TokenType.WORD, word); break; diff --git a/src/com/annimon/ownlang/parser/Parser.java b/src/com/annimon/ownlang/parser/Parser.java index 7b68c9a..1c90a13 100644 --- a/src/com/annimon/ownlang/parser/Parser.java +++ b/src/com/annimon/ownlang/parser/Parser.java @@ -1,5 +1,7 @@ package com.annimon.ownlang.parser; +import com.annimon.ownlang.lib.NumberValue; +import com.annimon.ownlang.lib.StringValue; import com.annimon.ownlang.lib.UserDefinedFunction; import com.annimon.ownlang.parser.ast.*; import java.util.ArrayList; @@ -250,6 +252,53 @@ public final class Parser { return new ArrayAccessExpression(variable, indices); } + private MatchExpression match() { + // match expression { + // case pattern1: result1 + // case pattern2 if extr: result2 + // } + final Expression expression = expression(); + consume(TokenType.LBRACE); + final List patterns = new ArrayList<>(); + do { + consume(TokenType.CASE); + MatchExpression.Pattern pattern = null; + final Token current = get(0); + if (match(TokenType.NUMBER)) { + pattern = new MatchExpression.ConstantPattern( + new NumberValue(Double.parseDouble(current.getText())) + ); + } else if (match(TokenType.HEX_NUMBER)) { + pattern = new MatchExpression.ConstantPattern( + new NumberValue(Long.parseLong(current.getText(), 16)) + ); + } else if (match(TokenType.TEXT)) { + pattern = new MatchExpression.ConstantPattern( + new StringValue(current.getText()) + ); + } else if (match(TokenType.WORD)) { + pattern = new MatchExpression.VariablePattern(current.getText()); + } + + if (pattern == null) { + throw new ParseException("Wrong pattern in match expression: " + current); + } + if (match(TokenType.IF)) { + pattern.optCondition = expression(); + } + + consume(TokenType.COLON); + if (lookMatch(0, TokenType.LBRACE)) { + pattern.result = block(); + } else { + pattern.result = new ReturnStatement(expression()); + } + patterns.add(pattern); + } while (!match(TokenType.RBRACE)); + + return new MatchExpression(expression, patterns); + } + private Expression expression() { return ternary(); } @@ -459,13 +508,40 @@ public final class Parser { } private Expression primary() { + if (match(TokenType.LPAREN)) { + Expression result = expression(); + match(TokenType.RPAREN); + return result; + } + + if (match(TokenType.COLONCOLON)) { + final String functionName = consume(TokenType.WORD).getText(); + return new FunctionReferenceExpression(functionName); + } + if (match(TokenType.MATCH)) { + return match(); + } + if (match(TokenType.DEF)) { + consume(TokenType.LPAREN); + final List argNames = new ArrayList<>(); + while (!match(TokenType.RPAREN)) { + argNames.add(consume(TokenType.WORD).getText()); + match(TokenType.COMMA); + } + Statement statement; + if (lookMatch(0, TokenType.EQ)) { + match(TokenType.EQ); + statement = new ReturnStatement(expression()); + } else { + statement = statementOrBlock(); + } + return new ValueExpression(new UserDefinedFunction(argNames, statement)); + } + return variable(); + } + + private Expression variable() { final Token current = get(0); - if (match(TokenType.NUMBER)) { - return new ValueExpression(Double.parseDouble(current.getText())); - } - if (match(TokenType.HEX_NUMBER)) { - return new ValueExpression(Long.parseLong(current.getText(), 16)); - } if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LBRACKET)) { return element(); } @@ -484,34 +560,20 @@ public final class Parser { if (match(TokenType.WORD)) { return new VariableExpression(current.getText()); } + return value(); + } + + private Expression value() { + final Token current = get(0); + if (match(TokenType.NUMBER)) { + return new ValueExpression(Double.parseDouble(current.getText())); + } + if (match(TokenType.HEX_NUMBER)) { + return new ValueExpression(Long.parseLong(current.getText(), 16)); + } if (match(TokenType.TEXT)) { return new ValueExpression(current.getText()); } - if (match(TokenType.COLONCOLON)) { - final String functionName = consume(TokenType.WORD).getText(); - return new FunctionReferenceExpression(functionName); - } - if (match(TokenType.DEF)) { - consume(TokenType.LPAREN); - final List argNames = new ArrayList<>(); - while (!match(TokenType.RPAREN)) { - argNames.add(consume(TokenType.WORD).getText()); - match(TokenType.COMMA); - } - Statement statement; - if (lookMatch(0, TokenType.EQ)) { - match(TokenType.EQ); - statement = new ReturnStatement(expression()); - } else { - statement = statementOrBlock(); - } - return new ValueExpression(new UserDefinedFunction(argNames, statement)); - } - if (match(TokenType.LPAREN)) { - Expression result = expression(); - match(TokenType.RPAREN); - return result; - } throw new ParseException("Unknown expression: " + current); } diff --git a/src/com/annimon/ownlang/parser/TokenType.java b/src/com/annimon/ownlang/parser/TokenType.java index 77e4723..1507c8b 100644 --- a/src/com/annimon/ownlang/parser/TokenType.java +++ b/src/com/annimon/ownlang/parser/TokenType.java @@ -24,6 +24,8 @@ public enum TokenType { DEF, RETURN, USE, + MATCH, + CASE, PLUS, // + MINUS, // - diff --git a/src/com/annimon/ownlang/parser/ast/MatchExpression.java b/src/com/annimon/ownlang/parser/ast/MatchExpression.java new file mode 100644 index 0000000..541577a --- /dev/null +++ b/src/com/annimon/ownlang/parser/ast/MatchExpression.java @@ -0,0 +1,119 @@ +package com.annimon.ownlang.parser.ast; + +import com.annimon.ownlang.lib.NumberValue; +import com.annimon.ownlang.lib.Value; +import com.annimon.ownlang.lib.Variables; +import java.util.List; + +/** + * + * @author aNNiMON + */ +public final class MatchExpression implements Expression { + + public final Expression expression; + public final List patterns; + + public MatchExpression(Expression expression, List patterns) { + this.expression = expression; + this.patterns = patterns; + } + + @Override + public Value eval() { + final Value value = expression.eval(); + for (Pattern p : patterns) { + if (p instanceof ConstantPattern) { + final ConstantPattern pattern = (ConstantPattern) p; + if (match(value, pattern.constant) && optMatches(p)) { + return evalResult(p.result); + } + } + if (p instanceof VariablePattern) { + final VariablePattern pattern = (VariablePattern) p; + if (pattern.variable.equals("_")) return evalResult(p.result); + + if (Variables.isExists(pattern.variable)) { + if (match(value, Variables.get(pattern.variable)) && optMatches(p)) { + return evalResult(p.result); + } + } else { + Variables.set(pattern.variable, value); + if (optMatches(p)) { + final Value result = evalResult(p.result);; + Variables.remove(pattern.variable); + return result; + } + Variables.remove(pattern.variable); + } + } + } + throw new RuntimeException("No pattern were matched"); + } + + private boolean match(Value value, Value constant) { + if (value.type() != constant.type()) return false; + return value.equals(constant); + } + + private boolean optMatches(Pattern pattern) { + if (pattern.optCondition == null) return true; + return pattern.optCondition.eval() != NumberValue.ZERO; + } + + private Value evalResult(Statement s) { + try { + s.execute(); + } catch (ReturnStatement ret) { + return ret.getResult(); + } + return NumberValue.ZERO; + } + + @Override + public void accept(Visitor visitor) { + visitor.visit(this); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("match ").append(expression).append(" {"); + for (Pattern p : patterns) { + sb.append("\n case ").append(p); + } + sb.append("\n}"); + return sb.toString(); + } + + public static abstract class Pattern { + public Statement result; + public Expression optCondition; + } + + public static class ConstantPattern extends Pattern { + public Value constant; + + public ConstantPattern(Value pattern) { + this.constant = pattern; + } + + @Override + public String toString() { + return constant + ": " + result; + } + } + + public static class VariablePattern extends Pattern { + public String variable; + + public VariablePattern(String pattern) { + this.variable = pattern; + } + + @Override + public String toString() { + return variable + ": " + result; + } + } +} diff --git a/src/com/annimon/ownlang/parser/ast/Visitor.java b/src/com/annimon/ownlang/parser/ast/Visitor.java index a09cf4f..1c1735d 100644 --- a/src/com/annimon/ownlang/parser/ast/Visitor.java +++ b/src/com/annimon/ownlang/parser/ast/Visitor.java @@ -25,6 +25,7 @@ public interface Visitor { void visit(FunctionalExpression s); void visit(IfStatement s); void visit(MapExpression s); + void visit(MatchExpression s); void visit(PrintStatement s); void visit(PrintlnStatement s); void visit(ReturnStatement s); diff --git a/src/com/annimon/ownlang/parser/visitors/AbstractVisitor.java b/src/com/annimon/ownlang/parser/visitors/AbstractVisitor.java index 8572117..3dfe0eb 100644 --- a/src/com/annimon/ownlang/parser/visitors/AbstractVisitor.java +++ b/src/com/annimon/ownlang/parser/visitors/AbstractVisitor.java @@ -125,6 +125,11 @@ public abstract class AbstractVisitor implements Visitor { entry.getValue().accept(this); } } + + @Override + public void visit(MatchExpression s) { + s.expression.accept(this); + } @Override public void visit(PrintStatement s) {