diff options
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/ArgSpec.java | 26 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/DefSpec.java | 2 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java | 1 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/FnExpression.java | 2 | ||||
| -rw-r--r-- | ast/src/main/java/lv/enes/orang/ast/VoidExpression.java | 14 | ||||
| -rw-r--r-- | dice.orang | 6 | ||||
| -rw-r--r-- | grammar.bnf | 5 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/Builtins.java | 7 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/Evaluator.java | 5 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/Parser.java | 12 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/value/Function.java | 18 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/value/Nothing.java | 21 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/value/PartialFunction.java | 44 | ||||
| -rw-r--r-- | orang/src/main/java/lv/enes/orang/value/Value.java | 3 | ||||
| -rw-r--r-- | orang/src/main/resources/lv/enes/orang/prelude.orang | 4 |
15 files changed, 114 insertions, 56 deletions
diff --git a/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java b/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java new file mode 100644 index 0000000..e167e8f --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/ArgSpec.java | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | public class ArgSpec { | ||
| 4 | public final Type type; | ||
| 5 | public final String name; | ||
| 6 | |||
| 7 | private static final ArgSpec NOTHING = new ArgSpec(Type.NOTHING, null); | ||
| 8 | |||
| 9 | public static ArgSpec nothing() { | ||
| 10 | return NOTHING; | ||
| 11 | } | ||
| 12 | |||
| 13 | public static ArgSpec named(String name) { | ||
| 14 | return new ArgSpec(Type.NAMED, name); | ||
| 15 | } | ||
| 16 | |||
| 17 | private ArgSpec(Type type, String name) { | ||
| 18 | this.type = type; | ||
| 19 | this.name = name; | ||
| 20 | } | ||
| 21 | |||
| 22 | public enum Type { | ||
| 23 | NOTHING, | ||
| 24 | NAMED, | ||
| 25 | } | ||
| 26 | } | ||
diff --git a/ast/src/main/java/lv/enes/orang/ast/DefSpec.java b/ast/src/main/java/lv/enes/orang/ast/DefSpec.java index 2233d3f..5e6cc68 100644 --- a/ast/src/main/java/lv/enes/orang/ast/DefSpec.java +++ b/ast/src/main/java/lv/enes/orang/ast/DefSpec.java | |||
| @@ -2,5 +2,5 @@ package lv.enes.orang.ast; | |||
| 2 | 2 | ||
| 3 | import java.util.List; | 3 | import java.util.List; |
| 4 | 4 | ||
| 5 | public record DefSpec(String name, List<String> args) { | 5 | public record DefSpec(String name, List<ArgSpec> args) { |
| 6 | } | 6 | } |
diff --git a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java index ddb1157..70f8a5e 100644 --- a/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java +++ b/ast/src/main/java/lv/enes/orang/ast/ExpressionVisitor.java | |||
| @@ -19,4 +19,5 @@ public interface ExpressionVisitor<R, E extends OrangException> { | |||
| 19 | R visitStringLiteral(StringLiteral expr) throws E; | 19 | R visitStringLiteral(StringLiteral expr) throws E; |
| 20 | R visitUnaryExpression(UnaryExpression expr) throws E; | 20 | R visitUnaryExpression(UnaryExpression expr) throws E; |
| 21 | R visitVariable(VariableExpression expr) throws E; | 21 | R visitVariable(VariableExpression expr) throws E; |
| 22 | R visitVoidExpression() throws E; | ||
| 22 | } | 23 | } |
diff --git a/ast/src/main/java/lv/enes/orang/ast/FnExpression.java b/ast/src/main/java/lv/enes/orang/ast/FnExpression.java index 68f43af..0bf322c 100644 --- a/ast/src/main/java/lv/enes/orang/ast/FnExpression.java +++ b/ast/src/main/java/lv/enes/orang/ast/FnExpression.java | |||
| @@ -3,7 +3,7 @@ package lv.enes.orang.ast; | |||
| 3 | import lv.enes.orang.utils.NonEmptyList; | 3 | import lv.enes.orang.utils.NonEmptyList; |
| 4 | import lv.enes.orang.core.OrangException; | 4 | import lv.enes.orang.core.OrangException; |
| 5 | 5 | ||
| 6 | public record FnExpression(NonEmptyList<String> args, Expression body) implements Expression { | 6 | public record FnExpression(NonEmptyList<ArgSpec> args, Expression body) implements Expression { |
| 7 | @Override | 7 | @Override |
| 8 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { | 8 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { |
| 9 | return visitor.visitFnExpression(this); | 9 | return visitor.visitFnExpression(this); |
diff --git a/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java b/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java new file mode 100644 index 0000000..39d164d --- /dev/null +++ b/ast/src/main/java/lv/enes/orang/ast/VoidExpression.java | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | package lv.enes.orang.ast; | ||
| 2 | |||
| 3 | import lv.enes.orang.core.OrangException; | ||
| 4 | |||
| 5 | public final class VoidExpression implements Expression { | ||
| 6 | public static final VoidExpression INSTANCE = new VoidExpression(); | ||
| 7 | |||
| 8 | private VoidExpression() {} | ||
| 9 | |||
| 10 | @Override | ||
| 11 | public <R, E extends OrangException> R accept(ExpressionVisitor<R, E> visitor) throws E { | ||
| 12 | return visitor.visitVoidExpression(); | ||
| 13 | } | ||
| 14 | } | ||
| @@ -1,14 +1,14 @@ | |||
| 1 | def dice sides times = | 1 | def dice sides times = |
| 2 | let once _ = randInt 1 (sides + 1) in | 2 | let once () = randInt 1 (sides + 1) in |
| 3 | let aux acc times = | 3 | let aux acc times = |
| 4 | if times ?= 0 then acc | 4 | if times ?= 0 then acc |
| 5 | else aux (acc + once ()) (times - 1) | 5 | else aux (acc + once ()) (times - 1) |
| 6 | in | 6 | in |
| 7 | aux 0 times | 7 | aux 0 times |
| 8 | 8 | ||
| 9 | def guessing_game _ = | 9 | def guessing_game () = |
| 10 | let secret = randInt 1 101 | 10 | let secret = randInt 1 101 |
| 11 | and guessOnce _ = do | 11 | and guessOnce () = do |
| 12 | print "Make your guess: "; | 12 | print "Make your guess: "; |
| 13 | let guess = readInt () in | 13 | let guess = readInt () in |
| 14 | if secret > guess then do | 14 | if secret > guess then do |
diff --git a/grammar.bnf b/grammar.bnf index 7c18f8f..9ddaffc 100644 --- a/grammar.bnf +++ b/grammar.bnf | |||
| @@ -4,7 +4,7 @@ program ::= (statement ';'?)*; | |||
| 4 | statement ::= definition | expression; | 4 | statement ::= definition | expression; |
| 5 | definition ::= 'def' def-spec '=' expression; | 5 | definition ::= 'def' def-spec '=' expression; |
| 6 | def-spec ::= IDENTIFIER arg-spec*; | 6 | def-spec ::= IDENTIFIER arg-spec*; |
| 7 | arg-spec ::= IDENTIFIER; | 7 | arg-spec ::= IDENTIFIER | '(' ')'; |
| 8 | expression ::= binary-expression | unary-expression; | 8 | expression ::= binary-expression | unary-expression; |
| 9 | unary-expression ::= unop+ simple-expression; | 9 | unary-expression ::= unop+ simple-expression; |
| 10 | binary-expression ::= call-expression (binop call-expression)*; | 10 | binary-expression ::= call-expression (binop call-expression)*; |
| @@ -13,6 +13,7 @@ binop ::= '*' | '/' | '+' | '-' | '?=' | '/=' | '>' | '>=' | '<' | '<='; | |||
| 13 | unop ::= '+' | '-' | '!'; | 13 | unop ::= '+' | '-' | '!'; |
| 14 | simple-expression ::= '(' expression ')' | 14 | simple-expression ::= '(' expression ')' |
| 15 | | 'true' | 'false' | 15 | | 'true' | 'false' |
| 16 | | '(' ')' | ||
| 16 | | INTEGER | 17 | | INTEGER |
| 17 | | IDENTIFIER | 18 | | IDENTIFIER |
| 18 | | STRING | 19 | | STRING |
| @@ -22,7 +23,7 @@ simple-expression ::= '(' expression ')' | |||
| 22 | | fn-expression | 23 | | fn-expression |
| 23 | | do-expression; | 24 | | do-expression; |
| 24 | 25 | ||
| 25 | array ::= '(' ')' | '[' ']' | '[' expression (',' expression)* ','? ']'; | 26 | array ::= '[' ']' | '[' expression (',' expression)* ','? ']'; |
| 26 | 27 | ||
| 27 | if-else-expression ::= 'if' expression 'then' expression 'else' expression; | 28 | if-else-expression ::= 'if' expression 'then' expression 'else' expression; |
| 28 | let-in-expression ::= 'let' def-spec '=' expression ('and' def-spec '=' expression)* 'in' expression; | 29 | let-in-expression ::= 'let' def-spec '=' expression ('and' def-spec '=' expression)* 'in' expression; |
diff --git a/orang/src/main/java/lv/enes/orang/Builtins.java b/orang/src/main/java/lv/enes/orang/Builtins.java index 0a76387..de296ac 100644 --- a/orang/src/main/java/lv/enes/orang/Builtins.java +++ b/orang/src/main/java/lv/enes/orang/Builtins.java | |||
| @@ -79,13 +79,6 @@ public final class Builtins { | |||
| 79 | } | 79 | } |
| 80 | 80 | ||
| 81 | private static Value readLn(List<Value> args) throws OrangRuntimeException { | 81 | private static Value readLn(List<Value> args) throws OrangRuntimeException { |
| 82 | assert args.size() == 1; | ||
| 83 | |||
| 84 | var arg = args.getFirst(); | ||
| 85 | if (!(arg instanceof Array) || !((Array) arg).items().isEmpty()) { | ||
| 86 | log.warn("You should call readLn with an empty tuple like `readLn ()`"); | ||
| 87 | } | ||
| 88 | |||
| 89 | try { | 82 | try { |
| 90 | return new OrangString(STDIN.readLine()); | 83 | return new OrangString(STDIN.readLine()); |
| 91 | } catch (IOException e) { | 84 | } catch (IOException e) { |
diff --git a/orang/src/main/java/lv/enes/orang/Evaluator.java b/orang/src/main/java/lv/enes/orang/Evaluator.java index e2e96ff..5d09508 100644 --- a/orang/src/main/java/lv/enes/orang/Evaluator.java +++ b/orang/src/main/java/lv/enes/orang/Evaluator.java | |||
| @@ -141,4 +141,9 @@ public record Evaluator(Scope scope, Value lastResult) implements ExpressionVisi | |||
| 141 | public Value visitVariable(VariableExpression expr) throws OrangRuntimeException { | 141 | public Value visitVariable(VariableExpression expr) throws OrangRuntimeException { |
| 142 | return scope.getDefinition(expr.name()); | 142 | return scope.getDefinition(expr.name()); |
| 143 | } | 143 | } |
| 144 | |||
| 145 | @Override | ||
| 146 | public Value visitVoidExpression() { | ||
| 147 | return Nothing.INSTANCE; | ||
| 148 | } | ||
| 144 | } | 149 | } |
diff --git a/orang/src/main/java/lv/enes/orang/Parser.java b/orang/src/main/java/lv/enes/orang/Parser.java index 4011bd7..77abe24 100644 --- a/orang/src/main/java/lv/enes/orang/Parser.java +++ b/orang/src/main/java/lv/enes/orang/Parser.java | |||
| @@ -91,11 +91,15 @@ public class Parser { | |||
| 91 | return new ArrayExpression(Collections.unmodifiableList(items)); | 91 | return new ArrayExpression(Collections.unmodifiableList(items)); |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | private List<String> parseArgSpecs() { | 94 | private List<ArgSpec> parseArgSpecs() throws ParserException { |
| 95 | var argSpecs = new ArrayList<String>(); | 95 | var argSpecs = new ArrayList<ArgSpec>(); |
| 96 | while (true) { | 96 | while (true) { |
| 97 | if (input.peek().type() == TokenType.IDENTIFIER) { | 97 | if (input.peek().type() == TokenType.IDENTIFIER) { |
| 98 | argSpecs.add(input.next().literal()); | 98 | argSpecs.add(ArgSpec.named(input.next().literal())); |
| 99 | } else if (input.peek().type() == TokenType.PAREN_LEFT) { | ||
| 100 | consumeToken(TokenType.PAREN_LEFT); | ||
| 101 | consumeToken(TokenType.PAREN_RIGHT); | ||
| 102 | argSpecs.add(ArgSpec.nothing()); | ||
| 99 | } else { | 103 | } else { |
| 100 | break; | 104 | break; |
| 101 | } | 105 | } |
| @@ -224,7 +228,7 @@ public class Parser { | |||
| 224 | case PAREN_LEFT -> { | 228 | case PAREN_LEFT -> { |
| 225 | consumeToken(TokenType.PAREN_LEFT); | 229 | consumeToken(TokenType.PAREN_LEFT); |
| 226 | if (maybeConsumeToken(TokenType.PAREN_RIGHT)) { | 230 | if (maybeConsumeToken(TokenType.PAREN_RIGHT)) { |
| 227 | yield new ArrayExpression(List.of()); | 231 | yield VoidExpression.INSTANCE; |
| 228 | } | 232 | } |
| 229 | var expr = parseExpression(); | 233 | var expr = parseExpression(); |
| 230 | consumeToken(TokenType.PAREN_RIGHT); | 234 | consumeToken(TokenType.PAREN_RIGHT); |
diff --git a/orang/src/main/java/lv/enes/orang/value/Function.java b/orang/src/main/java/lv/enes/orang/value/Function.java index 901776e..6e3c83a 100644 --- a/orang/src/main/java/lv/enes/orang/value/Function.java +++ b/orang/src/main/java/lv/enes/orang/value/Function.java | |||
| @@ -1,14 +1,11 @@ | |||
| 1 | package lv.enes.orang.value; | 1 | package lv.enes.orang.value; |
| 2 | 2 | ||
| 3 | import lv.enes.orang.Evaluator; | 3 | import lv.enes.orang.*; |
| 4 | import lv.enes.orang.ImmutableScope; | 4 | import lv.enes.orang.ast.ArgSpec; |
| 5 | import lv.enes.orang.OrangRuntimeException; | ||
| 6 | import lv.enes.orang.Scope; | ||
| 7 | import lv.enes.orang.ast.Expression; | 5 | import lv.enes.orang.ast.Expression; |
| 6 | import lv.enes.orang.utils.NonEmptyList; | ||
| 8 | 7 | ||
| 9 | import java.util.List; | 8 | public record Function(Scope scope, NonEmptyList<ArgSpec> args, Expression body) implements Value { |
| 10 | |||
| 11 | public record Function(Scope scope, List<String> args, Expression body) implements Value { | ||
| 12 | @Override | 9 | @Override |
| 13 | public String typeName() { | 10 | public String typeName() { |
| 14 | return "Function"; | 11 | return "Function"; |
| @@ -21,11 +18,6 @@ public record Function(Scope scope, List<String> args, Expression body) implemen | |||
| 21 | 18 | ||
| 22 | @Override | 19 | @Override |
| 23 | public Value call(Value param) throws OrangRuntimeException { | 20 | public Value call(Value param) throws OrangRuntimeException { |
| 24 | if (args.size() == 1) { | 21 | return PartialFunction.of(scope, args, body, param); |
| 25 | var eval = new Evaluator(ImmutableScope.of(scope, args.getFirst(), param)); | ||
| 26 | return eval.visit(body); | ||
| 27 | } else { | ||
| 28 | return new PartialFunction(scope, args, List.of(param), body); | ||
| 29 | } | ||
| 30 | } | 22 | } |
| 31 | } | 23 | } |
diff --git a/orang/src/main/java/lv/enes/orang/value/Nothing.java b/orang/src/main/java/lv/enes/orang/value/Nothing.java new file mode 100644 index 0000000..4a90010 --- /dev/null +++ b/orang/src/main/java/lv/enes/orang/value/Nothing.java | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | package lv.enes.orang.value; | ||
| 2 | |||
| 3 | import lombok.EqualsAndHashCode; | ||
| 4 | |||
| 5 | @EqualsAndHashCode | ||
| 6 | public final class Nothing implements Value { | ||
| 7 | public static final Nothing INSTANCE = new Nothing(); | ||
| 8 | |||
| 9 | private Nothing() { | ||
| 10 | } | ||
| 11 | |||
| 12 | @Override | ||
| 13 | public String typeName() { | ||
| 14 | return "Nothing"; | ||
| 15 | } | ||
| 16 | |||
| 17 | @Override | ||
| 18 | public String stringify() { | ||
| 19 | return "()"; | ||
| 20 | } | ||
| 21 | } | ||
diff --git a/orang/src/main/java/lv/enes/orang/value/PartialFunction.java b/orang/src/main/java/lv/enes/orang/value/PartialFunction.java index 0dbf530..cee0cd4 100644 --- a/orang/src/main/java/lv/enes/orang/value/PartialFunction.java +++ b/orang/src/main/java/lv/enes/orang/value/PartialFunction.java | |||
| @@ -1,17 +1,30 @@ | |||
| 1 | package lv.enes.orang.value; | 1 | package lv.enes.orang.value; |
| 2 | 2 | ||
| 3 | import lv.enes.orang.Evaluator; | 3 | import lv.enes.orang.*; |
| 4 | import lv.enes.orang.ImmutableScope; | 4 | import lv.enes.orang.ast.ArgSpec; |
| 5 | import lv.enes.orang.OrangRuntimeException; | ||
| 6 | import lv.enes.orang.Scope; | ||
| 7 | import lv.enes.orang.ast.Expression; | 5 | import lv.enes.orang.ast.Expression; |
| 6 | import lv.enes.orang.utils.NonEmptyList; | ||
| 8 | 7 | ||
| 9 | import java.util.ArrayList; | 8 | public record PartialFunction(Scope scope, NonEmptyList<ArgSpec> remainingArgs, Expression body) implements Value { |
| 10 | import java.util.Collections; | 9 | public static Value of(Scope scope, NonEmptyList<ArgSpec> remainingArgs, Expression body, Value param) throws OrangRuntimeException { |
| 11 | import java.util.HashMap; | 10 | var spec = remainingArgs.getFirst(); |
| 12 | import java.util.List; | 11 | var newScope = MutableScope.of(scope); |
| 12 | switch (spec.type) { | ||
| 13 | case NAMED -> newScope.setDefinition(spec.name, param); | ||
| 14 | case NOTHING -> { | ||
| 15 | if (!(param instanceof Nothing)) { | ||
| 16 | throw new OrangRuntimeException(STR."Expected () as a parameter but got \{param.typeName()}"); | ||
| 17 | } | ||
| 18 | } | ||
| 19 | } | ||
| 20 | |||
| 21 | if (remainingArgs.size() == 1) { | ||
| 22 | return new Evaluator(newScope).visit(body); | ||
| 23 | } else { | ||
| 24 | return new PartialFunction(newScope, new NonEmptyList<>(remainingArgs.subList(1, remainingArgs.size())), body); | ||
| 25 | } | ||
| 26 | } | ||
| 13 | 27 | ||
| 14 | public record PartialFunction(Scope scope, List<String> args, List<Value> params, Expression body) implements Value { | ||
| 15 | @Override | 28 | @Override |
| 16 | public String typeName() { | 29 | public String typeName() { |
| 17 | return "Function"; | 30 | return "Function"; |
| @@ -24,17 +37,6 @@ public record PartialFunction(Scope scope, List<String> args, List<Value> params | |||
| 24 | 37 | ||
| 25 | @Override | 38 | @Override |
| 26 | public Value call(Value param) throws OrangRuntimeException { | 39 | public Value call(Value param) throws OrangRuntimeException { |
| 27 | var newParams = new ArrayList<>(params); | 40 | return PartialFunction.of(scope, remainingArgs, body, param); |
| 28 | newParams.add(param); | ||
| 29 | if (newParams.size() == args.size()) { | ||
| 30 | var newDefs = new HashMap<String, Value>(); | ||
| 31 | for (var i = 0; i < args.size(); i++) { | ||
| 32 | newDefs.put(args.get(i), newParams.get(i)); | ||
| 33 | } | ||
| 34 | var eval = new Evaluator(ImmutableScope.of(scope, newDefs)); | ||
| 35 | return eval.visit(body); | ||
| 36 | } | ||
| 37 | |||
| 38 | return new PartialFunction(scope, args, Collections.unmodifiableList(newParams), body); | ||
| 39 | } | 41 | } |
| 40 | } | 42 | } |
diff --git a/orang/src/main/java/lv/enes/orang/value/Value.java b/orang/src/main/java/lv/enes/orang/value/Value.java index fa8275c..0816dc2 100644 --- a/orang/src/main/java/lv/enes/orang/value/Value.java +++ b/orang/src/main/java/lv/enes/orang/value/Value.java | |||
| @@ -3,8 +3,7 @@ package lv.enes.orang.value; | |||
| 3 | import lv.enes.orang.OrangRuntimeException; | 3 | import lv.enes.orang.OrangRuntimeException; |
| 4 | 4 | ||
| 5 | public sealed interface Value | 5 | public sealed interface Value |
| 6 | permits Array, BuiltinFunction, Function, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, | 6 | permits Array, BuiltinFunction, Function, Nothing, OrangBoolean, OrangInteger, OrangString, PartialBuiltinFunction, PartialFunction, Undefined { |
| 7 | PartialFunction, Undefined { | ||
| 8 | String typeName(); | 7 | String typeName(); |
| 9 | String stringify(); | 8 | String stringify(); |
| 10 | 9 | ||
diff --git a/orang/src/main/resources/lv/enes/orang/prelude.orang b/orang/src/main/resources/lv/enes/orang/prelude.orang index d29eef3..5b1d2d1 100644 --- a/orang/src/main/resources/lv/enes/orang/prelude.orang +++ b/orang/src/main/resources/lv/enes/orang/prelude.orang | |||
| @@ -4,10 +4,10 @@ def len arrayOrString = __builtin_len arrayOrString | |||
| 4 | def parseInt stringOrInt = __builtin_parseInt stringOrInt | 4 | def parseInt stringOrInt = __builtin_parseInt stringOrInt |
| 5 | def print anything = __builtin_print anything | 5 | def print anything = __builtin_print anything |
| 6 | def randInt min max = __builtin_randInt min max | 6 | def randInt min max = __builtin_randInt min max |
| 7 | def readLn _ = __builtin_readLn () | 7 | def readLn () = __builtin_readLn () |
| 8 | 8 | ||
| 9 | # standard library | 9 | # standard library |
| 10 | def readInt _ = parseInt (readLn ()) | 10 | def readInt () = parseInt (readLn ()) |
| 11 | def printLn x = do print x; print "\n"; x end | 11 | def printLn x = do print x; print "\n"; x end |
| 12 | 12 | ||
| 13 | # repl intro :) | 13 | # repl intro :) |