diff --git a/doc/site/branching.markdown b/doc/site/branching.markdown index e295be27..0561d1f8 100644 --- a/doc/site/branching.markdown +++ b/doc/site/branching.markdown @@ -57,3 +57,5 @@ An `||` ("logical or") expression is reversed. If the left-hand argument is trut :::wren io.write(false || 1) // 1 io.write(1 || 2) // 1 + +**TODO: Conditional operator.** diff --git a/src/wren_compiler.c b/src/wren_compiler.c index e4131460..2256644e 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -58,6 +58,7 @@ typedef enum TOKEN_AMPAMP, TOKEN_BANG, TOKEN_TILDE, + TOKEN_QUESTION, TOKEN_EQ, TOKEN_LT, TOKEN_GT, @@ -431,6 +432,9 @@ static void makeToken(Parser* parser, TokenType type) parser->current.start = parser->tokenStart; parser->current.length = (int)(parser->currentChar - parser->tokenStart); parser->current.line = parser->currentLine; + + // Make line tokens appear on the line containing the "\n". + if (type == TOKEN_LINE) parser->current.line--; } // If the current character is [c], then consumes it and makes a token of type @@ -722,6 +726,7 @@ static void readRawToken(Parser* parser) case '%': makeToken(parser, TOKEN_PERCENT); return; case '+': makeToken(parser, TOKEN_PLUS); return; case '~': makeToken(parser, TOKEN_TILDE); return; + case '?': makeToken(parser, TOKEN_QUESTION); return; case '/': if (peekChar(parser) == '/') { @@ -857,6 +862,7 @@ static void nextToken(Parser* parser) case TOKEN_AMPAMP: case TOKEN_BANG: case TOKEN_TILDE: + case TOKEN_QUESTION: case TOKEN_EQ: case TOKEN_LT: case TOKEN_GT: @@ -1893,6 +1899,29 @@ void or(Compiler* compiler, bool allowAssignment) patchJump(compiler, jump); } +void conditional(Compiler* compiler, bool allowAssignment) +{ + // Jump to the else branch if the condition is false. + int ifJump = emitJump(compiler, CODE_JUMP_IF); + + // Compile the then branch. + parsePrecedence(compiler, allowAssignment, PREC_LOGIC); + + consume(compiler, TOKEN_COLON, + "Expect ':' after then branch of conditional operator."); + + // Jump over the else branch when the if branch is taken. + int elseJump = emitJump(compiler, CODE_JUMP); + + // Compile the else branch. + patchJump(compiler, ifJump); + + parsePrecedence(compiler, allowAssignment, PREC_ASSIGNMENT); + + // Patch the jump over the else. + patchJump(compiler, elseJump); +} + void infixOp(Compiler* compiler, bool allowAssignment) { GrammarRule* rule = &rules[compiler->parser->previous.type]; @@ -1996,6 +2025,7 @@ GrammarRule rules[] = /* TOKEN_AMPAMP */ INFIX(PREC_LOGIC, and), /* TOKEN_BANG */ PREFIX_OPERATOR("!"), /* TOKEN_TILDE */ PREFIX_OPERATOR("~"), + /* TOKEN_QUESTION */ INFIX(PREC_ASSIGNMENT, conditional), /* TOKEN_EQ */ UNUSED, /* TOKEN_LT */ INFIX_OPERATOR(PREC_COMPARISON, "< "), /* TOKEN_GT */ INFIX_OPERATOR(PREC_COMPARISON, "> "), diff --git a/src/wren_core.c b/src/wren_core.c index c4fcd5be..7a20200a 100644 --- a/src/wren_core.c +++ b/src/wren_core.c @@ -307,6 +307,10 @@ DEF_NATIVE(fn_call14) { return callFunction(vm, args, 14); } DEF_NATIVE(fn_call15) { return callFunction(vm, args, 15); } DEF_NATIVE(fn_call16) { return callFunction(vm, args, 16); } +DEF_NATIVE(fn_toString) +{ + RETURN_VAL(wrenNewString(vm, "", 4)); +} DEF_NATIVE(list_add) { @@ -921,6 +925,7 @@ void wrenInitializeCore(WrenVM* vm) NATIVE(vm->fnClass, "call ", fn_call14); NATIVE(vm->fnClass, "call ", fn_call15); NATIVE(vm->fnClass, "call ", fn_call16); + NATIVE(vm->fnClass, "toString", fn_toString); vm->nullClass = defineClass(vm, "Null"); NATIVE(vm->nullClass, "toString", null_toString); diff --git a/test/conditional/conditional_in_then.wren b/test/conditional/conditional_in_then.wren new file mode 100644 index 00000000..c98ab769 --- /dev/null +++ b/test/conditional/conditional_in_then.wren @@ -0,0 +1 @@ +1 ? 2 ? 3 : 4 : 5 // expect error diff --git a/test/conditional/missing_colon.wren b/test/conditional/missing_colon.wren new file mode 100644 index 00000000..2b69346a --- /dev/null +++ b/test/conditional/missing_colon.wren @@ -0,0 +1,2 @@ +true ? 1 // expect error +"next expression" \ No newline at end of file diff --git a/test/conditional/missing_condition.wren b/test/conditional/missing_condition.wren new file mode 100644 index 00000000..7aee0864 --- /dev/null +++ b/test/conditional/missing_condition.wren @@ -0,0 +1 @@ +? 1 : 2 // expect error diff --git a/test/conditional/missing_else.wren b/test/conditional/missing_else.wren new file mode 100644 index 00000000..c5ce2ed2 --- /dev/null +++ b/test/conditional/missing_else.wren @@ -0,0 +1 @@ +true ? 1 : // expect error diff --git a/test/conditional/missing_question.wren b/test/conditional/missing_question.wren new file mode 100644 index 00000000..4804ecbd --- /dev/null +++ b/test/conditional/missing_question.wren @@ -0,0 +1 @@ +true 1 : 2 // expect error diff --git a/test/conditional/missing_then.wren b/test/conditional/missing_then.wren new file mode 100644 index 00000000..9b1720ba --- /dev/null +++ b/test/conditional/missing_then.wren @@ -0,0 +1 @@ +true ? : 2 // expect error diff --git a/test/conditional/precedence.wren b/test/conditional/precedence.wren new file mode 100644 index 00000000..a167a160 --- /dev/null +++ b/test/conditional/precedence.wren @@ -0,0 +1,59 @@ +class Foo { + static bar { return true } + static baz { return 1 } +} + +// Condition precedence. +IO.print(true ? 1 : 2) // expect: 1 +IO.print((true) ? 1 : 2) // expect: 1 +IO.print([true][0] ? 1 : 2) // expect: 1 +IO.print(Foo.bar ? 1 : 2) // expect: 1 +IO.print(3..4 ? 1 : 2) // expect: 1 +IO.print(3 * 4 ? 1 : 2) // expect: 1 +IO.print(3 + 4 ? 1 : 2) // expect: 1 +IO.print(true || false ? 1 : 2) // expect: 1 +IO.print(!false ? 1 : 2) // expect: 1 +IO.print(~0 ? 1 : 2) // expect: 1 +IO.print(3 is Num ? 1 : 2) // expect: 1 +IO.print(new Foo ? 1 : 2) // expect: 1 +IO.print(fn true ? 1 : 2) // expect: + +var a = 0 +IO.print(a = 3 ? 1 : 2) // expect: 1 +IO.print(a) // expect: 1 + +// Then branch precedence. +IO.print(true ? (1) : 2) // expect: 1 +IO.print(true ? [1][0] : 2) // expect: 1 +IO.print(true ? Foo.baz : 2) // expect: 1 +IO.print(true ? 3..4 : 2) // expect: 3..4 +IO.print(true ? 3 * 4 : 2) // expect: 12 +IO.print(true ? 3 + 4 : 2) // expect: 7 +IO.print(true ? 1 || false : 2) // expect: 1 +IO.print(true ? !true : 2) // expect: false +IO.print(true ? ~0 : 2) // expect: 4294967295 +IO.print(true ? 3 is Bool : 2) // expect: false +IO.print(true ? new Foo : 2) // expect: instance of Foo +// TODO: Is this what we want? +IO.print(true ? fn 1 : 2) // expect: + +IO.print(true ? a = 5 : 2) // expect: 5 +IO.print(a) // expect: 5 + +// Else branch precedence. +IO.print(false ? 1 : (2)) // expect: 2 +IO.print(false ? 1 : [2][0]) // expect: 2 +IO.print(false ? 2 : Foo.baz) // expect: 1 +IO.print(false ? 1 : 3..4) // expect: 3..4 +IO.print(false ? 1 : 3 * 4) // expect: 12 +IO.print(false ? 1 : 3 + 4) // expect: 7 +IO.print(false ? 1 : 2 || false) // expect: 2 +IO.print(false ? 1 : !false) // expect: true +IO.print(false ? 1 : ~0) // expect: 4294967295 +IO.print(false ? 1 : 3 is Num) // expect: true +IO.print(false ? 1 : new Foo) // expect: instance of Foo +IO.print(false ? 1 : fn 2) // expect: + +// Associativity. +IO.print(true ? 2 : true ? 4 : 5) // expect: 2 +IO.print(false ? 2 : true ? 4 : 5) // expect: 4 diff --git a/test/conditional/short_circuit.wren b/test/conditional/short_circuit.wren new file mode 100644 index 00000000..21f37521 --- /dev/null +++ b/test/conditional/short_circuit.wren @@ -0,0 +1,2 @@ +true ? IO.print("ok") : IO.print("no") // expect: ok +false ? IO.print("no") : IO.print("ok") // expect: ok diff --git a/test/function/to_string.wren b/test/function/to_string.wren new file mode 100644 index 00000000..c2c5821e --- /dev/null +++ b/test/function/to_string.wren @@ -0,0 +1 @@ +IO.print(fn {}) // expect: