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[];
|
||||
};
|
||||
|
||||
export type ExprFieldAccess = {
|
||||
kind: "fieldAccess";
|
||||
lhs: Expr;
|
||||
field: {
|
||||
value: string | number;
|
||||
span: Span;
|
||||
fieldIdx?: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ExprIf = {
|
||||
kind: "if";
|
||||
cond: Expr;
|
||||
|
|
@ -161,6 +171,7 @@ export type ExprKind =
|
|||
| ExprBinary
|
||||
| ExprUnary
|
||||
| ExprCall
|
||||
| ExprFieldAccess
|
||||
| ExprIf
|
||||
| ExprLoop
|
||||
| ExprBreak
|
||||
|
|
@ -546,6 +557,13 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr {
|
|||
args: expr.args.map((expr) => folder.expr(expr)),
|
||||
};
|
||||
}
|
||||
case "fieldAccess": {
|
||||
return {
|
||||
...expr,
|
||||
kind: "fieldAccess",
|
||||
lhs: folder.expr(expr.lhs),
|
||||
};
|
||||
}
|
||||
case "if": {
|
||||
return {
|
||||
...expr,
|
||||
|
|
|
|||
67
src/index.ts
67
src/index.ts
|
|
@ -11,71 +11,12 @@ import { exec } from "child_process";
|
|||
|
||||
const INPUT = `
|
||||
function main() = (
|
||||
prIntln(0);
|
||||
prIntln(1);
|
||||
prIntln(9);
|
||||
prIntln(2352353);
|
||||
prIntln(100);
|
||||
printTuples(("hello, ", "world\\n"));
|
||||
);
|
||||
|
||||
function prIntln(x: Int) = (
|
||||
prInt(x);
|
||||
print("\n");
|
||||
);
|
||||
|
||||
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");
|
||||
function printTuples(a: (String, String)) = (
|
||||
print(a.0);
|
||||
print(a.1);
|
||||
);
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)) {
|
||||
// ignore
|
||||
} 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,
|
||||
Ty,
|
||||
TyFn,
|
||||
TyTuple,
|
||||
varUnreachable,
|
||||
} from "./ast";
|
||||
import { encodeUtf8, unwrap } from "./utils";
|
||||
|
|
@ -558,6 +559,68 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
|
|||
instrs.push(callInstr);
|
||||
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": {
|
||||
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 {
|
||||
switch (param.kind) {
|
||||
case "string":
|
||||
return STRING_ABI;
|
||||
case "fn":
|
||||
todo("fn abi");
|
||||
case "int":
|
||||
return ["i64"];
|
||||
case "i32":
|
||||
return ["i32"];
|
||||
case "bool":
|
||||
return ["i32"];
|
||||
case "list":
|
||||
todo("list abi");
|
||||
case "tuple":
|
||||
return param.elems.flatMap(argRetAbi);
|
||||
case "struct":
|
||||
todo("struct ABI");
|
||||
case "never":
|
||||
return [];
|
||||
case "var":
|
||||
varUnreachable();
|
||||
}
|
||||
function argRetAbi(param: Ty): ArgRetAbi {
|
||||
switch (param.kind) {
|
||||
case "string":
|
||||
return STRING_ABI;
|
||||
case "fn":
|
||||
todo("fn abi");
|
||||
case "int":
|
||||
return ["i64"];
|
||||
case "i32":
|
||||
return ["i32"];
|
||||
case "bool":
|
||||
return ["i32"];
|
||||
case "list":
|
||||
todo("list abi");
|
||||
case "tuple":
|
||||
return param.elems.flatMap(argRetAbi);
|
||||
case "struct":
|
||||
todo("struct ABI");
|
||||
case "never":
|
||||
return [];
|
||||
case "var":
|
||||
varUnreachable();
|
||||
}
|
||||
}
|
||||
|
||||
function computeAbi(ty: TyFn): FnAbi {
|
||||
const params = ty.params.map(argRetAbi);
|
||||
const ret = argRetAbi(ty.returnTy);
|
||||
|
||||
|
|
@ -773,6 +836,14 @@ function blockTypeForBody(cx: Context, ty: Ty): wasm.Blocktype {
|
|||
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 {
|
||||
throw new Error(`TODO: ${msg}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ function parseExpr(t: Token[]): [Token[], Expr] {
|
|||
|
||||
UNARY = { "!" | "-" } CALL
|
||||
|
||||
CALL = ATOM { "(" EXPR_LIST ")" }
|
||||
CALL = ATOM { ( "(" EXPR_LIST ")" ) | ( "." ( IDENT | NUMBER ) ) }
|
||||
|
||||
ATOM = "(" { EXPR ";" | "," } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY | LET | IF | LOOP | BREAK
|
||||
EMPTY =
|
||||
|
|
@ -271,14 +271,34 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
|
|||
let lhs: Expr;
|
||||
[t, lhs] = parseExprAtom(t);
|
||||
|
||||
while (next(t)[1].kind === "(") {
|
||||
let popen;
|
||||
[t, popen] = next(t);
|
||||
while (next(t)[1].kind === "(" || next(t)[1].kind === ".") {
|
||||
let tok;
|
||||
[t, tok] = next(t);
|
||||
|
||||
let args;
|
||||
[t, args] = parseCommaSeparatedList(t, ")", parseExpr);
|
||||
if (tok.kind === "(") {
|
||||
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];
|
||||
|
|
@ -565,7 +585,10 @@ function expectNext<T extends BaseToken>(
|
|||
let tok;
|
||||
[t, tok] = next(t);
|
||||
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];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -140,6 +140,9 @@ function printExpr(expr: Expr, indent: number): string {
|
|||
);
|
||||
}
|
||||
}
|
||||
case "fieldAccess": {
|
||||
return `${printExpr(expr.lhs, indent)}.${expr.field.value}`;
|
||||
}
|
||||
case "if": {
|
||||
const elsePart = expr.else
|
||||
? ` else ${printExpr(expr.else, indent + 1)}`
|
||||
|
|
|
|||
|
|
@ -677,6 +677,76 @@ export function checkBody(
|
|||
|
||||
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": {
|
||||
const cond = this.expr(expr.cond);
|
||||
const then = this.expr(expr.then);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue