start structs

This commit is contained in:
nora 2023-07-27 20:56:02 +02:00
parent f6f6673721
commit b52abed441
8 changed files with 214 additions and 62 deletions

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# a random language compiling to wasm
The language is not well designed. It's just random garbage.

View file

@ -10,10 +10,15 @@ export type Identifier = {
export type ItemId = number;
export type ItemKind = {
kind: "function";
node: FunctionDef;
};
export type ItemKind =
| {
kind: "function";
node: FunctionDef;
}
| {
kind: "type";
node: TypeDef;
};
export type Item = ItemKind & {
span: Span;
@ -34,6 +39,17 @@ export type FunctionArg = {
span: Span;
};
export type TypeDef = {
name: string;
fields: FieldDef[];
ty?: TyStruct;
};
export type FieldDef = {
name: Identifier;
type: Type;
};
export type ExprEmpty = { kind: "empty" };
export type ExprLet = {
@ -262,7 +278,21 @@ export type TyVar = {
index: number;
};
export type Ty = TyString | TyInt | TyBool | TyList | TyTuple | TyFn | TyVar;
export type TyStruct = {
kind: "struct";
name: string;
fields: [string, Ty][];
};
export type Ty =
| TyString
| TyInt
| TyBool
| TyList
| TyTuple
| TyFn
| TyVar
| TyStruct;
export function tyIsUnit(ty: Ty): ty is TyUnit {
return ty.kind === "tuple" && ty.elems.length === 0;
@ -331,6 +361,19 @@ export function superFoldItem(item: Item, folder: Folder): Item {
id: item.id,
};
}
case "type": {
const fields = item.node.fields.map(({ name, type }) => ({
name,
type: folder.type(type),
}));
return {
kind: "type",
span: item.span,
node: { name: item.node.name, fields },
id: item.id,
};
}
}
}

View file

@ -10,8 +10,11 @@ import fs from "fs";
import { exec } from "child_process";
const input = `
function main() = ();
function test(i: Int, j: Int): Bool = false == (i == 0);
type Uwu = (
meow: String,
);
function main(a: Int, b: Uwu) = ();
`;
function main() {
@ -37,9 +40,10 @@ function main() {
console.log(resolvedPrinted);
console.log("-----AST typecked------");
const typecked = typeck(resolved);
return;
console.log("-----wasm--------------");
const wasmModule = lowerToWasm(typecked);
const moduleStringColor = writeModuleWatToString(wasmModule, true);

View file

@ -7,6 +7,7 @@ export type DatalessToken =
| "if"
| "then"
| "else"
| "type"
| "("
| ")"
| "["
@ -209,6 +210,7 @@ const keywords = new Set<string>([
"if",
"then",
"else",
"type",
]);
function isKeyword(kw: string): DatalessToken | undefined {
return keywords.has(kw) ? (kw as DatalessToken) : undefined;

View file

@ -303,6 +303,8 @@ function computeAbi(ty: TyFn): Abi {
return paramAbi(param.elems[0]);
}
todo("complex tuple abi");
case "struct":
todo("struct ABI");
case "var":
varUnreachable();
}
@ -333,6 +335,8 @@ function computeAbi(ty: TyFn): Abi {
break;
}
todo("complex tuple abi");
case "struct":
todo("struct ABI");
case "var":
varUnreachable();
}

View file

@ -5,16 +5,18 @@ import {
BinaryKind,
COMPARISON_KINDS,
Expr,
FieldDef,
FunctionArg,
FunctionDef,
Item,
LOGICAL_KINDS,
Type,
TypeDef,
UNARY_KINDS,
UnaryKind,
} from "./ast";
import { CompilerError, Span, todo } from "./error";
import { BaseToken, Token, TokenIdent } from "./lexer";
import { BaseToken, DatalessToken, Token, TokenIdent } from "./lexer";
type Parser<T> = (t: Token[]) => [Token[], T];
@ -41,24 +43,16 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, "(");
const args: FunctionArg[] = [];
let first = true;
while (next(t)[1]?.kind !== ")") {
if (!first) {
[t] = expectNext(t, ",");
}
first = false;
let args: FunctionArg[];
[t, args] = parseCommaSeparatedList(t, ")", (t) => {
let name;
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
[t] = expectNext(t, ":");
let type;
[t, type] = parseType(t);
args.push({ name: name.ident, type, span: name.span });
}
[t] = expectNext(t, ")");
return [t, { name: name.ident, type, span: name.span }];
});
let colon;
let returnType = undefined;
@ -91,6 +85,33 @@ function parseItem(t: Token[]): [Token[], Item] {
id: 0,
},
];
} else if (tok.kind === "type") {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, "=");
[t] = expectNext(t, "(");
let fields;
[t, fields] = parseCommaSeparatedList<FieldDef>(t, ")", (t) => {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, ":");
let type;
[t, type] = parseType(t);
return [t, { name: {
name: name.ident,
span: name.span,
}, type }];
});
[t] = expectNext(t, ";");
const def: TypeDef = {
name: name.ident,
fields,
};
return [t, { kind: "type", node: def, span: name.span, id: 0 }];
} else {
unexpectedToken(tok);
}
@ -125,7 +146,7 @@ function parseExpr(t: Token[]): [Token[], Expr] {
if (peak.kind === "let") {
[t] = expectNext(t, "let");
let name;
[t, name] = expectNext<TokenIdent & Token>(t, "identifier");
[t, name] = expectNext<TokenIdent>(t, "identifier");
let type = undefined;
let colon;
@ -236,15 +257,9 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
while (next(t)[1].kind === "(") {
let popen;
[t, popen] = next(t);
const args = [];
while (next(t)[1].kind !== ")") {
let arg;
[t, arg] = parseExpr(t);
args.push(arg);
// TODO i think this is incorrect
[t] = eat(t, ",");
}
[t] = expectNext(t, ")");
let args;
[t, args] = parseCommaSeparatedList(t, ")", parseExpr);
lhs = { kind: "call", span: popen.span, lhs, args };
}
@ -331,20 +346,23 @@ function parseType(t: Token[]): [Token[], Type] {
return [t, { kind: "list", elem, span }];
}
case "(": {
let first = true;
const elems = [];
while (next(t)[1]?.kind !== ")") {
if (!first) {
[t] = expectNext(t, ",");
}
first = false;
let type;
[t, type] = parseType(t);
elems.push(type);
if (next(t)[1]?.kind === ")") {
[t] = next(t);
return [t, { kind: "tuple", elems: [], span }];
}
[t] = expectNext(t, ")");
let head;
[t, head] = parseType(t);
return [t, { kind: "tuple", elems, span }];
if (next(t)[1]?.kind === ")") {
[t] = next(t);
// Just a type inside parens, not a tuple. `(T,)` is a tuple.
return [t, head];
}
let tail;
[t, tail] = parseCommaSeparatedList(t, ")", parseType);
return [t, { kind: "tuple", elems: [head, ...tail], span }];
}
default: {
throw new CompilerError(
@ -357,6 +375,42 @@ function parseType(t: Token[]): [Token[], Type] {
// helpers
function parseCommaSeparatedList<R>(
t: Token[],
terminator: Token["kind"],
parser: Parser<R>
): [Token[], R[]] {
const items: R[] = [];
// () | (a) | (a,) | (a, b)
while (true) {
if (next(t)[1]?.kind === terminator) {
break;
}
let nextValue;
[t, nextValue] = parser(t);
items.push(nextValue);
let comma;
[t, comma] = eat(t, ",");
if (!comma) {
// No comma? Fine, you don't like trailing commas.
// But this better be the end.
if (next(t)[1]?.kind !== terminator) {
unexpectedToken(next(t)[1]);
}
break;
}
}
[t] = expectNext(t, terminator);
return [t, items];
}
function eat<T extends BaseToken>(
t: Token[],
kind: T["kind"]
@ -371,13 +425,13 @@ function eat<T extends BaseToken>(
function expectNext<T extends BaseToken>(
t: Token[],
kind: T["kind"]
): [Token[], T] {
): [Token[], T & Token] {
let tok;
[t, tok] = next(t);
if (tok.kind !== kind) {
throw new CompilerError(`expected ${kind}, found ${tok.kind}`, tok.span);
}
return [t, tok as unknown as T];
return [t, tok as unknown as T & Token];
}
function next(t: Token[]): [Token[], Token] {

View file

@ -7,6 +7,7 @@ import {
Resolution,
Ty,
Type,
TypeDef,
tyIsUnit,
} from "./ast";
@ -19,6 +20,9 @@ function printItem(item: Item): string {
case "function": {
return printFunction(item.node);
}
case "type": {
return printTypeDef(item.node);
}
}
}
@ -27,7 +31,18 @@ function printFunction(func: FunctionDef): string {
.map(({ name, type }) => `${name}: ${printType(type)}`)
.join(", ");
const ret = func.returnType ? `: ${printType(func.returnType)}` : "";
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)}`;
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)};`;
}
function printTypeDef(type: TypeDef): string {
const fields = type.fields.map(
({ name, type }) => `${ind(1)}${name.name}: ${printType(type)},`
);
const fieldPart =
type.fields.length === 0 ? "()" : `(\n${fields.join("\n")}\n)`;
return `type ${type.name} = ${fieldPart};`;
}
function printExpr(expr: Expr, indent: number): string {
@ -165,6 +180,9 @@ export function printTy(ty: Ty): string {
case "var": {
return `?${ty.index}`;
}
case "struct": {
return ty.name;
}
}
}

View file

@ -20,6 +20,7 @@ import {
TY_UNIT,
TyFn,
Type,
TyStruct,
} from "./ast";
import { CompilerError, Span } from "./error";
import { printTy } from "./printer";
@ -61,18 +62,7 @@ function lowerAstTyBase(
): Ty {
switch (type.kind) {
case "ident": {
const res = type.value.res!;
switch (res.kind) {
case "local": {
throw new Error("Cannot resolve local here");
}
case "item": {
return typeOfItem(res.index);
}
case "builtin": {
return builtinAsTy(res.name, type.value.span);
}
}
return lowerIdentTy(type.value);
}
case "list": {
return {
@ -112,6 +102,14 @@ export function typeck(ast: Ast): Ast {
return { kind: "fn", params: args, returnTy };
}
case "type": {
const fields = item.node.fields.map<[string, Ty]>(({ name, type }) => [
name.name,
lowerAstTy(type),
]);
return { kind: "struct", name: item.node.name, fields };
}
}
}
@ -122,7 +120,7 @@ export function typeck(ast: Ast): Ast {
const res = ident.res!;
switch (res.kind) {
case "local": {
throw new Error("Cannot resolve local here");
throw new Error("Item type cannot refer to local variable");
}
case "item": {
return typeOfItem(res.index);
@ -149,7 +147,7 @@ export function typeck(ast: Ast): Ast {
ty: fnTy.returnTy,
};
return {
kind: "function",
...item,
node: {
name: item.node.name,
params: item.node.params.map((arg, i) => ({
@ -160,8 +158,34 @@ export function typeck(ast: Ast): Ast {
returnType,
ty: fnTy,
},
span: item.span,
id: item.id,
};
}
case "type": {
const fieldNames = new Set();
item.node.fields.forEach(({ name }) => {
if (fieldNames.has(name)) {
throw new CompilerError(
`type ${item.node.name} has a duplicate field: ${name.name}`,
name.span
);
}
fieldNames.add(name);
});
const ty = typeOfItem(item.id) as TyStruct;
return {
...item,
node: {
name: item.node.name,
fields: item.node.fields.map((field, i) => ({
name: field.name,
type: {
...field.type,
ty: ty.fields[i][1],
},
})),
},
};
}
}