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 ItemId = number;
export type ItemKind = { export type ItemKind =
| {
kind: "function"; kind: "function";
node: FunctionDef; node: FunctionDef;
}; }
| {
kind: "type";
node: TypeDef;
};
export type Item = ItemKind & { export type Item = ItemKind & {
span: Span; span: Span;
@ -34,6 +39,17 @@ export type FunctionArg = {
span: Span; 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 ExprEmpty = { kind: "empty" };
export type ExprLet = { export type ExprLet = {
@ -262,7 +278,21 @@ export type TyVar = {
index: number; 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 { export function tyIsUnit(ty: Ty): ty is TyUnit {
return ty.kind === "tuple" && ty.elems.length === 0; return ty.kind === "tuple" && ty.elems.length === 0;
@ -331,6 +361,19 @@ export function superFoldItem(item: Item, folder: Folder): Item {
id: item.id, 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"; import { exec } from "child_process";
const input = ` const input = `
function main() = (); type Uwu = (
function test(i: Int, j: Int): Bool = false == (i == 0); meow: String,
);
function main(a: Int, b: Uwu) = ();
`; `;
function main() { function main() {
@ -37,9 +40,10 @@ function main() {
console.log(resolvedPrinted); console.log(resolvedPrinted);
console.log("-----AST typecked------"); console.log("-----AST typecked------");
const typecked = typeck(resolved); const typecked = typeck(resolved);
return;
console.log("-----wasm--------------"); console.log("-----wasm--------------");
const wasmModule = lowerToWasm(typecked); const wasmModule = lowerToWasm(typecked);
const moduleStringColor = writeModuleWatToString(wasmModule, true); const moduleStringColor = writeModuleWatToString(wasmModule, true);

View file

@ -7,6 +7,7 @@ export type DatalessToken =
| "if" | "if"
| "then" | "then"
| "else" | "else"
| "type"
| "(" | "("
| ")" | ")"
| "[" | "["
@ -209,6 +210,7 @@ const keywords = new Set<string>([
"if", "if",
"then", "then",
"else", "else",
"type",
]); ]);
function isKeyword(kw: string): DatalessToken | undefined { function isKeyword(kw: string): DatalessToken | undefined {
return keywords.has(kw) ? (kw as 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]); return paramAbi(param.elems[0]);
} }
todo("complex tuple abi"); todo("complex tuple abi");
case "struct":
todo("struct ABI");
case "var": case "var":
varUnreachable(); varUnreachable();
} }
@ -333,6 +335,8 @@ function computeAbi(ty: TyFn): Abi {
break; break;
} }
todo("complex tuple abi"); todo("complex tuple abi");
case "struct":
todo("struct ABI");
case "var": case "var":
varUnreachable(); varUnreachable();
} }

View file

@ -5,16 +5,18 @@ import {
BinaryKind, BinaryKind,
COMPARISON_KINDS, COMPARISON_KINDS,
Expr, Expr,
FieldDef,
FunctionArg, FunctionArg,
FunctionDef, FunctionDef,
Item, Item,
LOGICAL_KINDS, LOGICAL_KINDS,
Type, Type,
TypeDef,
UNARY_KINDS, UNARY_KINDS,
UnaryKind, UnaryKind,
} from "./ast"; } from "./ast";
import { CompilerError, Span, todo } from "./error"; 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]; type Parser<T> = (t: Token[]) => [Token[], T];
@ -41,24 +43,16 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, "("); [t] = expectNext(t, "(");
const args: FunctionArg[] = []; let args: FunctionArg[];
let first = true; [t, args] = parseCommaSeparatedList(t, ")", (t) => {
while (next(t)[1]?.kind !== ")") {
if (!first) {
[t] = expectNext(t, ",");
}
first = false;
let name; let name;
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier"); [t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
[t] = expectNext(t, ":"); [t] = expectNext(t, ":");
let type; let type;
[t, type] = parseType(t); [t, type] = parseType(t);
args.push({ name: name.ident, type, span: name.span }); return [t, { name: name.ident, type, span: name.span }];
} });
[t] = expectNext(t, ")");
let colon; let colon;
let returnType = undefined; let returnType = undefined;
@ -91,6 +85,33 @@ function parseItem(t: Token[]): [Token[], Item] {
id: 0, 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 { } else {
unexpectedToken(tok); unexpectedToken(tok);
} }
@ -125,7 +146,7 @@ function parseExpr(t: Token[]): [Token[], Expr] {
if (peak.kind === "let") { if (peak.kind === "let") {
[t] = expectNext(t, "let"); [t] = expectNext(t, "let");
let name; let name;
[t, name] = expectNext<TokenIdent & Token>(t, "identifier"); [t, name] = expectNext<TokenIdent>(t, "identifier");
let type = undefined; let type = undefined;
let colon; let colon;
@ -236,15 +257,9 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
while (next(t)[1].kind === "(") { while (next(t)[1].kind === "(") {
let popen; let popen;
[t, popen] = next(t); [t, popen] = next(t);
const args = [];
while (next(t)[1].kind !== ")") { let args;
let arg; [t, args] = parseCommaSeparatedList(t, ")", parseExpr);
[t, arg] = parseExpr(t);
args.push(arg);
// TODO i think this is incorrect
[t] = eat(t, ",");
}
[t] = expectNext(t, ")");
lhs = { kind: "call", span: popen.span, lhs, args }; lhs = { kind: "call", span: popen.span, lhs, args };
} }
@ -331,20 +346,23 @@ function parseType(t: Token[]): [Token[], Type] {
return [t, { kind: "list", elem, span }]; return [t, { kind: "list", elem, span }];
} }
case "(": { case "(": {
let first = true; if (next(t)[1]?.kind === ")") {
const elems = []; [t] = next(t);
while (next(t)[1]?.kind !== ")") { return [t, { kind: "tuple", elems: [], span }];
if (!first) {
[t] = expectNext(t, ",");
} }
first = false; let head;
let type; [t, head] = parseType(t);
[t, type] = parseType(t);
elems.push(type);
}
[t] = expectNext(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: { default: {
throw new CompilerError( throw new CompilerError(
@ -357,6 +375,42 @@ function parseType(t: Token[]): [Token[], Type] {
// helpers // 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>( function eat<T extends BaseToken>(
t: Token[], t: Token[],
kind: T["kind"] kind: T["kind"]
@ -371,13 +425,13 @@ function eat<T extends BaseToken>(
function expectNext<T extends BaseToken>( function expectNext<T extends BaseToken>(
t: Token[], t: Token[],
kind: T["kind"] kind: T["kind"]
): [Token[], T] { ): [Token[], T & Token] {
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]; return [t, tok as unknown as T & Token];
} }
function next(t: Token[]): [Token[], Token] { function next(t: Token[]): [Token[], Token] {

View file

@ -7,6 +7,7 @@ import {
Resolution, Resolution,
Ty, Ty,
Type, Type,
TypeDef,
tyIsUnit, tyIsUnit,
} from "./ast"; } from "./ast";
@ -19,6 +20,9 @@ function printItem(item: Item): string {
case "function": { case "function": {
return printFunction(item.node); 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)}`) .map(({ name, type }) => `${name}: ${printType(type)}`)
.join(", "); .join(", ");
const ret = func.returnType ? `: ${printType(func.returnType)}` : ""; 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 { function printExpr(expr: Expr, indent: number): string {
@ -165,6 +180,9 @@ export function printTy(ty: Ty): string {
case "var": { case "var": {
return `?${ty.index}`; return `?${ty.index}`;
} }
case "struct": {
return ty.name;
}
} }
} }

View file

@ -20,6 +20,7 @@ import {
TY_UNIT, TY_UNIT,
TyFn, TyFn,
Type, Type,
TyStruct,
} from "./ast"; } from "./ast";
import { CompilerError, Span } from "./error"; import { CompilerError, Span } from "./error";
import { printTy } from "./printer"; import { printTy } from "./printer";
@ -61,18 +62,7 @@ function lowerAstTyBase(
): Ty { ): Ty {
switch (type.kind) { switch (type.kind) {
case "ident": { case "ident": {
const res = type.value.res!; return lowerIdentTy(type.value);
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);
}
}
} }
case "list": { case "list": {
return { return {
@ -112,6 +102,14 @@ export function typeck(ast: Ast): Ast {
return { kind: "fn", params: args, returnTy }; 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!; const res = ident.res!;
switch (res.kind) { switch (res.kind) {
case "local": { case "local": {
throw new Error("Cannot resolve local here"); throw new Error("Item type cannot refer to local variable");
} }
case "item": { case "item": {
return typeOfItem(res.index); return typeOfItem(res.index);
@ -149,7 +147,7 @@ export function typeck(ast: Ast): Ast {
ty: fnTy.returnTy, ty: fnTy.returnTy,
}; };
return { return {
kind: "function", ...item,
node: { node: {
name: item.node.name, name: item.node.name,
params: item.node.params.map((arg, i) => ({ params: item.node.params.map((arg, i) => ({
@ -160,8 +158,34 @@ export function typeck(ast: Ast): Ast {
returnType, returnType,
ty: fnTy, 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],
},
})),
},
}; };
} }
} }