diff --git a/src/parser/ooga.pegjs b/src/parser/ooga.pegjs index ff9b983..429a53a 100644 --- a/src/parser/ooga.pegjs +++ b/src/parser/ooga.pegjs @@ -1217,7 +1217,7 @@ ChannelWriteExpression channel: channel, value: value }; - } + } // Select // These are select statements as per the Go spec @@ -1243,18 +1243,14 @@ ChannelOperation = ChannelReadExpression / ChannelWriteExpression - SelectCaseBlock - = "{" __ clauses:(SelectClause)* __ def:SelectDefaultClause? __ "}" { - // Add a default case if none is present - return optionalList(clauses) - .concat( - def || { - tag: "SelectDefaultCase", - body: {tag: "BlockStatement", body: []} - } - ); - } + = "{" __ clauses:(SelectClause)* __ def:SelectDefaultClause? __ "}" { + if (def) { + return optionalList(clauses).concat(def); + } else { + return optionalList(clauses); + } + } // This should be either // 1. var x = <-channel: diff --git a/src/tests/test.ts b/src/tests/test.ts index 9aba2b9..d42d321 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -1979,3 +1979,46 @@ func foo() int { 5 / foo(); `, 'Division by 0 error!', '', defaultNumWords); + +// Test that select blocks if no cases match +testProgram(` +x := make(chan int); // unbuffered + +select { +case <-x: + print("This won't happen"); +} +10; +`, 'Stuck forever!', '', defaultNumWords); + +testProgram(` +x := make(chan int); // unbuffered + +go func() { + select { + case <-x: + print("This won't happen"); + } +}(); + +for i := 0; i < 100; i++ { +} +10; +`, 10, '', defaultNumWords); + +testProgram(` +x := make(chan int); // unbuffered + +go func() { + select { + case <-x: + print("This won't happen"); + default: + print("Print once"); + } +}(); + +for i := 0; i < 100; i++ { +} +10; +`, 10, '"Print once"', defaultNumWords); diff --git a/src/vm/oogavm-compiler.ts b/src/vm/oogavm-compiler.ts index e4ee0b3..b917aa4 100644 --- a/src/vm/oogavm-compiler.ts +++ b/src/vm/oogavm-compiler.ts @@ -331,6 +331,8 @@ const compileComp = { // there is no real "randomness" // we complete the select case on a single successful case so we need to jump to the end let jumps: number[] = []; + let start: number = wc; + let hasDefault: boolean = false; for (let i = 0; i < comp.cases.length; i++) { const compCase = comp.cases[i]; log('Compiling: ' + unparse(compCase)); @@ -391,13 +393,21 @@ const compileComp = { // jump to the next select case if possible jof.addr = wc; } else if (compCase.tag === 'SelectDefaultCase') { - // default has no checks and is always the last case as per our parser rules + console.log("Default case"); + hasDefault = true; compile(compCase.body, ce); - // no need to throw GOTO cos it's the last anyways + jumps.push(wc); + instrs[wc++] = { tag: Opcodes.GOTO, addr: 0}; } else { throw new CompilerError('Unsupported select case in SelectStatement'); } } + // If none of the cases were handled and there is no default, this is when we reach this compile section + // we will push a BLOCK_THREAD instruction followed by a GOTO to the start of the case + if (!hasDefault) { + instrs[wc++] = { tag: Opcodes.BLOCK_THREAD }; + instrs[wc++] = { tag: Opcodes.GOTO, addr: start }; + } for (let jumpInstr of jumps) { instrs[jumpInstr].addr = wc; } diff --git a/src/vm/oogavm-machine.ts b/src/vm/oogavm-machine.ts index ebfa73d..f406f75 100644 --- a/src/vm/oogavm-machine.ts +++ b/src/vm/oogavm-machine.ts @@ -1064,6 +1064,9 @@ const microcode = { } tempRoots.pop(); }, + BLOCK_THREAD: instr => { + blockThread(); + }, BREAKPOINT: instr => { // Print heap visualization // log('Breakpoint'); diff --git a/src/vm/opcodes.ts b/src/vm/opcodes.ts index ae8ce83..8e31307 100644 --- a/src/vm/opcodes.ts +++ b/src/vm/opcodes.ts @@ -39,6 +39,7 @@ const enum OpCodes { CHECK_CHANNEL = 'CHECK_CHANNEL', CHECK_WRITE = 'CHECK_WRITE', CHECK_READ = 'CHECK_READ', + BLOCK_THREAD = 'BLOCK_THREAD', BREAKPOINT = 'BREAKPOINT', APPEND = 'APPEND', }