diff --git a/.gitignore b/.gitignore index 9324dd90..a2b84ba9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ build/ # XCode user-specific stuff. -xcuserdata/ \ No newline at end of file +xcuserdata/ +*.xccheckout \ No newline at end of file diff --git a/src/compiler.c b/src/compiler.c index 4ab8b8e3..e4e31963 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -32,6 +32,7 @@ typedef enum TOKEN_PLUS, TOKEN_MINUS, TOKEN_PIPE, + TOKEN_PIPEPIPE, TOKEN_AMP, TOKEN_AMPAMP, TOKEN_BANG, @@ -384,7 +385,18 @@ static void readRawToken(Parser* parser) } return; - case '|': makeToken(parser, TOKEN_PIPE); return; + case '|': + if (peekChar(parser) == '|') + { + nextChar(parser); + makeToken(parser, TOKEN_PIPEPIPE); + } + else + { + makeToken(parser, TOKEN_PIPE); + } + return; + case '&': if (peekChar(parser) == '&') { @@ -512,6 +524,7 @@ static void nextToken(Parser* parser) case TOKEN_PLUS: case TOKEN_MINUS: case TOKEN_PIPE: + case TOKEN_PIPEPIPE: case TOKEN_AMP: case TOKEN_AMPAMP: case TOKEN_BANG: @@ -1000,6 +1013,17 @@ void and(Compiler* compiler, int allowAssignment) patchJump(compiler, jump); } +void or(Compiler* compiler, int allowAssignment) +{ + // Skip the right argument if the left is true. + emit(compiler, CODE_OR); + int jump = emit(compiler, 255); + + parsePrecedence(compiler, 0, PREC_LOGIC); + + patchJump(compiler, jump); +} + void infixOp(Compiler* compiler, int allowAssignment) { GrammarRule* rule = &rules[compiler->parser->previous.type]; @@ -1073,6 +1097,7 @@ GrammarRule rules[] = /* TOKEN_PLUS */ INFIX_OPERATOR(PREC_TERM, "+ "), /* TOKEN_MINUS */ OPERATOR(PREC_TERM, "- "), /* TOKEN_PIPE */ UNUSED, + /* TOKEN_PIPEPIPE */ INFIX(PREC_LOGIC, or), /* TOKEN_AMP */ UNUSED, /* TOKEN_AMPAMP */ INFIX(PREC_LOGIC, and), /* TOKEN_BANG */ PREFIX_OPERATOR("!"), diff --git a/src/vm.c b/src/vm.c index 2041c52a..0297c2ca 100644 --- a/src/vm.c +++ b/src/vm.c @@ -577,6 +577,14 @@ int dumpInstruction(VM* vm, ObjFn* fn, int i) break; } + case CODE_OR: + { + int offset = bytecode[i++]; + printf("OR %d\n", offset); + printf("%04d | offset %d\n", i, offset); + break; + } + case CODE_IS: printf("CODE_IS\n"); break; @@ -908,6 +916,25 @@ Value interpret(VM* vm, ObjFn* fn) break; } + case CODE_OR: + { + int offset = READ_ARG(); + Value condition = PEEK(); + + // False is the only falsey value. + if (IS_FALSE(condition)) + { + // Discard the condition and evaluate the right hand side. + POP(); + } + else + { + // Short-circuit the right hand side. + ip += offset; + } + break; + } + case CODE_IS: { Value classObj = POP(); diff --git a/src/vm.h b/src/vm.h index 543b3f2d..21590c37 100644 --- a/src/vm.h +++ b/src/vm.h @@ -83,6 +83,10 @@ typedef enum // continue. CODE_AND, + // If the top of the stack is non-false, jump [arg] forward. Otherwise, pop + // and continue. + CODE_OR, + // Pop [a] then [b] and push true if [b] is an instance of [a]. CODE_IS, diff --git a/test/and.wren b/test/and.wren index 1aa01134..27c78c49 100644 --- a/test/and.wren +++ b/test/and.wren @@ -18,3 +18,9 @@ io.write(true) && // expect: true // Swallow a trailing newline. io.write(true && true) // expect: true + +// Only false is falsy. +io.write(0 && true) // expect: true +io.write(null && true) // expect: true +io.write("" && true) // expect: true +io.write(false && true) // expect: false diff --git a/test/if.wren b/test/if.wren index f45b5cd3..2a9a69a6 100644 --- a/test/if.wren +++ b/test/if.wren @@ -44,3 +44,8 @@ if (true) class Foo {} // no error // Definition in else arm. if (false) null else var a = io.write("ok") // expect: ok if (true) null else class Foo {} // no error + +// Only false is falsy. +if (0) io.write(0) // expect: 0 +if (null) io.write(null) // expect: null +if ("") io.write("empty") // expect: empty diff --git a/test/or.wren b/test/or.wren new file mode 100644 index 00000000..9e404b18 --- /dev/null +++ b/test/or.wren @@ -0,0 +1,26 @@ +// Note: These tests implicitly depend on ints being truthy. +// Also rely on io.write() returning its argument. + +// Return the first true argument. +io.write(1 || true) // expect: 1 +io.write(false || 1) // expect: 1 +io.write(false || false || true) // expect: true + +// Return the last argument if all are false. +io.write(false || false) // expect: false +io.write(false || false || false) // expect: false + +// Short-circuit at the first true argument. +io.write(false) || // expect: false + io.write(true) || // expect: true + io.write(true) // should not print + +// Swallow a trailing newline. +io.write(true || + true) // expect: true + +// Only false is falsy. +io.write(0 || true) // expect: 0 +io.write(null || true) // expect: null +io.write(("" || true) == "") // expect: true +io.write(false || true) // expect: true