diff --git a/src/ast.ts b/src/ast.ts index 7eeb083..85cbbbb 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -101,6 +101,12 @@ export type ExprIf = { else?: Expr; }; +export type ExprStructLiteral = { + kind: "structLiteral"; + name: Identifier; + fields: [Identifier, Expr][]; +}; + export type ExprKind = | ExprEmpty | ExprLet @@ -110,7 +116,8 @@ export type ExprKind = | ExprBinary | ExprUnary | ExprCall - | ExprIf; + | ExprIf + | ExprStructLiteral; export type Expr = ExprKind & { span: Span; @@ -385,19 +392,19 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr { } case "let": { return { + ...expr, kind: "let", name: expr.name, type: expr.type && folder.type(expr.type), rhs: folder.expr(expr.rhs), after: folder.expr(expr.after), - span, }; } case "block": { return { + ...expr, kind: "block", exprs: expr.exprs.map((expr) => folder.expr(expr)), - span, }; } case "literal": { @@ -408,36 +415,44 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr { } case "binary": { return { + ...expr, kind: "binary", binaryKind: expr.binaryKind, lhs: folder.expr(expr.lhs), rhs: folder.expr(expr.rhs), - span, }; } case "unary": { return { + ...expr, kind: "unary", unaryKind: expr.unaryKind, rhs: folder.expr(expr.rhs), - span, }; } case "call": { return { + ...expr, kind: "call", lhs: folder.expr(expr.lhs), args: expr.args.map((expr) => folder.expr(expr)), - span, }; } case "if": { return { + ...expr, kind: "if", cond: folder.expr(expr.cond), then: folder.expr(expr.then), else: expr.else && folder.expr(expr.else), - span, + }; + } + case "structLiteral": { + return { + ...expr, + kind: "structLiteral", + name: folder.ident(expr.name), + fields: expr.fields.map(([name, expr]) => [name, folder.expr(expr)]), }; } } diff --git a/src/index.ts b/src/index.ts index 9fe71a6..c87a943 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,9 +12,12 @@ import { exec } from "child_process"; const input = ` type Uwu = ( meow: String, + oops: Int, + aaa: (), ); -function main(a: Int, b: Uwu) = (); +function aa(a: Int, b: Uwu): Uwu = Uwu {meow: "",oops:0,aaa:()}; +function main() = (); `; function main() { diff --git a/src/lexer.ts b/src/lexer.ts index bb556a6..a8adbbd 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -10,6 +10,8 @@ export type DatalessToken = | "type" | "(" | ")" + | "{" + | "}" | "[" | "]" | ";" @@ -54,6 +56,8 @@ export type BaseToken = { kind: Token["kind"] }; const SINGLE_PUNCT: string[] = [ "(", ")", + "}", + "{", "[", "]", ";", diff --git a/src/parser.ts b/src/parser.ts index 4ef21bd..b0ac386 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -5,9 +5,11 @@ import { BinaryKind, COMPARISON_KINDS, Expr, + ExprStructLiteral, FieldDef, FunctionArg, FunctionDef, + Identifier, Item, LOGICAL_KINDS, Type, @@ -98,10 +100,16 @@ function parseItem(t: Token[]): [Token[], Item] { [t] = expectNext(t, ":"); let type; [t, type] = parseType(t); - return [t, { name: { - name: name.ident, - span: name.span, - }, type }]; + return [ + t, + { + name: { + name: name.ident, + span: name.span, + }, + type, + }, + ]; }); [t] = expectNext(t, ";"); @@ -137,8 +145,9 @@ function parseExpr(t: Token[]): [Token[], Expr] { CALL = ATOM { "(" EXPR_LIST ")" } - ATOM = "(" { EXPR ";" } EXPR ")" | IDENT | LITERAL | EMPTY + ATOM = "(" { EXPR ";" } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY EMPTY = + STRUCT_INIT = "{" { NAME ":" EXPR } { "," NAME ":" EXPR } { "," } "}" EXPR_LIST = { EXPR { "," EXPR } { "," } } */ const [, peak] = next(t); @@ -309,6 +318,22 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] { } if (tok.kind === "identifier") { + console.log(t); + + if (maybeNextT(t)[1]?.kind === "{") { + let fields; + [t, fields] = parseStructInit(t); + return [ + t, + { + kind: "structLiteral", + name: { name: tok.ident, span }, + fields, + span, + }, + ]; + } + return [ t, { @@ -323,6 +348,23 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] { return [startT, { kind: "empty", span }]; } +function parseStructInit(t: Token[]): [Token[], ExprStructLiteral["fields"]] { + [t] = expectNext(t, "{"); + + let fields; + [t, fields] = parseCommaSeparatedList<[Identifier, Expr]>(t, "}", (t) => { + let name; + [t, name] = expectNext(t, "identifier"); + [t] = expectNext(t, ":"); + let expr; + [t, expr] = parseExpr(t); + + return [t, [{ name: name.ident, span: name.span }, expr]]; + }); + + return [t, fields]; +} + function parseType(t: Token[]): [Token[], Type] { let tok; [t, tok] = next(t); diff --git a/src/printer.ts b/src/printer.ts index 36dace4..d801d0b 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -126,6 +126,11 @@ function printExpr(expr: Expr, indent: number): string { indent + 1 )}${elsePart}`; } + case "structLiteral": { + return `${printIdent(expr.name)} { ${expr.fields + .map(([name, expr]) => `${name.name}: ${printExpr(expr, indent + 1)}`) + .join(", ")} }`; + } } } diff --git a/src/typeck.ts b/src/typeck.ts index f541a1f..1944b94 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -84,15 +84,16 @@ function lowerAstTyBase( export function typeck(ast: Ast): Ast { const itemTys = new Map(); function typeOfItem(index: ItemId): Ty { + const item = ast.items[index]; + const ty = itemTys.get(index); if (ty) { return ty; } if (ty === null) { - throw Error(`cycle computing type of #G${index}`); + throw new CompilerError(`cycle computing type of #G${index}`, item.span); } itemTys.set(index, null); - const item = ast.items[index]; switch (item.kind) { case "function": { const args = item.node.params.map((arg) => lowerAstTy(arg.type)); @@ -100,15 +101,28 @@ export function typeck(ast: Ast): Ast { ? lowerAstTy(item.node.returnType) : TY_UNIT; - return { kind: "fn", params: args, returnTy }; + const ty: Ty = { kind: "fn", params: args, returnTy }; + itemTys.set(item.id, ty); + return ty; } case "type": { + const ty: Ty = { + kind: "struct", + name: item.node.name, + fields: [ + /*dummy*/ + ], + }; + + itemTys.set(item.id, ty); + const fields = item.node.fields.map<[string, Ty]>(({ name, type }) => [ name.name, lowerAstTy(type), ]); - return { kind: "struct", name: item.node.name, fields }; + ty.fields = fields; + return ty; } } } @@ -379,6 +393,11 @@ export function checkBody( } break; } + case "struct": { + if (rhs.kind === "struct" && lhs.name === rhs.name) { + return; + } + } } throw new CompilerError( @@ -516,6 +535,50 @@ export function checkBody( return { ...expr, cond, then, else: elsePart, ty }; } + case "structLiteral": { + const fields = expr.fields.map<[Identifier, Expr]>(([name, expr]) => [ + name, + this.expr(expr), + ]); + + const structTy = typeOf(expr.name.res!, expr.name.span); + + if (structTy.kind !== "struct") { + throw new CompilerError( + `struct literal is only allowed for struct types`, + expr.span + ); + } + + const assignedFields = new Set(); + + fields.forEach(([name, field], i) => { + const fieldTy = structTy.fields.find((def) => def[0] === name.name); + if (!fieldTy) { + throw new CompilerError( + `field ${name.name} doesn't exist on type ${expr.name.name}`, + name.span + ); + } + assign(fieldTy[1], field.ty!, field.span); + assignedFields.add(name.name); + }); + + const missing: string[] = []; + structTy.fields.forEach(([name]) => { + if (!assignedFields.has(name)) { + missing.push(name); + } + }); + if (missing.length > 0) { + throw new CompilerError( + `missing fields in literal: ${missing.join(", ")}`, + expr.span + ); + } + + return { ...expr, fields, ty: structTy }; + } } }, };