diff --git a/src/parser/ooga.js b/src/parser/ooga.js index f04b359..5d7d85f 100644 --- a/src/parser/ooga.js +++ b/src/parser/ooga.js @@ -6254,7 +6254,7 @@ function peg$parse(input, options) { } function peg$parseForWithInitTestUpdate() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16; + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12; s0 = peg$currPos; s1 = peg$parseForToken(); @@ -6285,35 +6285,11 @@ function peg$parse(input, options) { s9 = peg$parseExpression(); if (s9 !== peg$FAILED) { s10 = peg$parse__(); - if (input.charCodeAt(peg$currPos) === 123) { - s11 = peg$c75; - peg$currPos++; - } else { - s11 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e96); } - } + s11 = peg$parseBlockStatement(); if (s11 !== peg$FAILED) { s12 = peg$parse__(); - s13 = peg$parseSequenceStatement(); - if (s13 === peg$FAILED) { - s13 = null; - } - s14 = peg$parse__(); - if (input.charCodeAt(peg$currPos) === 125) { - s15 = peg$c43; - peg$currPos++; - } else { - s15 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e60); } - } - if (s15 !== peg$FAILED) { - s16 = peg$parse__(); - peg$savedPos = s0; - s0 = peg$f76(s3, s6, s9, s13); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$f76(s3, s6, s9, s11); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -6347,7 +6323,7 @@ function peg$parse(input, options) { } function peg$parseForWithTest() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10; + var s0, s1, s2, s3, s4, s5, s6; s0 = peg$currPos; s1 = peg$parseForToken(); @@ -6356,35 +6332,11 @@ function peg$parse(input, options) { s3 = peg$parseForTest(); if (s3 !== peg$FAILED) { s4 = peg$parse__(); - if (input.charCodeAt(peg$currPos) === 123) { - s5 = peg$c75; - peg$currPos++; - } else { - s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e96); } - } + s5 = peg$parseBlockStatement(); if (s5 !== peg$FAILED) { s6 = peg$parse__(); - s7 = peg$parseSequenceStatement(); - if (s7 === peg$FAILED) { - s7 = null; - } - s8 = peg$parse__(); - if (input.charCodeAt(peg$currPos) === 125) { - s9 = peg$c43; - peg$currPos++; - } else { - s9 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e60); } - } - if (s9 !== peg$FAILED) { - s10 = peg$parse__(); - peg$savedPos = s0; - s0 = peg$f77(s3, s7); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$f77(s3, s5); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -6402,41 +6354,17 @@ function peg$parse(input, options) { } function peg$parseForInfinite() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8; + var s0, s1, s2, s3, s4; s0 = peg$currPos; s1 = peg$parseForToken(); if (s1 !== peg$FAILED) { s2 = peg$parse__(); - if (input.charCodeAt(peg$currPos) === 123) { - s3 = peg$c75; - peg$currPos++; - } else { - s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e96); } - } + s3 = peg$parseBlockStatement(); if (s3 !== peg$FAILED) { s4 = peg$parse__(); - s5 = peg$parseSequenceStatement(); - if (s5 === peg$FAILED) { - s5 = null; - } - s6 = peg$parse__(); - if (input.charCodeAt(peg$currPos) === 125) { - s7 = peg$c43; - peg$currPos++; - } else { - s7 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$e60); } - } - if (s7 !== peg$FAILED) { - s8 = peg$parse__(); - peg$savedPos = s0; - s0 = peg$f78(s5); - } else { - peg$currPos = s0; - s0 = peg$FAILED; - } + peg$savedPos = s0; + s0 = peg$f78(s3); } else { peg$currPos = s0; s0 = peg$FAILED; diff --git a/src/parser/ooga.pegjs b/src/parser/ooga.pegjs index 5cab016..a13bc12 100644 --- a/src/parser/ooga.pegjs +++ b/src/parser/ooga.pegjs @@ -753,7 +753,7 @@ ForWithInitTestUpdate init:ForInitStatement ";" __ test:ForTest ";" __ update:Expression __ - "{" __ body: StatementList? __ "}" __ + body:BlockStatement __ { return { tag: "ForStatement", @@ -769,7 +769,7 @@ ForWithInitTestUpdate ForWithTest = ForToken __ test:ForTest __ - "{" __ body: StatementList? __ "}" __ + body:BlockStatement __ { return { tag: "ForStatement", @@ -784,7 +784,7 @@ ForWithTest ForInfinite = ForToken __ - "{" __ body: StatementList? __ "}" __ + body:BlockStatement __ { return { tag: "ForStatement", diff --git a/src/tests/test.ts b/src/tests/test.ts index b554fd5..e476083 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -1076,7 +1076,7 @@ func booga(x []int) int { var sum int = 0; for var i int = 0; i < n; i++ { sum = sum + x[i]; - } + } return sum; } @@ -1114,9 +1114,9 @@ func googaDaBooga() []int { func addOne(x []int) []int { for i := 0; i < len(x); i++ { x[i] = x[i] + 1; - } + } return x; -} +} var x = googaDaBooga(); @@ -1131,9 +1131,9 @@ x[0]; defaultNumWords ); - // Test unbuffered goroutines -testProgram(` +testProgram( + ` func fooga(x chan int) { // writes to x, should block print(0); @@ -1155,12 +1155,15 @@ for i := 0; i < 10; i++ { // do nothing to stall to see 'fooga' being printed } 10; -`, 10, +`, + 10, '0\n"booga"\n1\n"fooga"', - defaultNumWords); + defaultNumWords +); // Test unblocking buffered goroutine -testProgram(` +testProgram( + ` func fooga(x chan int) { // writes to x, should be unblocking print(0); @@ -1182,15 +1185,17 @@ for i := 0; i < 10; i++ { // do nothing to stall to see 'fooga' being printed } 10; -`, 10, +`, + 10, '0\n"fooga"\n"booga"\n1', - defaultNumWords); - + defaultNumWords +); // Test blocking buffered goroutine -testProgram(` +testProgram( + ` func foo(x chan int) { - print(0); + print(0); x <- 1; // unblocking write print("foo"); // should print immediately after 0 } @@ -1204,7 +1209,7 @@ func goo(x chan int) { func hoo(x chan int) { print(3); var y int = <-x; // shud be an unblocking read - print(y); // verify that y is equal to 1 + print(y); // verify that y is equal to 1 } var x chan int = make(chan int, 1); // buffered channel of size 1 @@ -1216,22 +1221,25 @@ for i := 0; i < 10; i++ { // do nothing to stall to see everything being printed } 10; -`, 10, +`, + 10, '0\n"foo"\n2\n3\n1\n"goo"', - defaultNumWords); + defaultNumWords +); // test pushing strings onto channels -testProgram(` +testProgram( + ` func foo(x chan string) { - print("before foo"); + print("before foo"); x<- "Jotham"; // non blocking write - print("after foo"); + print("after foo"); } func goo(x chan string) { - print("before goo"); + print("before goo"); x<- "Wong"; // non blocking write - print("after goo"); + print("after goo"); } func hoo(x chan string) { @@ -1254,13 +1262,15 @@ for i := 0; i < 100; i++ { // do nothing to stall to see everything being printed } 10; -`, 10, - '"before foo"\n"after foo"\n"before goo"\n"after goo"\n"before hoo"\n"Jotham Wong"\n"after hoo"' - , defaultNumWords); - +`, + 10, + '"before foo"\n"after foo"\n"before goo"\n"after goo"\n"before hoo"\n"Jotham Wong"\n"after hoo"', + defaultNumWords +); // test that main will expire before blocking progresses -testProgram(` +testProgram( + ` func foo(x chan int) { var y = <-x; // blocking since no actual value in x yet print("This will not show"); @@ -1276,11 +1286,15 @@ go foo(x); go goo(x); print("This is the end"); 10; // do not give time for foo to read -`, 10, - '"This will show"\n"This is the end"', defaultNumWords); +`, + 10, + '"This will show"\n"This is the end"', + defaultNumWords +); // Simple test for deadlock detection -testProgram(` +testProgram( + ` func foo(x chan int) { x <- 5; } @@ -1288,11 +1302,19 @@ func foo(x chan int) { var x chan int = make(chan int); // unbuffered channel go foo(x); x <- 6; // will deadlock here -`, 'Deadlock detected!', '', defaultNumWords); - +`, + 'Deadlock detected!', + '', + defaultNumWords +); // Simple test for doomed forever -testProgram(` +testProgram( + ` var x chan int = make(chan int); // unbuffered channel <-x; // will block forever -`, 'Stuck forever!', '', defaultNumWords); +`, + 'Stuck forever!', + '', + defaultNumWords +); diff --git a/src/vm/oogavm-compiler.ts b/src/vm/oogavm-compiler.ts index d9db53d..baaca4f 100644 --- a/src/vm/oogavm-compiler.ts +++ b/src/vm/oogavm-compiler.ts @@ -227,6 +227,9 @@ const compileComp = { goto_instr.addr = wc; }, BlockStatement: (comp, ce) => { + if (!comp.body) { + return; + } const declarations: CompileTimeVariable[] = scanForLocalsBlock(comp.body); // Only enter and exit scope if there are actually declarations. if (declarations.length == 0) { @@ -779,8 +782,8 @@ const compileComp = { instrs[wc++] = { tag: Opcodes.LDARRI }; }, MakeCallExpression: (comp, ce) => { - console.log('Compiling MakeCallExpression'); - console.log(comp); + log('Compiling MakeCallExpression'); + log(comp); if (is_type(comp.type, ChanType) && comp.args.length === 0) { // unbuffered channel instrs[wc++] = { tag: Opcodes.CREATE_UNBUFFERED }; @@ -793,7 +796,7 @@ const compileComp = { // Slice (currently not supporting dynamically resizable array) // so this is just a default initialized array at the moment, that means } else { - throw new OogaError( 'Unsupported make type at the moment!'); + throw new OogaError('Unsupported make type at the moment!'); } }, ChannelReadExpression: (comp, ce) => { diff --git a/src/vm/oogavm-heap.ts b/src/vm/oogavm-heap.ts index 17688bb..de86a72 100644 --- a/src/vm/oogavm-heap.ts +++ b/src/vm/oogavm-heap.ts @@ -716,7 +716,7 @@ export function pushUnbufferedChannel(address: number, value: number) { export function popUnbufferedChannel(address: number): number { // check that unbuffered channel is not empty! const size = getUnBufferChannelLength(address); - log("Size of unbuffered channel at addr " + address + " is " + size); + log('Size of unbuffered channel at addr ' + address + ' is ' + size); if (size !== 1) { throw new OogaError('Attempting to pop empty unbuffered channel in the heap. Bug!'); } @@ -806,7 +806,7 @@ export function debugHeap(): void { break; case Tag.UNBUFFERED: if (getUnBufferChannelLength(curr) === 0) { - log("Empty unbuffered channel"); + log('Empty unbuffered channel'); } else { log('Unbuffered value: ' + getWord(curr + headerSize + 1)); } diff --git a/src/vm/oogavm-machine.ts b/src/vm/oogavm-machine.ts index 4d84da4..ec652b2 100644 --- a/src/vm/oogavm-machine.ts +++ b/src/vm/oogavm-machine.ts @@ -22,7 +22,8 @@ import { getArrayLength, getArrayValue, getArrayValueAtIndex, - getBlockFrameEnvironment, getBufferChannelLength, + getBlockFrameEnvironment, + getBufferChannelLength, getBuiltinID, getCallFrameEnvironment, getCallFramePC, @@ -31,19 +32,27 @@ import { getEnvironmentValue, getField, getPrevStackAddress, - getTagStringFromAddress, getUnBufferChannelLength, + getTagStringFromAddress, + getUnBufferChannelLength, initializeStack, - isArray, isBufferChannelFull, isBufferedChannel, + isArray, + isBufferChannelFull, + isBufferedChannel, isBuiltin, - isCallFrame, isChannel, + isCallFrame, + isChannel, isClosure, isUnassigned, peekStack, - peekStackN, popBufferedChannel, - popStack, popUnbufferedChannel, + peekStackN, + popBufferedChannel, + popStack, + popUnbufferedChannel, printHeapUsage, printStringPoolMapping, - pushStack, pushToBufferedChannel, pushUnbufferedChannel, + pushStack, + pushToBufferedChannel, + pushUnbufferedChannel, setArrayValue, setEnvironmentValue, setField, @@ -90,8 +99,8 @@ let isAtomicSection: boolean = false; enum ThreadState { BLOCKING, // whether thread was blocked - RUNNING, // if thread is currently running - YIELDED, // if thread naturally gave up control + RUNNING, // if thread is currently running + YIELDED, // if thread naturally gave up control } enum ProgramState { @@ -133,7 +142,7 @@ function initScheduler() { scheduler = new RoundRobinScheduler(); threads.clear(); mainThreadId = scheduler.newThread(); // main thread - log("Main Thread ID is " + mainThreadId); + log('Main Thread ID is ' + mainThreadId); const [newMainThreadId, newTimeQuanta] = scheduler.runThread(); // main thread mainThreadId = newMainThreadId; TimeQuanta = newTimeQuanta; @@ -153,7 +162,7 @@ function pauseThread(state: ThreadState) { } function deleteThread() { - log("Deleting current thread id " + currentThreadId); + log('Deleting current thread id ' + currentThreadId); threads.delete(currentThreadId); scheduler.deleteCurrentThread(currentThreadId); currentThreadId = -1; @@ -161,15 +170,15 @@ function deleteThread() { function runThread() { [currentThreadId, TimeQuanta] = scheduler.runThread(); - log("Current Thread ID is " + currentThreadId); + log('Current Thread ID is ' + currentThreadId); // TODO: Load thread state - log("Enumerating threads"); + log('Enumerating threads'); // @ts-ignore for (let [key, value] of threads) { - log("ThreadID: " + key + ", value= " + value); + log('ThreadID: ' + key + ', value= ' + value); } let thread = threads.get(currentThreadId)!; - log("Thread is " + thread); + log('Thread is ' + thread); OS = thread._OS; PC = thread._PC; RTS = thread._RTS; @@ -184,14 +193,14 @@ function runThread() { } } if (deadlocked) { - throw new OogaError("Deadlock detected!"); + throw new OogaError('Deadlock detected!'); } } } function blockThread() { if (threads.size === 1) { - throw new OogaError("Stuck forever!"); + throw new OogaError('Stuck forever!'); } pauseThread(ThreadState.BLOCKING); runThread(); @@ -526,7 +535,7 @@ const microcode = { DONE: instr => { // Stop the program if the main thread reaches the DONE. Else terminate the thread // and switch over to the next available one. - log("Current Thread ID is " + currentThreadId); + log('Current Thread ID is ' + currentThreadId); if (currentThreadId === mainThreadId) { running = false; } else { @@ -761,7 +770,9 @@ const microcode = { // push onto temp root cos we are allocating stuff here possibly tempRoots.push(chan); if (!isChannel(chan[0])) { - throw new OogaError("Expected channel but got " + getTagStringFromAddress(chan[0]) + " instead."); + throw new OogaError( + 'Expected channel but got ' + getTagStringFromAddress(chan[0]) + ' instead.' + ); } // behavior now depends on whether buffered or unbuffered if (isBufferedChannel(chan[0])) { @@ -775,7 +786,8 @@ const microcode = { // now 'block' blockThread(); // realise that if nothing ever pushes onto the channel, this goes on forever - } else { // unblocking read from buffered channel is simply a pop + } else { + // unblocking read from buffered channel is simply a pop let value = []; value[0] = popBufferedChannel(chan[0]); tempRoots.push(value); @@ -783,9 +795,11 @@ const microcode = { pushAddressOS(value); tempRoots.pop(); } - } else { //unbuffered + } else { + //unbuffered // an unbuffered channel is functionally equivalent to a buffered channel - if (getUnBufferChannelLength(chan[0]) === 0) { // blocking read + if (getUnBufferChannelLength(chan[0]) === 0) { + // blocking read PC--; tempRoots.push(chan); pushAddressOS(chan); @@ -806,7 +820,9 @@ const microcode = { let chan = []; [OS[0], chan[0]] = popStack(OS[0]); if (!isChannel(chan[0])) { - throw new OogaError("Expected channel but got " + getTagStringFromAddress(chan[0]) + " instead."); + throw new OogaError( + 'Expected channel but got ' + getTagStringFromAddress(chan[0]) + ' instead.' + ); } // followed by value to write let value = []; @@ -842,14 +858,16 @@ const microcode = { tempRoots.pop(); tempRoots.pop(); } - } else { // unbuffered + } else { + // unbuffered // it is possible that 2 channels attempt to write onto an unbuffered channel, the unbuffered channel // may be non-empty so 2 cases here // case 1: write onto an empty unbuffered channel // In this case, we are done, we simply write and then move onto the CHECK_CHANNEL opcode // case 2: write onto a non-empty unbuffered channel // In this case, we have to block until we can write - if (getUnBufferChannelLength(chan[0]) === 0) { // case 1 + if (getUnBufferChannelLength(chan[0]) === 0) { + // case 1 tempRoots.push(chan); tempRoots.push(value); @@ -859,7 +877,8 @@ const microcode = { tempRoots.pop(); tempRoots.pop(); - } else { // case 2 + } else { + // case 2 tempRoots.push(chan); tempRoots.push(value); @@ -880,7 +899,9 @@ const microcode = { let chan = []; [OS[0], chan[0]] = popStack(OS[0]); if (!isChannel(chan[0])) { - throw new OogaError("Expected channel but got " + getTagStringFromAddress(chan[0]) + " instead."); + throw new OogaError( + 'Expected channel but got ' + getTagStringFromAddress(chan[0]) + ' instead.' + ); } // behavior differs based on type of channel // if buffered channel, simply move on @@ -888,9 +909,11 @@ const microcode = { if (isBufferedChannel(chan[0])) { // done return; - } else if (getUnBufferChannelLength(chan[0]) === 0) { // unbuffered channel is empty, we can move on + } else if (getUnBufferChannelLength(chan[0]) === 0) { + // unbuffered channel is empty, we can move on // done - } else { // unbuffered channel not empty, we block + } else { + // unbuffered channel not empty, we block // push chan onto goroutine OS then block PC--; tempRoots.push(chan); @@ -898,7 +921,7 @@ const microcode = { tempRoots.pop(); blockThread(); } - } + }, }; // ******************************** diff --git a/src/vm/oogavm-typechecker.ts b/src/vm/oogavm-typechecker.ts index c165989..86d1edd 100644 --- a/src/vm/oogavm-typechecker.ts +++ b/src/vm/oogavm-typechecker.ts @@ -20,7 +20,6 @@ import { } from './oogavm-types.js'; import assert from 'assert'; import { TypecheckError } from './oogavm-errors.js'; -import { Channel } from 'diagnostics_channel'; const log = debug('ooga:typechecker'); @@ -81,6 +80,8 @@ const binary_add_type = [ new FunctionType([new FloatType(), new FloatType()], new FloatType()), new FunctionType([new IntegerType(), new FloatType()], new FloatType()), new FunctionType([new FloatType(), new IntegerType()], new FloatType()), + new FunctionType([new AnyType(), new StringType()], new StringType()), + new FunctionType([new StringType(), new AnyType()], new StringType()), ]; const mutexType = new StructType( @@ -93,7 +94,7 @@ const mutexType = new StructType( ); const global_type_frame = { - '+': binary_add_type, + '+': binary_add_type, // This allows for other types to be added to strings '-': binary_arith_type, '*': binary_arith_type, '/': binary_arith_type, @@ -293,6 +294,20 @@ const type_comp = { ); } + // log('ArraySliceLiteral: ', comp.type.elem_type); + for (let i = 0; i < comp.elements.length; i++) { + const t = type(comp.elements[i], te, struct_te); + // log('ArraySliceLiteral: ', t); + if (!equal_type(t, comp.type.elem_type)) { + throw new TypecheckError( + 'Array expected element type ' + + unparse_types(comp.type.elem_type) + + ', got ' + + unparse_types(t) + ); + } + } + return comp.type; }, Name: (comp, te, struct_te) => { @@ -316,6 +331,9 @@ const type_comp = { // 5. Assign each of these and their constituent expressions to their respective types - these types should be set dynamically // NOTE: here we only set the "supposed" type of the declarations, as defined by the programmer + if (!comp.body) { + return new NullType(); + } const struct_decls = comp.body.body.filter(comp => comp.tag === 'StructDeclaration'); let extended_struct_te = extend_type_environment([], [], struct_te); if (struct_decls.length > 0) { @@ -366,7 +384,6 @@ const type_comp = { const comp = decls_known_type[i]; const name = comp.id.name; - log('Extended TE:', extended_te); if (lookup_type_current_frame(name, extended_te)) { throw new TypecheckError( 'Variable ' + name + ' declared more than once in the same block!' @@ -374,7 +391,7 @@ const type_comp = { } const type = getType(comp, extended_struct_te); - log('KNOWN VARIABLE', name); + log('Known type declaration:', name, type); log(unparse(type)); extended_te = extend_current_type_environment(name, type, extended_te); } @@ -396,7 +413,7 @@ const type_comp = { ); } const t = type(comp.expression, extended_te, extended_struct_te); - + log('Unknown type declaration:', name, t); comp.type = t; extended_te = extend_current_type_environment(name, t, extended_te); } @@ -543,12 +560,19 @@ const type_comp = { log(e); return type(e, te, struct_te); }); - let fun_type: Type | undefined; + let fun_type: Type | undefined; + log(comp.callee); + log('fun_types:', fun_types); if (Array.isArray(fun_types)) { // The function can take in/return multiple types, we need to check which one is the correct one and use that assert( - Array.isArray(fun_types) && fun_types.every(f => is_type(f, FunctionType)), + Array.isArray(fun_types) && + fun_types.every(f => { + log('------------------------'); + log(f, is_type(f, FunctionType)); + return is_type(f, FunctionType); + }), 'expected array of FunctionTypes' ); @@ -974,20 +998,7 @@ const type_comp = { const chanType = type(comp.channel, te, struct_te); if (!is_type(chanType, ChanType)) { - // We consider the case where it is a logical expression x < -y instead - comp.tag = 'LogicalExpression'; - comp.operator = '<'; - comp.left = comp.channel; - comp.right = { - tag: 'UnaryExpression', - operator: '-unary', - argument: comp.value, - prefix: true, - }; - - log(comp); - - return type(comp, te, struct_te); + throw new TypecheckError('Expected channel type, got ' + unparse_types(chanType)); } assert(chanType instanceof ChanType, 'expected channel type');