Skip to content

Commit

Permalink
[V-119] While loops (#50)
Browse files Browse the repository at this point in the history
* Add basic support for while loops

* Update docs
  • Loading branch information
drew-y authored Sep 23, 2024
1 parent 9479ac7 commit c994017
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 17 deletions.
40 changes: 25 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**:

Expand Down Expand Up @@ -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
```

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -363,14 +365,23 @@ fn do_work(o: Object)

## Closures

Status: Not yet implemented
> Status: Not yet implemented

```rust
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
Expand All @@ -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<T>(a: T, b: T) -> T
Expand All @@ -403,7 +413,7 @@ fn add<T: Numeric>(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,
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ describe("E2E Compiler Pipeline", () => {
3,
42,
2, // IntersectionType tests
20, // While loop
]);
});

Expand Down
10 changes: 10 additions & 0 deletions src/__tests__/fixtures/e2e-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = `
Expand Down
35 changes: 34 additions & 1 deletion src/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface CompileExprOpts<T = Expr> {
extensionHelpers: ReturnType<typeof initExtensionHelpers>;
fieldLookupHelpers: ReturnType<typeof initFieldLookupHelpers>;
isReturnExpr?: boolean;
loopBreakId?: string;
}

export const compileExpression = (opts: CompileExprOpts): number => {
Expand Down Expand Up @@ -195,6 +196,8 @@ const compileMatch = (opts: CompileExprOpts<Match>) => {
const compileIdentifier = (opts: CompileExprOpts<Identifier>) => {
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}`);
Expand All @@ -220,7 +223,8 @@ const compileCall = (opts: CompileExprOpts<Call>): 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);
}
Expand All @@ -247,6 +251,35 @@ const compileCall = (opts: CompileExprOpts<Call>): number => {
return mod.call(id, args, returnType);
};

const compileWhile = (opts: CompileExprOpts<Call>) => {
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<Call>) => {
const { expr, mod } = opts;

Expand Down
17 changes: 17 additions & 0 deletions src/semantics/check-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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()) {
Expand Down Expand Up @@ -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}`);
Expand Down
5 changes: 4 additions & 1 deletion src/semantics/resolution/get-call-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ const isPrimitiveFnCall = (call: Call): boolean => {
name === "return" ||
name === "binaryen" ||
name === ":" ||
name === "="
name === "=" ||
name === "while" ||
name === "for" ||
name === "break"
);
};
7 changes: 7 additions & 0 deletions src/semantics/resolution/resolve-call-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
};

0 comments on commit c994017

Please sign in to comment.