Skip to content

Commit 4cb076c

Browse files
committed
Добавлен оператор match (Pattern Matching)
1 parent 794d6f2 commit 4cb076c

File tree

9 files changed

+253
-62
lines changed

9 files changed

+253
-62
lines changed

examples/network/github_timeline.own

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,37 +27,18 @@ def show_github_events(event) {
2727
}
2828

2929
def github_event_type(event) {
30-
type = event.type
3130
repo = "https://github.com/" + event.repo.name
3231
payload = event.payload
33-
34-
if (type == "CommitCommentEvent") {
35-
return "commented commit in " + repo + "\n" + payload.comment.body
32+
return match event.type {
33+
case "CommitCommentEvent": "commented commit in " + repo + "\n" + payload.comment.body
34+
case "CreateEvent": "created " + payload.ref_type + " on " + repo
35+
case "DeleteEvent": "deleted " + payload.ref_type + " on " + repo
36+
case "ForkEvent": "forked repository " + repo
37+
case "IssueCommentEvent": "commented issue " + payload.issue.title + " on " + repo + "\n" + payload.comment.body
38+
case "IssuesEvent": payload.action + " issue '" + payload.issue.title + "' on " + repo
39+
case "PullRequestEvent": payload.action + " pull request #" + payload.number + " '" + payload.pull_request.title + "' on " + repo
40+
case "PushEvent": "pushed " + length(payload.commits) + " commits to " + repo
41+
case "WatchEvent": "start watching repository " + repo
42+
case type : type + " on " + repo
3643
}
37-
if (type == "CreateEvent") {
38-
return "created " + payload.ref_type + " on " + repo
39-
}
40-
if (type == "DeleteEvent") {
41-
return "deleted " + payload.ref_type + " on " + repo
42-
}
43-
if (type == "ForkEvent") {
44-
return "forked repository " + repo
45-
}
46-
if (type == "IssueCommentEvent") {
47-
return "commented issue " + payload.issue.title + " on " + repo + "\n" + payload.comment.body
48-
}
49-
if (type == "IssuesEvent") {
50-
return payload.action + " issue '" + payload.issue.title + "' on " + repo
51-
}
52-
if (type == "PullRequestEvent") {
53-
pr = payload.pull_request
54-
return payload.action + " pull request #" + payload.number + " '" + pr.title + "' on " + repo
55-
}
56-
if (type == "PushEvent") {
57-
return "pushed " + length(payload.commits) + " commits to " + repo
58-
}
59-
if (type == "WatchEvent") {
60-
return "start watching repository " + repo
61-
}
62-
return type + " on " + repo
6344
}

program.own

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ for i = 0, i < 4, i = i + 1 {
8181
}
8282

8383
// map
84-
map = {"+" : add, "-" : sub. "*" : mul, "/" : div}
84+
map = {"+" : add, "-" : sub, "*" : mul, "/" : div}
8585
map["%"] = def(x,y) = x % y
8686
map["pow"] = def(x,y) = pow(x, y)
8787
println map["+"]
@@ -149,4 +149,19 @@ println jsonencode({
149149
"key": "value",
150150
10: "1000"
151151
}
152-
})
152+
})
153+
154+
println ""
155+
156+
157+
def fact1(n) = match n {
158+
case 0: 1
159+
case n: n * fact1(n - 1)
160+
}
161+
def fact2(n) = match n {
162+
case n if n == 0: 1
163+
case _: n * fact2(n - 1)
164+
}
165+
166+
println fact1(6)
167+
println fact2(6)

src/com/annimon/ownlang/lib/Variables.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@ public static Value get(String key) {
4040
public static void set(String key, Value value) {
4141
variables.put(key, value);
4242
}
43+
44+
public static void remove(String key) {
45+
variables.remove(key);
46+
}
4347
}

src/com/annimon/ownlang/parser/Lexer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ private void tokenizeWord() {
173173
case "def": addToken(TokenType.DEF); break;
174174
case "return": addToken(TokenType.RETURN); break;
175175
case "use": addToken(TokenType.USE); break;
176+
case "match": addToken(TokenType.MATCH); break;
177+
case "case": addToken(TokenType.CASE); break;
176178
default:
177179
addToken(TokenType.WORD, word);
178180
break;

src/com/annimon/ownlang/parser/Parser.java

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.annimon.ownlang.parser;
22

3+
import com.annimon.ownlang.lib.NumberValue;
4+
import com.annimon.ownlang.lib.StringValue;
35
import com.annimon.ownlang.lib.UserDefinedFunction;
46
import com.annimon.ownlang.parser.ast.*;
57
import java.util.ArrayList;
@@ -250,6 +252,53 @@ private ArrayAccessExpression object() {
250252
return new ArrayAccessExpression(variable, indices);
251253
}
252254

255+
private MatchExpression match() {
256+
// match expression {
257+
// case pattern1: result1
258+
// case pattern2 if extr: result2
259+
// }
260+
final Expression expression = expression();
261+
consume(TokenType.LBRACE);
262+
final List<MatchExpression.Pattern> patterns = new ArrayList<>();
263+
do {
264+
consume(TokenType.CASE);
265+
MatchExpression.Pattern pattern = null;
266+
final Token current = get(0);
267+
if (match(TokenType.NUMBER)) {
268+
pattern = new MatchExpression.ConstantPattern(
269+
new NumberValue(Double.parseDouble(current.getText()))
270+
);
271+
} else if (match(TokenType.HEX_NUMBER)) {
272+
pattern = new MatchExpression.ConstantPattern(
273+
new NumberValue(Long.parseLong(current.getText(), 16))
274+
);
275+
} else if (match(TokenType.TEXT)) {
276+
pattern = new MatchExpression.ConstantPattern(
277+
new StringValue(current.getText())
278+
);
279+
} else if (match(TokenType.WORD)) {
280+
pattern = new MatchExpression.VariablePattern(current.getText());
281+
}
282+
283+
if (pattern == null) {
284+
throw new ParseException("Wrong pattern in match expression: " + current);
285+
}
286+
if (match(TokenType.IF)) {
287+
pattern.optCondition = expression();
288+
}
289+
290+
consume(TokenType.COLON);
291+
if (lookMatch(0, TokenType.LBRACE)) {
292+
pattern.result = block();
293+
} else {
294+
pattern.result = new ReturnStatement(expression());
295+
}
296+
patterns.add(pattern);
297+
} while (!match(TokenType.RBRACE));
298+
299+
return new MatchExpression(expression, patterns);
300+
}
301+
253302
private Expression expression() {
254303
return ternary();
255304
}
@@ -459,13 +508,40 @@ private Expression unary() {
459508
}
460509

461510
private Expression primary() {
462-
final Token current = get(0);
463-
if (match(TokenType.NUMBER)) {
464-
return new ValueExpression(Double.parseDouble(current.getText()));
511+
if (match(TokenType.LPAREN)) {
512+
Expression result = expression();
513+
match(TokenType.RPAREN);
514+
return result;
465515
}
466-
if (match(TokenType.HEX_NUMBER)) {
467-
return new ValueExpression(Long.parseLong(current.getText(), 16));
516+
517+
if (match(TokenType.COLONCOLON)) {
518+
final String functionName = consume(TokenType.WORD).getText();
519+
return new FunctionReferenceExpression(functionName);
520+
}
521+
if (match(TokenType.MATCH)) {
522+
return match();
523+
}
524+
if (match(TokenType.DEF)) {
525+
consume(TokenType.LPAREN);
526+
final List<String> argNames = new ArrayList<>();
527+
while (!match(TokenType.RPAREN)) {
528+
argNames.add(consume(TokenType.WORD).getText());
529+
match(TokenType.COMMA);
530+
}
531+
Statement statement;
532+
if (lookMatch(0, TokenType.EQ)) {
533+
match(TokenType.EQ);
534+
statement = new ReturnStatement(expression());
535+
} else {
536+
statement = statementOrBlock();
537+
}
538+
return new ValueExpression(new UserDefinedFunction(argNames, statement));
468539
}
540+
return variable();
541+
}
542+
543+
private Expression variable() {
544+
final Token current = get(0);
469545
if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.LBRACKET)) {
470546
return element();
471547
}
@@ -484,33 +560,19 @@ private Expression primary() {
484560
if (match(TokenType.WORD)) {
485561
return new VariableExpression(current.getText());
486562
}
487-
if (match(TokenType.TEXT)) {
488-
return new ValueExpression(current.getText());
489-
}
490-
if (match(TokenType.COLONCOLON)) {
491-
final String functionName = consume(TokenType.WORD).getText();
492-
return new FunctionReferenceExpression(functionName);
563+
return value();
564+
}
565+
566+
private Expression value() {
567+
final Token current = get(0);
568+
if (match(TokenType.NUMBER)) {
569+
return new ValueExpression(Double.parseDouble(current.getText()));
493570
}
494-
if (match(TokenType.DEF)) {
495-
consume(TokenType.LPAREN);
496-
final List<String> argNames = new ArrayList<>();
497-
while (!match(TokenType.RPAREN)) {
498-
argNames.add(consume(TokenType.WORD).getText());
499-
match(TokenType.COMMA);
500-
}
501-
Statement statement;
502-
if (lookMatch(0, TokenType.EQ)) {
503-
match(TokenType.EQ);
504-
statement = new ReturnStatement(expression());
505-
} else {
506-
statement = statementOrBlock();
507-
}
508-
return new ValueExpression(new UserDefinedFunction(argNames, statement));
571+
if (match(TokenType.HEX_NUMBER)) {
572+
return new ValueExpression(Long.parseLong(current.getText(), 16));
509573
}
510-
if (match(TokenType.LPAREN)) {
511-
Expression result = expression();
512-
match(TokenType.RPAREN);
513-
return result;
574+
if (match(TokenType.TEXT)) {
575+
return new ValueExpression(current.getText());
514576
}
515577
throw new ParseException("Unknown expression: " + current);
516578
}

src/com/annimon/ownlang/parser/TokenType.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public enum TokenType {
2424
DEF,
2525
RETURN,
2626
USE,
27+
MATCH,
28+
CASE,
2729

2830
PLUS, // +
2931
MINUS, // -
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.annimon.ownlang.parser.ast;
2+
3+
import com.annimon.ownlang.lib.NumberValue;
4+
import com.annimon.ownlang.lib.Value;
5+
import com.annimon.ownlang.lib.Variables;
6+
import java.util.List;
7+
8+
/**
9+
*
10+
* @author aNNiMON
11+
*/
12+
public final class MatchExpression implements Expression {
13+
14+
public final Expression expression;
15+
public final List<Pattern> patterns;
16+
17+
public MatchExpression(Expression expression, List<Pattern> patterns) {
18+
this.expression = expression;
19+
this.patterns = patterns;
20+
}
21+
22+
@Override
23+
public Value eval() {
24+
final Value value = expression.eval();
25+
for (Pattern p : patterns) {
26+
if (p instanceof ConstantPattern) {
27+
final ConstantPattern pattern = (ConstantPattern) p;
28+
if (match(value, pattern.constant) && optMatches(p)) {
29+
return evalResult(p.result);
30+
}
31+
}
32+
if (p instanceof VariablePattern) {
33+
final VariablePattern pattern = (VariablePattern) p;
34+
if (pattern.variable.equals("_")) return evalResult(p.result);
35+
36+
if (Variables.isExists(pattern.variable)) {
37+
if (match(value, Variables.get(pattern.variable)) && optMatches(p)) {
38+
return evalResult(p.result);
39+
}
40+
} else {
41+
Variables.set(pattern.variable, value);
42+
if (optMatches(p)) {
43+
final Value result = evalResult(p.result);;
44+
Variables.remove(pattern.variable);
45+
return result;
46+
}
47+
Variables.remove(pattern.variable);
48+
}
49+
}
50+
}
51+
throw new RuntimeException("No pattern were matched");
52+
}
53+
54+
private boolean match(Value value, Value constant) {
55+
if (value.type() != constant.type()) return false;
56+
return value.equals(constant);
57+
}
58+
59+
private boolean optMatches(Pattern pattern) {
60+
if (pattern.optCondition == null) return true;
61+
return pattern.optCondition.eval() != NumberValue.ZERO;
62+
}
63+
64+
private Value evalResult(Statement s) {
65+
try {
66+
s.execute();
67+
} catch (ReturnStatement ret) {
68+
return ret.getResult();
69+
}
70+
return NumberValue.ZERO;
71+
}
72+
73+
@Override
74+
public void accept(Visitor visitor) {
75+
visitor.visit(this);
76+
}
77+
78+
@Override
79+
public String toString() {
80+
final StringBuilder sb = new StringBuilder();
81+
sb.append("match ").append(expression).append(" {");
82+
for (Pattern p : patterns) {
83+
sb.append("\n case ").append(p);
84+
}
85+
sb.append("\n}");
86+
return sb.toString();
87+
}
88+
89+
public static abstract class Pattern {
90+
public Statement result;
91+
public Expression optCondition;
92+
}
93+
94+
public static class ConstantPattern extends Pattern {
95+
public Value constant;
96+
97+
public ConstantPattern(Value pattern) {
98+
this.constant = pattern;
99+
}
100+
101+
@Override
102+
public String toString() {
103+
return constant + ": " + result;
104+
}
105+
}
106+
107+
public static class VariablePattern extends Pattern {
108+
public String variable;
109+
110+
public VariablePattern(String pattern) {
111+
this.variable = pattern;
112+
}
113+
114+
@Override
115+
public String toString() {
116+
return variable + ": " + result;
117+
}
118+
}
119+
}

src/com/annimon/ownlang/parser/ast/Visitor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public interface Visitor {
2525
void visit(FunctionalExpression s);
2626
void visit(IfStatement s);
2727
void visit(MapExpression s);
28+
void visit(MatchExpression s);
2829
void visit(PrintStatement s);
2930
void visit(PrintlnStatement s);
3031
void visit(ReturnStatement s);

0 commit comments

Comments
 (0)