diff --git a/README.md b/README.md index 0a8eaa5..642c4fb 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,9 @@ pub fn main() **Disclaimer** -Voyd is in it's very early stages of development. Voyd is not ready for public -announcement or use. Some core syntax and semantics are subject to change. -Expect frequent breaking changes. In addition, many documented features are not -yet implemented. +Voyd is in it's very early stages of development. Voyd is not ready for production use. Some syntax and semantics are still subject to change. Expect frequent breaking changes. + +All features documented in README Overview are fully implemented unless otherwise stated. Features documented in the [reference](./reference/) are not yet marked with status and may not be implemented. **Features**: @@ -84,9 +83,9 @@ true // Boolean false // Boolean 1 // i32 by default 1.0 // f64 by default -"Hello!" // String, can be multiline, supports interpolation via ${} -[1, 2, 3] // Array literal -(1, 2, 3) // Tuple literal +"Hello!" // String, can be multiline, supports interpolation via ${} (NOTE: Not yet implemented) +[1, 2, 3] // Array literal (NOTE: Not yet implemented) +(1, 2, 3) // Tuple literal (NOTE: Not yet implemented) {x: 2, y: 4} // Structural object literal ``` @@ -130,7 +129,7 @@ Voyd also supports uniform function call syntax (UFCS), allowing functions to be ### Labeled arguments -Status: Not yet implemented +> Status: Not yet implemented Labeled arguments can be defined by wrapping parameters you wish to be labeled on call in curly braces. @@ -203,7 +202,10 @@ let x = if 3 < val then: "hello" else: "bye" ## Loops -Status: Not yet implemented +> Status: Partially implemented. +> - Tail call optimization fully implemented. +> - While loops and break partially implemented. Do not yet support returning a value. +> - For loops not yet implemented. While loops are the most basic looping construct @@ -337,7 +339,7 @@ a union, they must have a case for each object in the union ## Traits -Status: Not yet implemented +> Status: Not yet implemented Traits define a set of behavior that can be implemented on any object type (nominal, structural, union, or intersection) @@ -363,7 +365,7 @@ fn do_work(o: Object) ## Closures -Status: Not yet implemented +> Status: Not yet implemented ```rust let double = n => n * 2 @@ -371,6 +373,15 @@ let double = n => n * 2 array.map n => n * 2 ``` +Void also supports a concise syntax for passing closures to labeled arguments: + +```rust +try do(): + call_fn_that_has_exception_effect() +catch: + print "Error!" +``` + ## Dot Notation The dot is a simple form of syntactic sugar @@ -386,8 +397,7 @@ squared(x) ## Generics -Status: Basic implementation complete for objects, functions, impls, and type -aliases. Inference is not yet supported. +> Status: Basic implementation complete for objects, functions, impls, and type aliases. Inference is not yet supported. ```rust fn add(a: T, b: T) -> T @@ -403,7 +413,7 @@ fn add(a: T, b: T) -> T ## Effects -Status: Not yet implemented +> Status: Not yet implemented Effects (will be) a powerful construct of the voyd type system. Effects are useful for a large class of problems including type safe exceptions, @@ -432,7 +442,7 @@ effect fn get() -> Int ## JSX -Status: In Progress +> Status: Implementation in progress Voyd has built in support for JSX. Useful for rendering websites or creating interactive web apps diff --git a/src/__tests__/compiler.test.ts b/src/__tests__/compiler.test.ts index 10d2896..9e0fa76 100644 --- a/src/__tests__/compiler.test.ts +++ b/src/__tests__/compiler.test.ts @@ -57,6 +57,7 @@ describe("E2E Compiler Pipeline", () => { 3, 42, 2, // IntersectionType tests + 20, // While loop ]); }); diff --git a/src/__tests__/fixtures/e2e-file.ts b/src/__tests__/fixtures/e2e-file.ts index 4b7b978..c1e3bff 100644 --- a/src/__tests__/fixtures/e2e-file.ts +++ b/src/__tests__/fixtures/e2e-file.ts @@ -221,6 +221,16 @@ fn get_legs(a: Animal & { legs: i32 }) -> i32 pub fn test18() -> i32 let human = Mammal { age: 10, legs: 2 } get_legs(human) + +// Test while loops and breaking, should return 20 +pub fn test19() -> i32 + var x = 0 + var i = 0 + while i < 10 do: + x = x + i * 2 + i = i + 1 + if i == 5 then: break + x `; export const tcoText = ` diff --git a/src/assembler.ts b/src/assembler.ts index 4118538..501d4eb 100644 --- a/src/assembler.ts +++ b/src/assembler.ts @@ -50,6 +50,7 @@ export interface CompileExprOpts { extensionHelpers: ReturnType; fieldLookupHelpers: ReturnType; isReturnExpr?: boolean; + loopBreakId?: string; } export const compileExpression = (opts: CompileExprOpts): number => { @@ -195,6 +196,8 @@ const compileMatch = (opts: CompileExprOpts) => { const compileIdentifier = (opts: CompileExprOpts) => { const { expr, mod } = opts; + if (expr.is("break")) return mod.br(opts.loopBreakId!); + const entity = expr.resolve(); if (!entity) { throw new Error(`Unrecognized symbol ${expr.value}`); @@ -220,7 +223,8 @@ const compileCall = (opts: CompileExprOpts): number => { if (expr.calls("export")) return compileExport(opts); if (expr.calls("mod")) return mod.nop(); if (expr.calls("member-access")) return compileObjMemberAccess(opts); - + if (expr.calls("while")) return compileWhile(opts); + if (expr.calls("break")) return mod.br(opts.loopBreakId!); if (expr.calls("binaryen")) { return compileBnrCall(opts); } @@ -247,6 +251,35 @@ const compileCall = (opts: CompileExprOpts): number => { return mod.call(id, args, returnType); }; +const compileWhile = (opts: CompileExprOpts) => { + const { expr, mod } = opts; + const loopId = expr.syntaxId.toString(); + const breakId = `__break_${loopId}`; + return mod.loop( + loopId, + mod.block(breakId, [ + mod.br_if( + breakId, + mod.i32.ne( + compileExpression({ + ...opts, + expr: expr.exprArgAt(0), + isReturnExpr: false, + }), + mod.i32.const(1) + ) + ), + compileExpression({ + ...opts, + expr: expr.labeledArgAt(1), + loopBreakId: breakId, + isReturnExpr: false, + }), + mod.br(loopId), + ]) + ); +}; + const compileObjectInit = (opts: CompileExprOpts) => { const { expr, mod } = opts; diff --git a/src/semantics/check-types.ts b/src/semantics/check-types.ts index bf38925..fb1f7fe 100644 --- a/src/semantics/check-types.ts +++ b/src/semantics/check-types.ts @@ -52,8 +52,10 @@ const checkCallTypes = (call: Call): Call | ObjectLiteral => { if (call.calls("if")) return checkIf(call); if (call.calls("binaryen")) return checkBinaryenCall(call); if (call.calls("mod")) return call; + if (call.calls("break")) return call; if (call.calls(":")) return checkLabeledArg(call); if (call.calls("=")) return checkAssign(call); + if (call.calls("while")) return checkWhile(call); if (call.calls("member-access")) return call; // TODO if (call.fn?.isObjectType()) return checkObjectInit(call); @@ -79,6 +81,19 @@ const checkCallTypes = (call: Call): Call | ObjectLiteral => { return call; }; +const checkWhile = (call: Call) => { + const cond = call.argAt(0); + const condType = getExprType(cond); + if (!cond || !condType || !typesAreCompatible(condType, bool)) { + throw new Error( + `While conditions must resolve to a boolean at ${cond?.location}` + ); + } + + checkTypes(call.argAt(1)); + return call; +}; + const checkObjectInit = (call: Call): Call => { const literal = call.argAt(0); if (!literal?.isObjectLiteral()) { @@ -119,6 +134,8 @@ export const checkAssign = (call: Call) => { }; const checkIdentifier = (id: Identifier) => { + if (id.is("return") || id.is("break")) return id; + const entity = id.resolve(); if (!entity) { throw new Error(`Unrecognized identifier, ${id} at ${id.location}`); diff --git a/src/semantics/resolution/get-call-fn.ts b/src/semantics/resolution/get-call-fn.ts index 33438f4..5f52a68 100644 --- a/src/semantics/resolution/get-call-fn.ts +++ b/src/semantics/resolution/get-call-fn.ts @@ -113,6 +113,9 @@ const isPrimitiveFnCall = (call: Call): boolean => { name === "return" || name === "binaryen" || name === ":" || - name === "=" + name === "=" || + name === "while" || + name === "for" || + name === "break" ); }; diff --git a/src/semantics/resolution/resolve-call-types.ts b/src/semantics/resolution/resolve-call-types.ts index fb6e007..dab9c97 100644 --- a/src/semantics/resolution/resolve-call-types.ts +++ b/src/semantics/resolution/resolve-call-types.ts @@ -12,6 +12,7 @@ export const resolveCallTypes = (call: Call): Call => { if (call.calls("export")) return resolveExport(call); if (call.calls("if")) return resolveIf(call); if (call.calls(":")) return checkLabeledArg(call); + if (call.calls("while")) return resolveWhile(call); call.args = call.args.map(resolveTypes); const memberAccessCall = getMemberAccessCall(call); @@ -125,3 +126,9 @@ export const resolveIf = (call: Call) => { call.type = thenType; return call; }; + +export const resolveWhile = (call: Call) => { + call.args = call.args.map(resolveTypes); + call.type = dVoid; + return call; +};