diff --git a/src/vm/wren_compiler.c b/src/vm/wren_compiler.c index 92b16cac0..5b6120be2 100644 --- a/src/vm/wren_compiler.c +++ b/src/vm/wren_compiler.c @@ -58,6 +58,9 @@ typedef enum TOKEN_RIGHT_BRACKET, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, + TOKEN_AT, + TOKEN_ATLT, + TOKEN_ATGT, TOKEN_COLON, TOKEN_DOT, TOKEN_DOTDOT, @@ -73,6 +76,8 @@ typedef enum TOKEN_GTGT, TOKEN_PIPE, TOKEN_PIPEPIPE, + TOKEN_PIPELT, + TOKEN_PIPEGT, TOKEN_CARET, TOKEN_AMP, TOKEN_AMPAMP, @@ -1112,11 +1117,35 @@ static void nextToken(Parser* parser) case '~': makeToken(parser, TOKEN_TILDE); return; case '?': makeToken(parser, TOKEN_QUESTION); return; - case '|': twoCharToken(parser, '|', TOKEN_PIPEPIPE, TOKEN_PIPE); return; + case '|': + if (matchChar(parser, '|')) + { + makeToken(parser, TOKEN_PIPEPIPE); + } + else if (matchChar(parser, '<')) + { + makeToken(parser, TOKEN_PIPELT); + } + else + { + twoCharToken(parser, '>', TOKEN_PIPEGT, TOKEN_PIPE); + } + return; case '&': twoCharToken(parser, '&', TOKEN_AMPAMP, TOKEN_AMP); return; case '=': twoCharToken(parser, '=', TOKEN_EQEQ, TOKEN_EQ); return; case '!': twoCharToken(parser, '=', TOKEN_BANGEQ, TOKEN_BANG); return; + case '@': + if (matchChar(parser, '<')) + { + makeToken(parser, TOKEN_ATLT); + } + else + { + twoCharToken(parser, '>', TOKEN_ATGT, TOKEN_AT); + } + return; + case '.': if (matchChar(parser, '.')) { @@ -1714,6 +1743,8 @@ typedef enum PREC_LOWEST, PREC_ASSIGNMENT, // = PREC_CONDITIONAL, // ?: + PREC_FN_COMPOSITION_CALL, // |< |> + PREC_FN_COMPOSITION, // @< @> PREC_LOGICAL_OR, // || PREC_LOGICAL_AND, // && PREC_EQUALITY, // == != @@ -2533,6 +2564,65 @@ static void call(Compiler* compiler, bool canAssign) namedCall(compiler, canAssign, CODE_CALL_0); } +static void fnCompositionCall(Compiler* compiler, bool canAssign, bool isForward) +{ + GrammarRule* rule = getRule(compiler->parser->previous.type); + + // An infix operator cannot end an expression. + ignoreNewlines(compiler); + + // Compile the right-hand side. + parsePrecedence(compiler, (Precedence)(rule->precedence + isForward)); + + if (isForward) + { + emitOp(compiler, CODE_SWAP); + } + callMethod(compiler, 1, "call(_)", 7); +} + +static void backwardFnCompositionCall(Compiler* compiler, bool canAssign) +{ + return fnCompositionCall(compiler, canAssign, false); +} + +static void forwardFnCompositionCall(Compiler* compiler, bool canAssign) +{ + return fnCompositionCall(compiler, canAssign, true); +} + +static void fnComposition(Compiler* compiler, bool canAssign, bool isForward) +{ + loadCoreVariable(compiler, "ComposedFn"); + + // Prepare the stack to call the helper class + emitOp(compiler, CODE_SWAP); + + GrammarRule* rule = getRule(compiler->parser->previous.type); + + // An infix operator cannot end an expression. + ignoreNewlines(compiler); + + // Compile the right-hand side. + parsePrecedence(compiler, (Precedence)(rule->precedence + isForward)); + + if (isForward) + { + emitOp(compiler, CODE_SWAP); + } + callMethod(compiler, 2, "new(_,_)", 8); +} + +static void backwardFnComposition(Compiler* compiler, bool canAssign) +{ + fnComposition(compiler, canAssign, false); +} + +static void forwardFnComposition(Compiler* compiler, bool canAssign) +{ + fnComposition(compiler, canAssign, true); +} + static void and_(Compiler* compiler, bool canAssign) { ignoreNewlines(compiler); @@ -2754,6 +2844,9 @@ GrammarRule rules[] = /* TOKEN_RIGHT_BRACKET */ UNUSED, /* TOKEN_LEFT_BRACE */ PREFIX(map), /* TOKEN_RIGHT_BRACE */ UNUSED, + /* TOKEN_AT */ UNUSED, + /* TOKEN_ATLT */ INFIX(PREC_FN_COMPOSITION, backwardFnComposition), + /* TOKEN_ATGT */ INFIX(PREC_FN_COMPOSITION, forwardFnComposition), /* TOKEN_COLON */ UNUSED, /* TOKEN_DOT */ INFIX(PREC_CALL, call), /* TOKEN_DOTDOT */ INFIX_OPERATOR(PREC_RANGE, ".."), @@ -2769,6 +2862,8 @@ GrammarRule rules[] = /* TOKEN_GTGT */ INFIX_OPERATOR(PREC_BITWISE_SHIFT, ">>"), /* TOKEN_PIPE */ INFIX_OPERATOR(PREC_BITWISE_OR, "|"), /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGICAL_OR, or_), + /* TOKEN_PIPELT */ INFIX(PREC_FN_COMPOSITION_CALL, backwardFnCompositionCall), + /* TOKEN_PIPEGT */ INFIX(PREC_FN_COMPOSITION_CALL, forwardFnCompositionCall), /* TOKEN_CARET */ INFIX_OPERATOR(PREC_BITWISE_XOR, "^"), /* TOKEN_AMP */ INFIX_OPERATOR(PREC_BITWISE_AND, "&"), /* TOKEN_AMPAMP */ INFIX(PREC_LOGICAL_AND, and_), @@ -2869,6 +2964,7 @@ static int getByteCountForArguments(const uint8_t* bytecode, case CODE_FALSE: case CODE_TRUE: case CODE_POP: + case CODE_SWAP: case CODE_CLOSE_UPVALUE: case CODE_RETURN: case CODE_END: diff --git a/src/vm/wren_core.wren b/src/vm/wren_core.wren index f073062c2..dd108dfcf 100644 --- a/src/vm/wren_core.wren +++ b/src/vm/wren_core.wren @@ -4,6 +4,15 @@ class Fn {} class Null {} class Num {} +class ComposedFn { + construct new(lhs, rhs) { + _lhs = lhs + _rhs = rhs + } + + call(value) { _lhs.call(_rhs.call(value)) } +} + class Sequence { all(f) { var result = true diff --git a/src/vm/wren_core.wren.inc b/src/vm/wren_core.wren.inc index be296cdf7..85105bffb 100644 --- a/src/vm/wren_core.wren.inc +++ b/src/vm/wren_core.wren.inc @@ -6,6 +6,15 @@ static const char* coreModuleSource = "class Null {}\n" "class Num {}\n" "\n" +"class ComposedFn {\n" +" construct new(lhs, rhs) {\n" +" _lhs = lhs\n" +" _rhs = rhs\n" +" }\n" +"\n" +" call(value) { _lhs.call(_rhs.call(value)) }\n" +"}\n" +"\n" "class Sequence {\n" " all(f) {\n" " var result = true\n" diff --git a/src/vm/wren_debug.c b/src/vm/wren_debug.c index 2c4664867..300a34a89 100644 --- a/src/vm/wren_debug.c +++ b/src/vm/wren_debug.c @@ -177,6 +177,7 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine) case CODE_STORE_FIELD: BYTE_INSTRUCTION("STORE_FIELD"); case CODE_POP: printf("POP\n"); break; + case CODE_SWAP: printf("SWAP\n"); break; case CODE_CALL_0: case CODE_CALL_1: diff --git a/src/vm/wren_opcodes.h b/src/vm/wren_opcodes.h index 46ba8b47a..7807f3e52 100644 --- a/src/vm/wren_opcodes.h +++ b/src/vm/wren_opcodes.h @@ -77,6 +77,9 @@ OPCODE(STORE_FIELD, -1) // Pop and discard the top of stack. OPCODE(POP, -1) +// Swap the two top elements of the the stack. +OPCODE(SWAP, 0) + // Invoke the method with symbol [arg]. The number indicates the number of // arguments (not including the receiver). OPCODE(CALL_0, 0) diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 254d0b037..e36913556 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -1255,6 +1255,13 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) LOAD_FRAME(); DISPATCH(); } + CASE_CODE(SWAP): + { + Value value = PEEK(); + PEEK() = PEEK2(); + PEEK2() = value; + DISPATCH(); + } CASE_CODE(CONSTRUCT): ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class."); diff --git a/test/language/functional_operator/backward_call.wren b/test/language/functional_operator/backward_call.wren new file mode 100644 index 000000000..df8d0d1ef --- /dev/null +++ b/test/language/functional_operator/backward_call.wren @@ -0,0 +1,11 @@ +var add1 = Fn.new {|x| x + 1} +var double = Fn.new {|x| x * 2} + +System.print(double |< 1) // expect: 2 + +// Check operator priority +System.print(double |< add1 |< 1) // expect: 4 + +// Swallow a trailing newline. +System.print(double |< + 1) // expect: 2 diff --git a/test/language/functional_operator/backward_composition.wren b/test/language/functional_operator/backward_composition.wren new file mode 100644 index 000000000..3bafcfd46 --- /dev/null +++ b/test/language/functional_operator/backward_composition.wren @@ -0,0 +1,12 @@ +var add1 = Fn.new {|x| x + 1} +var double = Fn.new {|x| x * 2} + +System.print((double @< add1).call(1)) // expect: 4 + +// Check operator priority +System.print(1 |> double @< add1) // expect: 4 +System.print(double @< add1 |< 1) // expect: 4 + +// Swallow a trailing newline. +System.print(double @< + add1 |< 1) // expect: 4 diff --git a/test/language/functional_operator/forward_call.wren b/test/language/functional_operator/forward_call.wren new file mode 100644 index 000000000..0097d3c38 --- /dev/null +++ b/test/language/functional_operator/forward_call.wren @@ -0,0 +1,11 @@ +var add1 = Fn.new {|x| x + 1} +var double = Fn.new {|x| x * 2} + +System.print(1 |> double) // expect: 2 + +// Check operator priority +System.print(1 |> double |> add1) // expect: 3 + +// Swallow a trailing newline. +System.print(1 |> + double) // expect: 2 diff --git a/test/language/functional_operator/forward_composition.wren b/test/language/functional_operator/forward_composition.wren new file mode 100644 index 000000000..1ece2329b --- /dev/null +++ b/test/language/functional_operator/forward_composition.wren @@ -0,0 +1,12 @@ +var add1 = Fn.new {|x| x + 1} +var double = Fn.new {|x| x * 2} + +System.print((double @> add1).call(1)) // expect: 3 + +// Check operator priority +System.print(1 |> double @> add1) // expect: 3 +System.print(double @> add1 |< 1) // expect: 3 + +// Swallow a trailing newline. +System.print(double @> + add1 |< 1) // expect: 3