From 3c86d5c4755090ec758240e92fcefc5b57f0a5b9 Mon Sep 17 00:00:00 2001 From: JothamWong <45916998+JothamWong@users.noreply.github.com> Date: Sun, 14 Apr 2024 03:15:43 +0800 Subject: [PATCH 1/6] Add slices Slices follow golang convention where appending to a full one will reallocate a new slice with double the capacity --- src/tests/test.ts | 137 ++++++++++++++++++++--------------- src/vm/oogavm-compiler.ts | 19 ++++- src/vm/oogavm-heap.ts | 119 +++++++++++++++++++++++++++++- src/vm/oogavm-machine.ts | 82 ++++++++++++++++++--- src/vm/oogavm-typechecker.ts | 24 +++++- src/vm/opcodes.ts | 2 + 6 files changed, 311 insertions(+), 72 deletions(-) diff --git a/src/tests/test.ts b/src/tests/test.ts index eb1b14a..035b8bc 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -839,64 +839,64 @@ v.X // 3 // Test WaitGroups -testProgram( - ` - -type Vertex struct { - X int -} - -func (v *Vertex) Add(w Vertex) { - v.X = v.X + w.X; -} - -v := Vertex{1}; -w := Vertex{2}; - -go func() { - for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended - } - v.Add(w); -}() - -v.X; //1 -`, - 1, - '', - defaultNumWords -); - -testProgram( - ` - -type Vertex struct { - X int -} - -func (v *Vertex) Add(w Vertex) { - v.X = v.X + w.X; -} - -v := Vertex{1}; -w := Vertex{2}; - -wg := WaitGroup{1}; - -go func() { - for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended - } - v.Add(w); - wg.Done(); -}() - -wg.Wait(); - -v.X; //3 -`, - 3, - '', - defaultNumWords -); +// testProgram( +// ` +// +// type Vertex struct { +// X int +// } +// +// func (v *Vertex) Add(w Vertex) { +// v.X = v.X + w.X; +// } +// +// v := Vertex{1}; +// w := Vertex{2}; +// +// go func() { +// for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended +// } +// v.Add(w); +// }() +// +// v.X; //1 +// `, +// 1, +// '', +// defaultNumWords +// ); + +// testProgram( +// ` +// +// type Vertex struct { +// X int +// } +// +// func (v *Vertex) Add(w Vertex) { +// v.X = v.X + w.X; +// } +// +// v := Vertex{1}; +// w := Vertex{2}; +// +// wg := WaitGroup{1}; +// +// go func() { +// for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended +// } +// v.Add(w); +// wg.Done(); +// }() +// +// wg.Wait(); +// +// v.X; //3 +// `, +// 3, +// '', +// defaultNumWords +// ); // Testing struct methods with goroutines (goroutines cannot return anything) testProgram( @@ -1667,3 +1667,24 @@ for { '', defaultNumWords ); + + +// Testing slices +testProgram(` +var x []int = make([]int, 5, 10); // create a slice of len 5 and capacity 10 +x = append(x, 5); +for i := 0; i < len(x); i++ { + print(x[i]); +} +x[5]; +`, 5, '0\n0\n0\n0\n0\n5\n', defaultNumWords); + +// Testing re-allocation +testProgram(` +var x []int = make([]int, 5, 5); // create a slice of len 5 and capacity 5 +var y []int = append(x, 10); // this should point to a new y + +print(y[5]); // shud be 10 +print(len(x)); // should be 5 +y[0]; // should be 0 +`, 0, '10\n5\n', defaultNumWords); diff --git a/src/vm/oogavm-compiler.ts b/src/vm/oogavm-compiler.ts index 5daf791..1ec9e77 100644 --- a/src/vm/oogavm-compiler.ts +++ b/src/vm/oogavm-compiler.ts @@ -895,8 +895,18 @@ const compileComp = { compile(comp.args[0], ce); instrs[wc++] = { tag: Opcodes.CREATE_BUFFERED }; } else if (is_type(comp.type, ArrayType)) { - // Slice (currently not supporting dynamically resizable array) - // so this is just a default initialized array at the moment, that means + // Slice which is of initial len and capacity and can grow by appending + // The format is len, capacity + if (comp.args.length === 1) { + // If the user only provides a single value, capacity == len + // this means that len == capacity + compile(comp.args[0], ce); // len + compile(comp.args[0], ce); // capacity + } else { + compile(comp.args[0], ce); // len + compile(comp.args[1], ce); // capacity + } + instrs[wc++] = { tag: Opcodes.CREATE_SLICE, elementType: comp.type.elem_type }; } else { throw new OogaError('Unsupported make type at the moment!'); } @@ -913,6 +923,11 @@ const compileComp = { instrs[wc++] = { tag: Opcodes.WRITE_CHANNEL }; instrs[wc++] = { tag: Opcodes.CHECK_CHANNEL }; }, + AppendExpression: (comp, ce) => { + compile(comp.args[0], ce); + compile(comp.name, ce); + instrs[wc++] = { tag: Opcodes.APPEND }; + } }; // NOTE: We are a left precedence diff --git a/src/vm/oogavm-heap.ts b/src/vm/oogavm-heap.ts index f999e76..a6e0d4b 100644 --- a/src/vm/oogavm-heap.ts +++ b/src/vm/oogavm-heap.ts @@ -1,5 +1,5 @@ import debug from 'debug'; -import { HeapError, HeapOutOfMemoryError, OogaError } from './oogavm-errors.js'; +import { HeapError, HeapOutOfMemoryError, OogaError, RuntimeError } from './oogavm-errors.js'; import { getRoots, E, OS, RTS } from './oogavm-machine.js'; import { head } from '../utils/utils'; @@ -28,6 +28,7 @@ export enum Tag { STRING, MUTEX, ARRAY, + SLICE, BUFFERED, UNBUFFERED, } @@ -75,6 +76,8 @@ function getTagString(tag: Tag): string { return 'MUTEX'; case Tag.ARRAY: return 'ARRAY'; + case Tag.SLICE: + return 'SLICE'; case Tag.BUFFERED: return 'BUFFERED'; case Tag.UNBUFFERED: @@ -461,6 +464,7 @@ function isStruct(address: number) { // ********************* function allocateNumber(n: number): number { const numAddress = allocate(Tag.NUMBER, 3); + log("Allocating number " + n + " to address " + numAddress); setWord(numAddress + headerSize, n); return numAddress; } @@ -588,7 +592,8 @@ export function getArrayValue(address: number): any[] { let result: any[] = []; for (let i = 0; i < arrayLength; i++) { const arrayElementAddress = getWordOffset(address, i + headerSize); - result.push(arrayElementAddress); + const arrayElementValue = addressToTSValue(arrayElementAddress); + result.push(arrayElementValue); } return result; } @@ -605,6 +610,104 @@ export function getArrayValueAtIndex(arrayAddress: number, idx: number): any { return getWordOffset(arrayAddress, idx + headerSize); } +// ******************************** +// Slice +// ******************************** +// A slice is a resizable array. +// 3rd word is for current number of elements inside. +export function allocateSlice(len: number, initialCapacity: number): number { + if (len > initialCapacity) { + throw new RuntimeError('Cannot allocate slice with len more than capacity'); + } + const address = allocate(Tag.SLICE, initialCapacity + headerSize + 1); + // starts with len + heap.setUint32((address + headerSize) * wordSize, len); + return address; +} + +export function setSliceValue(sliceAddress: number, idx: number, value: number) { + // Check does not exceed capacity + const capacity = getSliceCapacity(sliceAddress); + log("Slice capacity is " + capacity); + if (idx >= capacity) { + throw new OogaError("Indexing out of bounds. Indexed " + idx + " on a slice of capacity " + capacity + "."); + } + setWord(sliceAddress + idx + headerSize + 1, value); + log("Set word at index " + idx + " of slice at " + sliceAddress + " to " + value); +} + +// Attempt to append to a slice at latest value. +// If hit capacity, will reallocate a new slice of double the capacity +// and return the address of the reallocated slice. +// This aligns with how golang actually does slices. +// Cos this can trigger GC, params are lists +export function appendToSlice(sliceAddress: number[], value: number[]): number { + const sliceLength = getSliceLength(sliceAddress[0]); + const sliceCapacity = getSliceCapacity(sliceAddress[0]); + log("Slice length is " + sliceLength); + log("Slice cap is " + sliceCapacity); + if (sliceLength === sliceCapacity) { + log("Copying over slice"); + let returnAddress = allocateSlice(sliceLength, sliceLength * 2); + // Copy over all elements + for (let i = 0; i < sliceLength; i++) { + const sliceElement = getSliceValueAtIndex(sliceAddress[0], i); + setSliceValue(returnAddress, i, sliceElement); + } + // finally append + setSliceValue(returnAddress, sliceLength, value[0]); + heap.setUint32((returnAddress + headerSize) * wordSize, sliceLength + 1); + return returnAddress; + } + log("Just allocating directly."); + // no allocation, just set latest value and update length + setSliceValue(sliceAddress[0], sliceLength, value[0]); + heap.setUint32((sliceAddress[0] + headerSize) * wordSize, sliceLength + 1); + return sliceAddress[0]; +} + +// Returns the addresses of the values in the array +export function getSliceValue(address: number): any[] { + const sliceLength = getSliceLength(address); + let result: any[] = []; + for (let i = 0; i < sliceLength; i++) { + const arrayElementAddress = getWordOffset(address, i + headerSize + 1); + const arrayElementValue = addressToTSValue(arrayElementAddress); + result.push(arrayElementValue); + } + return result; +} + +// returns the address of the element at said index +export function getSliceValueAtIndex(sliceAddress: number, idx: number): any { + const sliceLength: number = getSliceLength(sliceAddress); + if (idx < 0) { + throw new OogaError('Negative indexing not allowed!'); + } + if (idx >= sliceLength) { + throw new OogaError('Array out of bounds error!'); + } + return getWordOffset(sliceAddress, idx + headerSize + 1); +} + +export function isSlice(address: number): boolean { + return getTag(address) === Tag.SLICE; +} + +export function getSliceCapacity(address: number): number { + if (getTag(address) !== Tag.SLICE) { + throw new OogaError('Called getArrayLength on non array type'); + } + return getSize(address) - headerSize - 1; +} + +export function getSliceLength(address: number): number { + if (getTag(address) !== Tag.SLICE) { + throw new OogaError('Called getArrayLength on non array type'); + } + return heap.getUint32((address + headerSize) * wordSize); +} + // ******************************** // Channels // ******************************** @@ -828,6 +931,16 @@ export function debugHeap(): void { log('Child ' + i + ': ' + getWord(curr + headerSize + 1 + i)); } break; + case Tag.ARRAY: + for (let i = 0; i < getArrayLength(curr); i++) { + log('Child ' + i + ": " + getArrayValueAtIndex(curr, i)); + } + break; + case Tag.SLICE: + for (let i = 0; i < getSliceLength(curr); i++) { + log('Child ' + i + ": " + getSliceValueAtIndex(curr, i)); + } + break; default: break; } @@ -868,6 +981,8 @@ export function addressToTSValue(address: number) { return getStringValue(address); } else if (isArray(address)) { return getArrayValue(address); + } else if (isSlice(address)) { + return getSliceValue(address); } else if (isBufferedChannel(address)) { return ''; } else if (isUnbufferedChannel(address)) { diff --git a/src/vm/oogavm-machine.ts b/src/vm/oogavm-machine.ts index fa385c9..17f9e1e 100644 --- a/src/vm/oogavm-machine.ts +++ b/src/vm/oogavm-machine.ts @@ -13,9 +13,9 @@ import { allocateClosure, allocateEnvironment, allocateFrame, - allocateMutex, + allocateMutex, allocateSlice, allocateStruct, - allocateUnbufferedChannel, + allocateUnbufferedChannel, appendToSlice, constructHeap, debugHeap, extendEnvironment, @@ -31,7 +31,7 @@ import { getClosurePC, getEnvironmentValue, getField, - getPrevStackAddress, + getPrevStackAddress, getSliceLength, getSliceValueAtIndex, getTagStringFromAddress, getUnBufferChannelLength, initializeStack, @@ -42,7 +42,7 @@ import { isBuiltin, isCallFrame, isChannel, - isClosure, + isClosure, isSlice, isUnassigned, isUnbufferedChannel, isUnbufferedChannelEmpty, @@ -60,7 +60,7 @@ import { setArrayValue, setEnvironmentValue, setField, - setFrameValue, + setFrameValue, setSliceValue, True, TSValueToAddress, Unassigned, @@ -248,11 +248,14 @@ export const builtinMappings = { log('Len sys call'); [OS[0], value] = popStack(OS[0]); // TODO: Expect an array only at the moment - if (!isArray(value)) { + if (isArray(value)) { + return TSValueToAddress(getArrayLength(value)); + } else if (isSlice(value)) { + return TSValueToAddress(getSliceLength(value)); + } else { const tag = getTagStringFromAddress(value); throw new OogaError('Expected value to be of type Array but got ' + tag); } - return TSValueToAddress(getArrayLength(value)); }, createMutex: () => { let mutex = [allocateMutex()]; @@ -452,7 +455,16 @@ const microcode = { [OS[0], arrayAddress[0]] = popStack(OS[0]); log('ArrayAddr: ' + arrayAddress); tempRoots.push(arrayAddress); - let arrayValue = getArrayValueAtIndex(arrayAddress[0], arrayIndex); // the address + + // Can either be slice or array, cannot just use same method cos their byte arrangement is different + let arrayValue; + if (isArray(arrayAddress[0])) { + log("IS array"); + arrayValue = getArrayValueAtIndex(arrayAddress[0], arrayIndex); // the address + } else if (isSlice(arrayAddress[0])) { + log("IS slice"); + arrayValue = getSliceValueAtIndex(arrayAddress[0], arrayIndex); + } pushAddressOS([arrayValue]); tempRoots.pop(); tempRoots.pop(); @@ -753,6 +765,42 @@ const microcode = { END_ATOMIC: instr => { isAtomicSection = false; }, + CREATE_SLICE: instr => { + let capacity; + [OS[0], capacity] = popStack(OS[0]); + capacity = addressToTSValue(capacity); + let len; + [OS[0], len] = popStack(OS[0]); + len = addressToTSValue(len); + + let sliceAddr = []; + tempRoots.push(sliceAddr); + sliceAddr[0] = allocateSlice(len, capacity); + + // Allocate a default value depending on the elem_type + const elem_type = instr.elementType.name; + let defaultValue = []; + tempRoots.push(defaultValue); + log("Elem type is " + elem_type); + if (elem_type === 'Integer') { + defaultValue[0] = TSValueToAddress(0); + } else if (elem_type === 'Boolean') { + defaultValue[0] = TSValueToAddress(false); + } else if (elem_type === 'String') { + defaultValue[0] = TSValueToAddress(''); + } else { // all structs and stuff will be default initialized to null + defaultValue[0] = TSValueToAddress(null); + } + // Allocate each element to default value + for (let i = 0; i < len; i++) { + setSliceValue(sliceAddr[0], i, defaultValue[0]); + } + + pushAddressOS(sliceAddr); + + tempRoots.pop(); + tempRoots.pop(); + }, CREATE_UNBUFFERED: instr => { let unbufferedChannel = [allocateUnbufferedChannel()]; tempRoots.push(unbufferedChannel); @@ -976,6 +1024,22 @@ const microcode = { } tempRoots.pop(); }, + APPEND: instr => { + // expect slice on top + let sliceAddress = []; + tempRoots.push(sliceAddress); + [OS[0], sliceAddress[0]] = popStack(OS[0]); + // expect value on top + let value = []; + tempRoots.push(value); + [OS[0], value[0]] = popStack(OS[0]); + log("Trying to appending value " + addressToTSValue(value[0]) + " to slice at addr " + sliceAddress[0]); + sliceAddress[0] = appendToSlice(sliceAddress, value); + log("Appending value " + value[0] + " to slice at addr " + sliceAddress[0]); + pushAddressOS(sliceAddress); + tempRoots.pop(); + tempRoots.pop(); + } }; // ******************************** @@ -1060,7 +1124,7 @@ function runInstruction() { log('OS: ' + OS[0]); log('E: ' + E[0]); log('PC: ' + PC); - // debugHeap(); + debugHeap(); // printOSStack(); // printHeapUsage(); // printStringPoolMapping(); diff --git a/src/vm/oogavm-typechecker.ts b/src/vm/oogavm-typechecker.ts index a7998aa..2ff6830 100644 --- a/src/vm/oogavm-typechecker.ts +++ b/src/vm/oogavm-typechecker.ts @@ -19,7 +19,7 @@ import { ChanType, } from './oogavm-types.js'; import assert from 'assert'; -import { TypecheckError } from './oogavm-errors.js'; +import { CompilerError, TypecheckError } from './oogavm-errors.js'; const log = debug('ooga:typechecker'); @@ -722,6 +722,8 @@ const type_comp = { throw new TypecheckError('Expected integer argument m to make([]T, n, m)'); } } + + } else { throw new TypecheckError('Make call expects channel or slice type'); } @@ -729,6 +731,26 @@ const type_comp = { log(unparse(comp)); return t; }, + AppendExpression: (comp, te, struct_te) => { + log('Append Expression'); + log(unparse(comp)); + let ts = type(comp.name, te, struct_te); + log("ts is " + ts); + if (is_type(ts, ArrayType) && !(ts as ArrayType).is_array) { + if (comp.args.length !== 1) { + throw new CompilerError('Expected 1 argument to append but got ' + comp.args.length + ' args.'); + } + const elem_type = (ts as ArrayType).elem_type; + const arg_type = type(comp.args[0], te, struct_te); + if (!equal_type(elem_type, arg_type)) { + throw new TypecheckError('Expected to append ' + elem_type + ' but got ' + arg_type + ' instead.'); + } + + } else { + throw new TypecheckError('Expected slice type but got ' + ts); + } + return ts; + }, ConstantDeclaration: (comp, te, struct_te) => { log('ConstantDeclaration'); log(unparse(comp)); diff --git a/src/vm/opcodes.ts b/src/vm/opcodes.ts index cb9e8dc..c909390 100644 --- a/src/vm/opcodes.ts +++ b/src/vm/opcodes.ts @@ -33,11 +33,13 @@ const enum OpCodes { END_ATOMIC = 'END_ATOMIC', CREATE_UNBUFFERED = 'CREATE_UNBUFFERED', CREATE_BUFFERED = 'CREATE_BUFFERED', + CREATE_SLICE = 'CREATE_SLICE', READ_CHANNEL = 'READ_CHANNEL', WRITE_CHANNEL = 'WRITE_CHANNEL', CHECK_CHANNEL = 'CHECK_CHANNEL', CHECK_WRITE = 'CHECK_WRITE', CHECK_READ = 'CHECK_READ', + APPEND = 'APPEND', } export default OpCodes; From d31d41f5f42851f9885e712a8960a4cfcba1bbb7 Mon Sep 17 00:00:00 2001 From: JothamWong <45916998+JothamWong@users.noreply.github.com> Date: Sun, 14 Apr 2024 03:19:11 +0800 Subject: [PATCH 2/6] Update WaitGroup test to use 100 iterations No idea why its suddenly super slow Also add the grammar file cos I forgot to --- src/parser/ooga.pegjs | 9 ++++ src/tests/test.ts | 118 +++++++++++++++++++++--------------------- 2 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/parser/ooga.pegjs b/src/parser/ooga.pegjs index 81d877d..742cbbe 100644 --- a/src/parser/ooga.pegjs +++ b/src/parser/ooga.pegjs @@ -152,6 +152,7 @@ Keyword / CaseToken / DefaultToken / SelectToken + / AppendToken Type @@ -275,6 +276,7 @@ SwitchToken = "switch" !IdentifierPart CaseToken = "case" !IdentifierPart DefaultToken = "default" !IdentifierPart SelectToken = "select" !IdentifierPart +AppendToken = "append" !IdentifierPart SingleLineComment @@ -391,6 +393,13 @@ CallExpression args: args || [] }; } + / AppendToken __ "(" __ name:Identifier __ "," __ args:ArgumentList __ ")" { + return { + tag: "AppendExpression", + name: name, + args: args + } + } MakeArguments = "," __ args:ArgumentList { diff --git a/src/tests/test.ts b/src/tests/test.ts index 035b8bc..62b2673 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -839,64 +839,66 @@ v.X // 3 // Test WaitGroups -// testProgram( -// ` -// -// type Vertex struct { -// X int -// } -// -// func (v *Vertex) Add(w Vertex) { -// v.X = v.X + w.X; -// } -// -// v := Vertex{1}; -// w := Vertex{2}; -// -// go func() { -// for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended -// } -// v.Add(w); -// }() -// -// v.X; //1 -// `, -// 1, -// '', -// defaultNumWords -// ); - -// testProgram( -// ` -// -// type Vertex struct { -// X int -// } -// -// func (v *Vertex) Add(w Vertex) { -// v.X = v.X + w.X; -// } -// -// v := Vertex{1}; -// w := Vertex{2}; -// -// wg := WaitGroup{1}; -// -// go func() { -// for i := 0; i < 1000; i++ { // iterate for 1000 times to prove that waitgroup works as intended -// } -// v.Add(w); -// wg.Done(); -// }() -// -// wg.Wait(); -// -// v.X; //3 -// `, -// 3, -// '', -// defaultNumWords -// ); +testProgram( + ` + +type Vertex struct { + X int +} + +func (v *Vertex) Add(w Vertex) { + v.X = v.X + w.X; +} + +v := Vertex{1}; +w := Vertex{2}; + +go func() { + for i := 0; i < 100; i++ { // iterate for 100 times to prove that waitgroup works as intended + } + v.Add(w); +}() + +v.X; //1 +`, + 1, + '', + defaultNumWords +); + +testProgram( + ` + +type Vertex struct { + X int +} + +func (v *Vertex) Add(w Vertex) { + v.X = v.X + w.X; +} + +v := Vertex{1}; +w := Vertex{2}; + +wg := WaitGroup{1}; + +go func() { + + for i := 0; i < 100; i++ { // iterate for 100 times to prove that waitgroup works as intended + } + v.Add(w); + wg.Done(); +}() + +wg.Wait(); + +v.X; //3 + +`, + 3, + '', + defaultNumWords +); // Testing struct methods with goroutines (goroutines cannot return anything) testProgram( From 2425e07768b1b42c6cdc42fe12b4f004bfa29941 Mon Sep 17 00:00:00 2001 From: JothamWong <45916998+JothamWong@users.noreply.github.com> Date: Sun, 14 Apr 2024 03:23:11 +0800 Subject: [PATCH 3/6] Add default initialization tests --- src/tests/test.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/tests/test.ts b/src/tests/test.ts index 62b2673..1128edd 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -1690,3 +1690,24 @@ print(y[5]); // shud be 10 print(len(x)); // should be 5 y[0]; // should be 0 `, 0, '10\n5\n', defaultNumWords); + +// Testing default initialization +testProgram(` +var x []bool = make([]bool, 5, 5); // create a slice of len 5 and capacity 5 + +for i := 0; i < len(x); i++ { + print(x[i]); +} +0; +`, 0, 'false\nfalse\nfalse\nfalse\nfalse\n', defaultNumWords); + + +testProgram(` +var x []string = make([]string, 5, 5); // create a slice of len 5 and capacity 5 +x = append(x, "Jotham"); + +for i := 0; i < len(x); i++ { + print(x[i]); +} +0; +`, 0, '""\n""\n""\n""\n""\n"Jotham"\n', defaultNumWords); From 5269a887e852db0aa095546d7b5b8cca294df564 Mon Sep 17 00:00:00 2001 From: JothamWong <45916998+JothamWong@users.noreply.github.com> Date: Sun, 14 Apr 2024 03:26:09 +0800 Subject: [PATCH 4/6] Add out of bounds error testing --- src/tests/test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tests/test.ts b/src/tests/test.ts index 1128edd..9ed73a3 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -1711,3 +1711,14 @@ for i := 0; i < len(x); i++ { } 0; `, 0, '""\n""\n""\n""\n""\n"Jotham"\n', defaultNumWords); + +// Test out of bounds error +testProgram(` +var x []int = make([]int, 5, 5); +x[6]; +`, 'Array out of bounds error!', '', defaultNumWords); + +testProgram(` +var x []int = make([]int, 5, 10); +x[6]; // still garbage data +`, 'Array out of bounds error!', '', defaultNumWords); From 1c07fc3174b7bd232ebedbb3ed418a0acc294740 Mon Sep 17 00:00:00 2001 From: JothamWong <45916998+JothamWong@users.noreply.github.com> Date: Sun, 14 Apr 2024 03:31:46 +0800 Subject: [PATCH 5/6] Update README to document Slices --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28a6a13..639c7fe 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,19 @@ of programs. Ooga supports integers, floats, booleans, strings and custom Struct expressions. -WORK IN PROGRESS: Slices and Channels +Ooga also supports buffered, unbuffered channels as well as arrays and slices which are dynamically resizable arrays. + +Ooga slices follow Golang's slices behaviour in which a re-allocation of memory creates a new slice. + +Consider the following ooga snippet code. +```go +var x []int = make([]int, 5, 5); // allocates a slice of len 5 and capacity 5 +var y []int = append(x, 5); // this causes a reallocation of a new slice of capacity 10 +// x and y now point to slices of capacity 5 and 10 respectively +// y has len = 6 +``` + +This is a test case in our suite. ### Test Suite From 0df852667d12a1ef0c862035609a69e9901e8fcf Mon Sep 17 00:00:00 2001 From: JothamWong <45916998+JothamWong@users.noreply.github.com> Date: Sun, 14 Apr 2024 03:56:50 +0800 Subject: [PATCH 6/6] Update slice test for default struct initialization --- src/tests/test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tests/test.ts b/src/tests/test.ts index 9ed73a3..1bc104d 100644 --- a/src/tests/test.ts +++ b/src/tests/test.ts @@ -1712,6 +1712,20 @@ for i := 0; i < len(x); i++ { 0; `, 0, '""\n""\n""\n""\n""\n"Jotham"\n', defaultNumWords); +testProgram(` +type Vector struct { + x int + y int +} + +var vs []Vector = make([]Vector, 5, 10); +for i := 0; i < len(vs); i++ { + print(vs[i]); // null 5 times +} +10; +`, 10, 'null\nnull\nnull\nnull\nnull\n', defaultNumWords); + + // Test out of bounds error testProgram(` var x []int = make([]int, 5, 5);