mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
implement field accesses
This commit is contained in:
parent
7f65dc0277
commit
2da011caf4
7 changed files with 224 additions and 96 deletions
18
src/ast.ts
18
src/ast.ts
|
|
@ -120,6 +120,16 @@ export type ExprCall = {
|
||||||
args: Expr[];
|
args: Expr[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ExprFieldAccess = {
|
||||||
|
kind: "fieldAccess";
|
||||||
|
lhs: Expr;
|
||||||
|
field: {
|
||||||
|
value: string | number;
|
||||||
|
span: Span;
|
||||||
|
fieldIdx?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export type ExprIf = {
|
export type ExprIf = {
|
||||||
kind: "if";
|
kind: "if";
|
||||||
cond: Expr;
|
cond: Expr;
|
||||||
|
|
@ -161,6 +171,7 @@ export type ExprKind =
|
||||||
| ExprBinary
|
| ExprBinary
|
||||||
| ExprUnary
|
| ExprUnary
|
||||||
| ExprCall
|
| ExprCall
|
||||||
|
| ExprFieldAccess
|
||||||
| ExprIf
|
| ExprIf
|
||||||
| ExprLoop
|
| ExprLoop
|
||||||
| ExprBreak
|
| ExprBreak
|
||||||
|
|
@ -546,6 +557,13 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr {
|
||||||
args: expr.args.map((expr) => folder.expr(expr)),
|
args: expr.args.map((expr) => folder.expr(expr)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case "fieldAccess": {
|
||||||
|
return {
|
||||||
|
...expr,
|
||||||
|
kind: "fieldAccess",
|
||||||
|
lhs: folder.expr(expr.lhs),
|
||||||
|
};
|
||||||
|
}
|
||||||
case "if": {
|
case "if": {
|
||||||
return {
|
return {
|
||||||
...expr,
|
...expr,
|
||||||
|
|
|
||||||
67
src/index.ts
67
src/index.ts
|
|
@ -11,71 +11,12 @@ import { exec } from "child_process";
|
||||||
|
|
||||||
const INPUT = `
|
const INPUT = `
|
||||||
function main() = (
|
function main() = (
|
||||||
prIntln(0);
|
printTuples(("hello, ", "world\\n"));
|
||||||
prIntln(1);
|
|
||||||
prIntln(9);
|
|
||||||
prIntln(2352353);
|
|
||||||
prIntln(100);
|
|
||||||
);
|
);
|
||||||
|
|
||||||
function prIntln(x: Int) = (
|
function printTuples(a: (String, String)) = (
|
||||||
prInt(x);
|
print(a.0);
|
||||||
print("\n");
|
print(a.1);
|
||||||
);
|
|
||||||
|
|
||||||
function stringForDigit(x: Int): String =
|
|
||||||
if x == 0 then "0"
|
|
||||||
else if x == 1 then "1"
|
|
||||||
else if x == 2 then "2"
|
|
||||||
else if x == 3 then "3"
|
|
||||||
else if x == 4 then "4"
|
|
||||||
else if x == 5 then "5"
|
|
||||||
else if x == 6 then "6"
|
|
||||||
else if x == 7 then "7"
|
|
||||||
else if x == 8 then "8"
|
|
||||||
else if x == 9 then "9"
|
|
||||||
else trap();
|
|
||||||
|
|
||||||
function log10(x: Int): Int = (
|
|
||||||
let i = 0;
|
|
||||||
loop (
|
|
||||||
if x < 10 then break;
|
|
||||||
i = i + 1;
|
|
||||||
x = x / 10;
|
|
||||||
);
|
|
||||||
i
|
|
||||||
);
|
|
||||||
|
|
||||||
function pow(base: Int, exp: Int): Int = (
|
|
||||||
let acc = 1;
|
|
||||||
loop (
|
|
||||||
if exp == 0 then break;
|
|
||||||
acc = acc * base;
|
|
||||||
exp = exp - 1;
|
|
||||||
);
|
|
||||||
acc
|
|
||||||
);
|
|
||||||
|
|
||||||
function prInt(x: Int) = (
|
|
||||||
let mag = log10(x);
|
|
||||||
|
|
||||||
loop (
|
|
||||||
if mag == 0 then break;
|
|
||||||
let base = pow(10, mag);
|
|
||||||
|
|
||||||
let digit = x / base;
|
|
||||||
print(stringForDigit(digit));
|
|
||||||
|
|
||||||
x = x % base;
|
|
||||||
mag = mag - 1;
|
|
||||||
);
|
|
||||||
|
|
||||||
print(stringForDigit(x % 10));
|
|
||||||
);
|
|
||||||
|
|
||||||
function println(s: String) = (
|
|
||||||
print(s);
|
|
||||||
print("\n");
|
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export type DatalessToken =
|
||||||
| "]"
|
| "]"
|
||||||
| ";"
|
| ";"
|
||||||
| ":"
|
| ":"
|
||||||
|
| "."
|
||||||
| ","
|
| ","
|
||||||
| "="
|
| "="
|
||||||
| "+"
|
| "+"
|
||||||
|
|
@ -70,6 +71,7 @@ const SINGLE_PUNCT: string[] = [
|
||||||
"]",
|
"]",
|
||||||
";",
|
";",
|
||||||
":",
|
":",
|
||||||
|
".",
|
||||||
",",
|
",",
|
||||||
"+",
|
"+",
|
||||||
"-",
|
"-",
|
||||||
|
|
@ -245,7 +247,7 @@ export function tokenize(input: string): Token[] {
|
||||||
} else if (isWhitespace(next)) {
|
} else if (isWhitespace(next)) {
|
||||||
// ignore
|
// ignore
|
||||||
} else {
|
} else {
|
||||||
throw new CompilerError(`Invalid character: \`${next}\``, span);
|
throw new CompilerError(`invalid character: \`${next}\``, span);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
119
src/lower.ts
119
src/lower.ts
|
|
@ -9,6 +9,7 @@ import {
|
||||||
Resolution,
|
Resolution,
|
||||||
Ty,
|
Ty,
|
||||||
TyFn,
|
TyFn,
|
||||||
|
TyTuple,
|
||||||
varUnreachable,
|
varUnreachable,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { encodeUtf8, unwrap } from "./utils";
|
import { encodeUtf8, unwrap } from "./utils";
|
||||||
|
|
@ -558,6 +559,68 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
||||||
instrs.push(callInstr);
|
instrs.push(callInstr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "fieldAccess": {
|
||||||
|
// We could just naively always evaluate the LHS normally, but that's kinda
|
||||||
|
// stupid as it would cause way too much code for `let a = (0, 0, 0); a.0`
|
||||||
|
// as that operation would first load the entire tuple onto the stack!
|
||||||
|
// Therefore, we are a little clever be peeking into the LHS and doing
|
||||||
|
// something smarter if it's another field access or ident (in the future,
|
||||||
|
// we should be able to generalize this to all "places"/"lvalues").
|
||||||
|
|
||||||
|
// TODO: Actually do this instead of being naive.
|
||||||
|
|
||||||
|
const isPlace = (expr: Expr) =>
|
||||||
|
expr.kind === "ident" || expr.kind === "fieldAccess";
|
||||||
|
|
||||||
|
function project() {}
|
||||||
|
|
||||||
|
lowerExpr(fcx, instrs, expr.lhs);
|
||||||
|
|
||||||
|
switch (expr.lhs.ty!.kind) {
|
||||||
|
case "tuple": {
|
||||||
|
// Tuples have a by-value ABI, so we can simply index.
|
||||||
|
const lhsSize = argRetAbi(expr.lhs.ty!).length;
|
||||||
|
const resultAbi = argRetAbi(expr.ty!);
|
||||||
|
const resultSize = resultAbi.length;
|
||||||
|
const wasmIdx = wasmTypeIdxForTupleField(
|
||||||
|
expr.lhs.ty!,
|
||||||
|
expr.field.fieldIdx!
|
||||||
|
);
|
||||||
|
|
||||||
|
// lhsSize=5, resultSize=2, wasmIdx=2
|
||||||
|
// I I Y Y I
|
||||||
|
// drop, 2xlocal.set, drop, drop, 2xlocal.get
|
||||||
|
|
||||||
|
// TODO: Establish some way of having reusable "scratch locals".
|
||||||
|
const localIdx = fcx.wasm.locals.length + fcx.wasmType.params.length;
|
||||||
|
fcx.wasm.locals.push(...resultAbi);
|
||||||
|
|
||||||
|
Array(lhsSize - wasmIdx - resultSize)
|
||||||
|
.fill(0)
|
||||||
|
.forEach(() => instrs.push({ kind: "drop" }));
|
||||||
|
|
||||||
|
if (expr.field.fieldIdx! > 0) {
|
||||||
|
// Keep the result in scratch space.
|
||||||
|
storeVariable(instrs, { localIdx, types: resultAbi });
|
||||||
|
|
||||||
|
Array(wasmIdx)
|
||||||
|
.fill(0)
|
||||||
|
.forEach(() => instrs.push({ kind: "drop" }));
|
||||||
|
|
||||||
|
loadVariable(instrs, { localIdx, types: resultAbi });
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "struct": {
|
||||||
|
todo("struct field accesses");
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error("invalid field access lhs");
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "if": {
|
case "if": {
|
||||||
lowerExpr(fcx, instrs, expr.cond!);
|
lowerExpr(fcx, instrs, expr.cond!);
|
||||||
|
|
||||||
|
|
@ -690,32 +753,32 @@ function storeVariable(instrs: wasm.Instr[], loc: VarLocation) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeAbi(ty: TyFn): FnAbi {
|
function argRetAbi(param: Ty): ArgRetAbi {
|
||||||
function argRetAbi(param: Ty): ArgRetAbi {
|
switch (param.kind) {
|
||||||
switch (param.kind) {
|
case "string":
|
||||||
case "string":
|
return STRING_ABI;
|
||||||
return STRING_ABI;
|
case "fn":
|
||||||
case "fn":
|
todo("fn abi");
|
||||||
todo("fn abi");
|
case "int":
|
||||||
case "int":
|
return ["i64"];
|
||||||
return ["i64"];
|
case "i32":
|
||||||
case "i32":
|
return ["i32"];
|
||||||
return ["i32"];
|
case "bool":
|
||||||
case "bool":
|
return ["i32"];
|
||||||
return ["i32"];
|
case "list":
|
||||||
case "list":
|
todo("list abi");
|
||||||
todo("list abi");
|
case "tuple":
|
||||||
case "tuple":
|
return param.elems.flatMap(argRetAbi);
|
||||||
return param.elems.flatMap(argRetAbi);
|
case "struct":
|
||||||
case "struct":
|
todo("struct ABI");
|
||||||
todo("struct ABI");
|
case "never":
|
||||||
case "never":
|
return [];
|
||||||
return [];
|
case "var":
|
||||||
case "var":
|
varUnreachable();
|
||||||
varUnreachable();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeAbi(ty: TyFn): FnAbi {
|
||||||
const params = ty.params.map(argRetAbi);
|
const params = ty.params.map(argRetAbi);
|
||||||
const ret = argRetAbi(ty.returnTy);
|
const ret = argRetAbi(ty.returnTy);
|
||||||
|
|
||||||
|
|
@ -773,6 +836,14 @@ function blockTypeForBody(cx: Context, ty: Ty): wasm.Blocktype {
|
||||||
return { kind: "typeidx", idx: typeIdx };
|
return { kind: "typeidx", idx: typeIdx };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wasmTypeIdxForTupleField(ty: TyTuple, idx: number): number {
|
||||||
|
// Tuples are all flattened by value, so we just count the values in
|
||||||
|
// the flattened representation.
|
||||||
|
const layout = ty.elems.map(argRetAbi);
|
||||||
|
const head = layout.slice(0, idx);
|
||||||
|
return head.reduce((a, b) => a + b.length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
function todo(msg: string): never {
|
function todo(msg: string): never {
|
||||||
throw new Error(`TODO: ${msg}`);
|
throw new Error(`TODO: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -188,7 +188,7 @@ function parseExpr(t: Token[]): [Token[], Expr] {
|
||||||
|
|
||||||
UNARY = { "!" | "-" } CALL
|
UNARY = { "!" | "-" } CALL
|
||||||
|
|
||||||
CALL = ATOM { "(" EXPR_LIST ")" }
|
CALL = ATOM { ( "(" EXPR_LIST ")" ) | ( "." ( IDENT | NUMBER ) ) }
|
||||||
|
|
||||||
ATOM = "(" { EXPR ";" | "," } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY | LET | IF | LOOP | BREAK
|
ATOM = "(" { EXPR ";" | "," } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY | LET | IF | LOOP | BREAK
|
||||||
EMPTY =
|
EMPTY =
|
||||||
|
|
@ -271,14 +271,34 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
|
||||||
let lhs: Expr;
|
let lhs: Expr;
|
||||||
[t, lhs] = parseExprAtom(t);
|
[t, lhs] = parseExprAtom(t);
|
||||||
|
|
||||||
while (next(t)[1].kind === "(") {
|
while (next(t)[1].kind === "(" || next(t)[1].kind === ".") {
|
||||||
let popen;
|
let tok;
|
||||||
[t, popen] = next(t);
|
[t, tok] = next(t);
|
||||||
|
|
||||||
let args;
|
if (tok.kind === "(") {
|
||||||
[t, args] = parseCommaSeparatedList(t, ")", parseExpr);
|
let args;
|
||||||
|
[t, args] = parseCommaSeparatedList(t, ")", parseExpr);
|
||||||
|
|
||||||
lhs = { kind: "call", span: popen.span, lhs, args };
|
lhs = { kind: "call", span: tok.span, lhs, args };
|
||||||
|
} else if (tok.kind === ".") {
|
||||||
|
let access;
|
||||||
|
[t, access] = next(t);
|
||||||
|
let value;
|
||||||
|
if (access.kind === "identifier") {
|
||||||
|
value = access.ident;
|
||||||
|
} else if (access.kind === "lit_int") {
|
||||||
|
value = access.value;
|
||||||
|
} else {
|
||||||
|
unexpectedToken(access, "identifier or integer");
|
||||||
|
}
|
||||||
|
|
||||||
|
lhs = {
|
||||||
|
kind: "fieldAccess",
|
||||||
|
lhs,
|
||||||
|
field: { span: access.span, value },
|
||||||
|
span: spanMerge(lhs.span, access.span),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [t, lhs];
|
return [t, lhs];
|
||||||
|
|
@ -565,7 +585,10 @@ function expectNext<T extends BaseToken>(
|
||||||
let tok;
|
let tok;
|
||||||
[t, tok] = next(t);
|
[t, tok] = next(t);
|
||||||
if (tok.kind !== kind) {
|
if (tok.kind !== kind) {
|
||||||
throw new CompilerError(`expected \`${kind}\`, found \`${tok.kind}\``, tok.span);
|
throw new CompilerError(
|
||||||
|
`expected \`${kind}\`, found \`${tok.kind}\``,
|
||||||
|
tok.span
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return [t, tok as unknown as T & Token];
|
return [t, tok as unknown as T & Token];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,9 @@ function printExpr(expr: Expr, indent: number): string {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "fieldAccess": {
|
||||||
|
return `${printExpr(expr.lhs, indent)}.${expr.field.value}`;
|
||||||
|
}
|
||||||
case "if": {
|
case "if": {
|
||||||
const elsePart = expr.else
|
const elsePart = expr.else
|
||||||
? ` else ${printExpr(expr.else, indent + 1)}`
|
? ` else ${printExpr(expr.else, indent + 1)}`
|
||||||
|
|
|
||||||
|
|
@ -677,6 +677,76 @@ export function checkBody(
|
||||||
|
|
||||||
return { ...expr, lhs, args, ty: lhsTy.returnTy };
|
return { ...expr, lhs, args, ty: lhsTy.returnTy };
|
||||||
}
|
}
|
||||||
|
case "fieldAccess": {
|
||||||
|
const lhs = this.expr(expr.lhs);
|
||||||
|
lhs.ty = infcx.resolveIfPossible(lhs.ty!);
|
||||||
|
|
||||||
|
const { field } = expr;
|
||||||
|
let ty: Ty;
|
||||||
|
let fieldIdx: number;
|
||||||
|
switch (lhs.ty.kind) {
|
||||||
|
case "tuple": {
|
||||||
|
const { elems } = lhs.ty;
|
||||||
|
if (typeof field.value === "number") {
|
||||||
|
if (elems.length > field.value) {
|
||||||
|
ty = elems[field.value];
|
||||||
|
fieldIdx = field.value;
|
||||||
|
} else {
|
||||||
|
throw new CompilerError(
|
||||||
|
`tuple with ${elems.length} elements cannot be indexed with ${field.value}`,
|
||||||
|
field.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new CompilerError(
|
||||||
|
"tuple fields must be accessed with numbers",
|
||||||
|
field.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "struct": {
|
||||||
|
if (typeof field.value === "string") {
|
||||||
|
const idx = lhs.ty.fields.findIndex(
|
||||||
|
([name]) => name === field.value
|
||||||
|
);
|
||||||
|
if (idx === -1) {
|
||||||
|
throw new CompilerError(
|
||||||
|
`field \`${field.value}\` does not exist on ${printTy(
|
||||||
|
lhs.ty
|
||||||
|
)}`,
|
||||||
|
field.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ty = lhs.ty.fields[idx][1];
|
||||||
|
fieldIdx = idx;
|
||||||
|
} else {
|
||||||
|
throw new CompilerError(
|
||||||
|
"struct fields must be accessed with their name",
|
||||||
|
field.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new CompilerError(
|
||||||
|
"only tuples and structs have fields",
|
||||||
|
expr.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...expr,
|
||||||
|
lhs,
|
||||||
|
field: {
|
||||||
|
...expr.field,
|
||||||
|
fieldIdx,
|
||||||
|
},
|
||||||
|
ty,
|
||||||
|
};
|
||||||
|
}
|
||||||
case "if": {
|
case "if": {
|
||||||
const cond = this.expr(expr.cond);
|
const cond = this.expr(expr.cond);
|
||||||
const then = this.expr(expr.then);
|
const then = this.expr(expr.then);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue