Добавлен оператор match (Pattern Matching)

This commit is contained in:
Victor 2016-01-15 21:42:04 +02:00
parent 794d6f2d76
commit 4cb076ca6d
9 changed files with 254 additions and 63 deletions

View File

@ -27,37 +27,18 @@ def show_github_events(event) {
} }
def github_event_type(event) { def github_event_type(event) {
type = event.type
repo = "https://github.com/" + event.repo.name repo = "https://github.com/" + event.repo.name
payload = event.payload payload = event.payload
return match event.type {
if (type == "CommitCommentEvent") { case "CommitCommentEvent": "commented commit in " + repo + "\n" + payload.comment.body
return "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
} }

View File

@ -81,7 +81,7 @@ for i = 0, i < 4, i = i + 1 {
} }
// map // map
map = {"+" : add, "-" : sub. "*" : mul, "/" : div} map = {"+" : add, "-" : sub, "*" : mul, "/" : div}
map["%"] = def(x,y) = x % y map["%"] = def(x,y) = x % y
map["pow"] = def(x,y) = pow(x, y) map["pow"] = def(x,y) = pow(x, y)
println map["+"] println map["+"]
@ -150,3 +150,18 @@ println jsonencode({
10: "1000" 10: "1000"
} }
}) })
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)

View File

@ -40,4 +40,8 @@ public final class Variables {
public static void set(String key, Value value) { public static void set(String key, Value value) {
variables.put(key, value); variables.put(key, value);
} }
public static void remove(String key) {
variables.remove(key);
}
} }

View File

@ -173,6 +173,8 @@ public final class Lexer {
case "def": addToken(TokenType.DEF); break; case "def": addToken(TokenType.DEF); break;
case "return": addToken(TokenType.RETURN); break; case "return": addToken(TokenType.RETURN); break;
case "use": addToken(TokenType.USE); break; case "use": addToken(TokenType.USE); break;
case "match": addToken(TokenType.MATCH); break;
case "case": addToken(TokenType.CASE); break;
default: default:
addToken(TokenType.WORD, word); addToken(TokenType.WORD, word);
break; break;

View File

@ -1,5 +1,7 @@
package com.annimon.ownlang.parser; 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.lib.UserDefinedFunction;
import com.annimon.ownlang.parser.ast.*; import com.annimon.ownlang.parser.ast.*;
import java.util.ArrayList; import java.util.ArrayList;
@ -250,6 +252,53 @@ public final class Parser {
return new ArrayAccessExpression(variable, indices); 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<MatchExpression.Pattern> 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() { private Expression expression() {
return ternary(); return ternary();
} }
@ -459,13 +508,40 @@ public final class Parser {
} }
private Expression primary() { 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<String> 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); 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)) { if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LBRACKET)) {
return element(); return element();
} }
@ -484,34 +560,20 @@ public final class Parser {
if (match(TokenType.WORD)) { if (match(TokenType.WORD)) {
return new VariableExpression(current.getText()); 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)) { if (match(TokenType.TEXT)) {
return new ValueExpression(current.getText()); 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<String> 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); throw new ParseException("Unknown expression: " + current);
} }

View File

@ -24,6 +24,8 @@ public enum TokenType {
DEF, DEF,
RETURN, RETURN,
USE, USE,
MATCH,
CASE,
PLUS, // + PLUS, // +
MINUS, // - MINUS, // -

View File

@ -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<Pattern> patterns;
public MatchExpression(Expression expression, List<Pattern> 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;
}
}
}

View File

@ -25,6 +25,7 @@ public interface Visitor {
void visit(FunctionalExpression s); void visit(FunctionalExpression s);
void visit(IfStatement s); void visit(IfStatement s);
void visit(MapExpression s); void visit(MapExpression s);
void visit(MatchExpression s);
void visit(PrintStatement s); void visit(PrintStatement s);
void visit(PrintlnStatement s); void visit(PrintlnStatement s);
void visit(ReturnStatement s); void visit(ReturnStatement s);

View File

@ -126,6 +126,11 @@ public abstract class AbstractVisitor implements Visitor {
} }
} }
@Override
public void visit(MatchExpression s) {
s.expression.accept(this);
}
@Override @Override
public void visit(PrintStatement s) { public void visit(PrintStatement s) {
s.expression.accept(this); s.expression.accept(this);