Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[V-119] While loops #50

Merged
merged 2 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};