diff --git a/README.md b/README.md new file mode 100644 index 0000000..58603af --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# a random language compiling to wasm + +The language is not well designed. It's just random garbage. diff --git a/src/ast.ts b/src/ast.ts index 5ba6189..7eeb083 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -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, + }; + } } } diff --git a/src/index.ts b/src/index.ts index 5db9908..9fe71a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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); diff --git a/src/lexer.ts b/src/lexer.ts index 96a8821..bb556a6 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -7,6 +7,7 @@ export type DatalessToken = | "if" | "then" | "else" + | "type" | "(" | ")" | "[" @@ -209,6 +210,7 @@ const keywords = new Set([ "if", "then", "else", + "type", ]); function isKeyword(kw: string): DatalessToken | undefined { return keywords.has(kw) ? (kw as DatalessToken) : undefined; diff --git a/src/lower.ts b/src/lower.ts index 7781c91..377a2d8 100644 --- a/src/lower.ts +++ b/src/lower.ts @@ -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(); } diff --git a/src/parser.ts b/src/parser.ts index c8c4fbd..4ef21bd 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -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: 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(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(t, "identifier"); + [t] = expectNext(t, "="); + [t] = expectNext(t, "("); + + let fields; + [t, fields] = parseCommaSeparatedList(t, ")", (t) => { + let name; + [t, name] = expectNext(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(t, "identifier"); + [t, name] = expectNext(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( + t: Token[], + terminator: Token["kind"], + parser: Parser +): [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: Token[], kind: T["kind"] @@ -371,13 +425,13 @@ function eat( function expectNext( 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] { diff --git a/src/printer.ts b/src/printer.ts index 8e34be4..36dace4 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -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; + } } } diff --git a/src/typeck.ts b/src/typeck.ts index f08b7f5..f541a1f 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -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], + }, + })), + }, }; } }