start implementing module res

This commit is contained in:
nora 2023-07-31 13:06:28 +02:00
parent 2da011caf4
commit cbbda39688
9 changed files with 389 additions and 95 deletions

View file

@ -1,7 +1,11 @@
import { Span } from "./error";
import { LitIntType } from "./lexer";
export type Ast = { items: Item[]; typeckResults?: TypeckResults };
export type Ast = {
rootItems: Item[];
typeckResults?: TypeckResults;
itemsById: Map<ItemId, Item>;
};
export type Identifier = {
name: string;
@ -23,6 +27,10 @@ export type ItemKind =
| {
kind: "import";
node: ImportDef;
}
| {
kind: "mod";
node: ModItem;
};
export type Item = ItemKind & {
@ -64,6 +72,20 @@ export type ImportDef = {
ty?: TyFn;
};
export type ModItem = {
name: string;
modKind: ModItemKind;
};
export type ModItemKind =
| {
kind: "inline";
contents: Item[];
}
| {
kind: "extern";
};
export type ExprEmpty = { kind: "empty" };
export type ExprLet = {
@ -101,6 +123,20 @@ export type ExprIdent = {
value: Identifier;
};
/**
* `a.b.c` in source code where `a` and `b` are modules.
* This expression is not parsed, but fieldAccess gets converted
* to path expressions in resolve.
*/
export type ExprPath = {
kind: "path";
segments: string[];
/**
* Since this only exists after resolve, we always have a res.
*/
res: Resolution;
};
export type ExprBinary = {
kind: "binary";
binaryKind: BinaryKind;
@ -168,6 +204,7 @@ export type ExprKind =
| ExprBlock
| ExprLiteral
| ExprIdent
| ExprPath
| ExprBinary
| ExprUnary
| ExprCall
@ -291,12 +328,7 @@ export type Resolution =
}
| {
kind: "item";
/**
* Items are numbered in the order they appear in.
* Right now we only have one scope of items (global)
* so this is enough.
*/
index: number;
id: ItemId;
}
| {
kind: "builtin";
@ -438,7 +470,8 @@ export const DEFAULT_FOLDER: Folder = {
export function foldAst(ast: Ast, folder: Folder): Ast {
return {
items: ast.items.map((item) => folder.item(item)),
rootItems: ast.rootItems.map((item) => folder.item(item)),
itemsById: ast.itemsById,
typeckResults: ast.typeckResults,
};
}
@ -493,6 +526,30 @@ export function superFoldItem(item: Item, folder: Folder): Item {
},
};
}
case "mod": {
let kind: ModItemKind;
const { modKind: itemKind } = item.node;
switch (itemKind.kind) {
case "inline":
kind = {
kind: "inline",
contents: itemKind.contents.map((item) => folder.item(item)),
};
break;
case "extern":
kind = { kind: "extern" };
break;
}
return {
...item,
kind: "mod",
node: {
name: item.node.name,
modKind: kind,
},
};
}
}
}
@ -532,6 +589,9 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr {
case "ident": {
return { kind: "ident", value: folder.ident(expr.value), span };
}
case "path": {
return { ...expr, kind: "path" };
}
case "binary": {
return {
...expr,

View file

@ -11,12 +11,13 @@ import { exec } from "child_process";
const INPUT = `
function main() = (
printTuples(("hello, ", "world\\n"));
owo.uwu.meow();
);
function printTuples(a: (String, String)) = (
print(a.0);
print(a.1);
mod owo (
mod uwu (
function meow() =;
);
);
`;
@ -38,14 +39,14 @@ function main() {
const ast = parse(tokens);
console.log("-----AST---------------");
console.dir(ast, { depth: 50 });
console.dir(ast.rootItems, { depth: 50 });
const printed = printAst(ast);
console.log("-----AST pretty--------");
const printed = printAst(ast);
console.log(printed);
const resolved = resolve(ast);
console.log("-----AST resolved------");
const resolved = resolve(ast);
const resolvedPrinted = printAst(resolved);
console.log(resolvedPrinted);

View file

@ -10,6 +10,8 @@ export type DatalessToken =
| "loop"
| "break"
| "import"
| "extern"
| "mod"
| "("
| ")"
| "{"
@ -90,13 +92,24 @@ export function tokenize(input: string): Token[] {
const next = input[i];
const span: Span = { start: i, end: i + 1 };
if (next === "/" && input[i + 1] === "/") {
if (next === "/" && input[i + 1] === "/") {
while (input[i] !== "\n") {
i++;
}
continue;
}
}
if (next === "/" && input[i + 1] === "*") {
i++;
i++;
while (input[i] !== "*" && input[i + 1] !== "/") {
i++;
if (input[i] === undefined) {
throw new CompilerError("unterminated block comment", span);
}
}
}
if (SINGLE_PUNCT.includes(next)) {
tokens.push({ kind: next as DatalessToken, span });
@ -218,14 +231,14 @@ export function tokenize(input: string): Token[] {
}
let type: LitIntType = "Int";
console.log(input[i + 2]);
console.log(input[i + 2]);
if (input[i + 1] === "_" && isIdentStart(input[i + 2])) {
console.log("yes", input.slice(i+2, i+5));
if (input.slice(i+2, i+5) === "Int") {
console.log("yes", input.slice(i + 2, i + 5));
if (input.slice(i + 2, i + 5) === "Int") {
i += 4;
type = "Int";
} else if (input.slice(i+2, i+5) === "I32") {
} else if (input.slice(i + 2, i + 5) === "I32") {
i += 4;
type = "I32";
}
@ -292,6 +305,8 @@ const KEYOWRDS: DatalessToken[] = [
"loop",
"break",
"import",
"extern",
"mod",
];
const KEYWORD_SET = new Set<string>(KEYOWRDS);

View file

@ -128,7 +128,7 @@ export function lower(ast: Ast): wasm.Module {
relocations: [],
};
ast.items.forEach((item) => {
ast.rootItems.forEach((item) => {
switch (item.kind) {
case "function": {
lowerFunc(cx, item, item.node);
@ -195,7 +195,7 @@ function lowerImport(cx: Context, item: Item, def: ImportDef) {
setMap<Resolution, FuncOrImport>(
cx.funcIndices,
{ kind: "item", index: item.id },
{ kind: "item", id: item.id },
{ kind: "import", idx }
);
}
@ -246,7 +246,7 @@ function lowerFunc(cx: Context, item: Item, func: FunctionDef) {
fcx.cx.mod.funcs.push(wasmFunc);
setMap<Resolution, FuncOrImport>(
fcx.cx.funcIndices,
{ kind: "item", index: fcx.item.id },
{ kind: "item", id: fcx.item.id },
{ kind: "func", idx }
);
}
@ -382,6 +382,9 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
break;
}
case "path": {
todo("path");
}
case "binary": {
// By evaluating the LHS first, the RHS is on top, which
// is correct as it's popped first. Evaluating the LHS first

View file

@ -16,6 +16,7 @@ import {
ImportDef,
Item,
LOGICAL_KINDS,
ModItem,
Type,
TypeDef,
UNARY_KINDS,
@ -23,6 +24,7 @@ import {
binaryExprPrecedenceClass,
foldAst,
superFoldExpr,
superFoldItem,
} from "./ast";
import { CompilerError, Span, spanMerge } from "./error";
import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer";
@ -39,7 +41,7 @@ export function parse(t: Token[]): Ast {
items.push(item);
}
const ast = assignIds({ items: items });
const ast = assignIds(items);
validateAst(ast);
@ -128,6 +130,43 @@ function parseItem(t: Token[]): [Token[], Item] {
};
return [t, { kind: "import", node: def, span: tok.span, id: 0 }];
} else if (tok.kind === "extern") {
[t] = expectNext(t, "mod");
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
const node: ModItem = {
name: name.ident,
modKind: { kind: "extern" },
};
[t] = expectNext(t, ";");
return [t, { kind: "mod", node, span: name.span, id: 0 }];
} else if (tok.kind === "mod") {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, "(");
const contents: Item[] = [];
while (next(t)[1].kind !== ")") {
let item;
[t, item] = parseItem(t);
contents.push(item);
}
[t] = expectNext(t, ")");
[t] = expectNext(t, ";");
const node: ModItem = {
name: name.ident,
modKind: { kind: "inline", contents },
};
return [t, { kind: "mod", node, span: name.span, id: 0 }];
} else {
unexpectedToken(tok, "item");
}
@ -615,8 +654,17 @@ function unexpectedToken(token: Token, expected: string): never {
}
function validateAst(ast: Ast) {
const seenItemIds = new Set();
const validator: Folder = {
...DEFAULT_FOLDER,
item(item: Item): Item {
if (seenItemIds.has(item.id)) {
throw new Error(`duplicate item id: ${item.id} for ${item.node.name}`);
}
seenItemIds.add(item.id);
return superFoldItem(item, this);
},
expr(expr: Expr): Expr {
if (expr.kind === "block") {
expr.exprs.forEach((inner) => {
@ -660,13 +708,22 @@ function validateAst(ast: Ast) {
foldAst(ast, validator);
}
function assignIds(ast: Ast): Ast {
let loopId = new Ids();
function assignIds(rootItems: Item[]): Ast {
const itemId = new Ids();
const loopId = new Ids();
const astItems = { items: ast.items.map((item, i) => ({ ...item, id: i })) };
const ast: Ast = {
rootItems,
itemsById: new Map(),
};
const assigner: Folder = {
...DEFAULT_FOLDER,
item(item: Item): Item {
const id = itemId.next();
ast.itemsById.set(id, item);
return { ...superFoldItem(item, this), id };
},
expr(expr: Expr): Expr {
if (expr.kind === "loop") {
return {
@ -678,5 +735,5 @@ function assignIds(ast: Ast): Ast {
},
};
return foldAst(astItems, assigner);
return foldAst(ast, assigner);
}

View file

@ -5,6 +5,7 @@ import {
Identifier,
ImportDef,
Item,
ModItem,
Resolution,
StringLiteral,
Ty,
@ -14,7 +15,7 @@ import {
} from "./ast";
export function printAst(ast: Ast): string {
return ast.items.map(printItem).join("\n");
return ast.rootItems.map(printItem).join("\n");
}
function printStringLiteral(lit: StringLiteral): string {
@ -22,15 +23,20 @@ function printStringLiteral(lit: StringLiteral): string {
}
function printItem(item: Item): string {
const id = `/*${item.id}*/ `;
switch (item.kind) {
case "function": {
return printFunction(item.node);
return id + printFunction(item.node);
}
case "type": {
return printTypeDef(item.node);
return id + printTypeDef(item.node);
}
case "import": {
return printImportDef(item.node);
return id + printImportDef(item.node);
}
case "mod": {
return id +printMod(item.node);
}
}
}
@ -65,6 +71,17 @@ function printImportDef(def: ImportDef): string {
)}(${args})${ret};`;
}
function printMod(mod: ModItem): string {
switch (mod.modKind.kind) {
case "inline":
return `mod ${mod.name} (\n${mod.modKind.contents
.map(printItem)
.join("\n ")});`;
case "extern":
return `extern mod ${mod.name};`;
}
}
function printExpr(expr: Expr, indent: number): string {
switch (expr.kind) {
case "empty": {
@ -117,6 +134,9 @@ function printExpr(expr: Expr, indent: number): string {
case "ident": {
return printIdent(expr.value);
}
case "path": {
return `<${expr.segments.join(".")}>${printRes(expr.res)}`;
}
case "binary": {
return `${printExpr(expr.lhs, indent)} ${expr.binaryKind} ${printExpr(
expr.rhs,
@ -185,18 +205,19 @@ function printType(type: Type): string {
}
}
function printIdent(ident: Identifier): string {
const printRes = (res: Resolution): string => {
switch (res.kind) {
case "local":
return `#${res.index}`;
case "item":
return `#G${res.index}`;
case "builtin": {
return `#B`;
}
function printRes(res: Resolution): string {
switch (res.kind) {
case "local":
return `#${res.index}`;
case "item":
return `#G${res.id}`;
case "builtin": {
return `#B`;
}
};
}
}
function printIdent(ident: Identifier): string {
const res = ident.res ? printRes(ident.res) : "";
return `${ident.name}${res}`;
}

View file

@ -6,21 +6,60 @@ import {
Expr,
Folder,
Identifier,
Item,
ItemId,
LocalInfo,
ModItem,
Resolution,
foldAst,
superFoldExpr,
superFoldItem,
} from "./ast";
import { CompilerError } from "./error";
import { CompilerError, spanMerge, todo } from "./error";
import { unwrap } from "./utils";
const BUILTIN_SET = new Set<string>(BUILTINS);
type Context = {
ast: Ast;
modContentsCache: Map<ItemId, Map<string, ItemId>>;
};
function resolveModItem(
cx: Context,
mod: ModItem,
modId: ItemId,
name: string
): ItemId | undefined {
const cachedContents = cx.modContentsCache.get(modId);
if (cachedContents) {
return cachedContents.get(name);
}
switch (mod.modKind.kind) {
case "inline": {
const contents = new Map(
mod.modKind.contents.map((item) => [item.node.name, item.id])
);
cx.modContentsCache.set(modId, contents);
return contents.get(name);
}
case "extern": {
todo("extern mod items");
}
}
}
export function resolve(ast: Ast): Ast {
const cx: Context = { ast, modContentsCache: new Map() };
const rootItems = resolveModule(cx, ast.rootItems);
return { ...ast, rootItems };
}
function resolveModule(cx: Context, contents: Item[]): Item[] {
const items = new Map<string, number>();
for (let i = 0; i < ast.items.length; i++) {
const item = ast.items[i];
contents.forEach((item) => {
const existing = items.get(item.node.name);
if (existing !== undefined) {
throw new CompilerError(
@ -28,8 +67,8 @@ export function resolve(ast: Ast): Ast {
item.span
);
}
items.set(item.node.name, i);
}
items.set(item.node.name, item.id);
});
const scopes: string[] = [];
@ -59,7 +98,7 @@ export function resolve(ast: Ast): Ast {
if (item !== undefined) {
return {
kind: "item",
index: item,
id: item,
};
}
@ -103,43 +142,99 @@ export function resolve(ast: Ast): Ast {
id: item.id,
};
}
case "mod": {
if (item.node.modKind.kind === "inline") {
const contents = resolveModule(cx, item.node.modKind.contents);
return {
...item,
kind: "mod",
node: { ...item.node, modKind: { kind: "inline", contents } },
};
}
break;
}
}
return superFoldItem(item, this);
},
expr(expr) {
if (expr.kind === "block") {
const prevScopeLength = scopes.length;
blockLocals.push([]);
switch (expr.kind) {
case "block": {
const prevScopeLength = scopes.length;
blockLocals.push([]);
const exprs = expr.exprs.map<Expr>((inner) => this.expr(inner));
const exprs = expr.exprs.map<Expr>((inner) => this.expr(inner));
scopes.length = prevScopeLength;
const locals = blockLocals.pop();
scopes.length = prevScopeLength;
const locals = blockLocals.pop();
return {
kind: "block",
exprs,
locals,
span: expr.span,
};
} else if (expr.kind === "let") {
let rhs = this.expr(expr.rhs);
let type = expr.type && this.type(expr.type);
return {
kind: "block",
exprs,
locals,
span: expr.span,
};
}
case "let": {
let rhs = this.expr(expr.rhs);
let type = expr.type && this.type(expr.type);
scopes.push(expr.name.name);
const local = { name: expr.name.name, span: expr.name.span };
blockLocals[blockLocals.length - 1].push(local);
scopes.push(expr.name.name);
const local = { name: expr.name.name, span: expr.name.span };
blockLocals[blockLocals.length - 1].push(local);
return {
...expr,
name: expr.name,
local,
type,
rhs,
};
} else {
return superFoldExpr(expr, this);
return {
...expr,
name: expr.name,
local,
type,
rhs,
};
}
case "fieldAccess": {
if (expr.lhs.kind === "ident") {
// If the lhs is a module we need to convert this into a path.
const res = resolveIdent(expr.lhs.value);
if (res.kind === "item") {
const module = unwrap(cx.ast.itemsById.get(res.id));
if (module.kind === "mod") {
if (typeof expr.field.value === "number") {
throw new CompilerError(
"module contents cannot be indexed with a number",
expr.field.span
);
}
const pathResItem = resolveModItem(
cx,
module.node,
module.id,
expr.field.value
);
if (pathResItem === undefined) {
throw new CompilerError(
`module ${module.node.name} has no item ${expr.field.value}`,
expr.field.span
);
}
const pathRes: Resolution = { kind: "item", id: pathResItem };
return {
kind: "path",
segments: [expr.lhs.value.name, expr.field.value],
res: pathRes,
span: spanMerge(expr.lhs.span, expr.field.span),
};
}
}
}
return superFoldExpr(expr, this);
}
default: {
return superFoldExpr(expr, this);
}
}
},
ident(ident) {
@ -148,7 +243,5 @@ export function resolve(ast: Ast): Ast {
},
};
const resolved = foldAst(ast, resolver);
return resolved;
return contents.map((item) => resolver.item(item));
}

View file

@ -14,6 +14,7 @@ import {
ItemId,
LOGICAL_KINDS,
LoopId,
ModItemKind,
Resolution,
Ty,
TY_BOOL,
@ -29,6 +30,7 @@ import {
} from "./ast";
import { CompilerError, Span } from "./error";
import { printTy } from "./printer";
import { unwrap } from "./utils";
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
return { kind: "fn", params, returnTy };
@ -81,10 +83,11 @@ function typeOfBuiltinValue(name: BuiltinName, span: Span): Ty {
}
}
// TODO: Cleanup, maybe get the ident switch into this function because typeOfItem is unused.
function lowerAstTyBase(
type: Type,
lowerIdentTy: (ident: Identifier) => Ty,
typeOfItem: (index: number) => Ty
typeOfItem: (index: number, cause: Span) => Ty
): Ty {
switch (type.kind) {
case "ident": {
@ -112,8 +115,8 @@ function lowerAstTyBase(
export function typeck(ast: Ast): Ast {
const itemTys = new Map<number, Ty | null>();
function typeOfItem(index: ItemId): Ty {
const item = ast.items[index];
function typeOfItem(index: ItemId, cause: Span): Ty {
const item = unwrap(ast.itemsById.get(index));
const ty = itemTys.get(index);
if (ty) {
@ -154,6 +157,12 @@ export function typeck(ast: Ast): Ast {
ty.fields = fields;
return ty;
}
case "mod": {
throw new CompilerError(
`module ${item.node.name} is not a type`,
cause
);
}
}
}
@ -167,7 +176,7 @@ export function typeck(ast: Ast): Ast {
throw new Error("Item type cannot refer to local variable");
}
case "item": {
return typeOfItem(res.index);
return typeOfItem(res.id, type.span);
}
case "builtin": {
return builtinAsTy(res.name, ident.span);
@ -183,7 +192,7 @@ export function typeck(ast: Ast): Ast {
item(item) {
switch (item.kind) {
case "function": {
const fnTy = typeOfItem(item.id) as TyFn;
const fnTy = typeOfItem(item.id, item.span) as TyFn;
const body = checkBody(item.node.body, fnTy, typeOfItem);
const returnType = item.node.returnType && {
@ -205,7 +214,7 @@ export function typeck(ast: Ast): Ast {
};
}
case "import": {
const fnTy = typeOfItem(item.id) as TyFn;
const fnTy = typeOfItem(item.id, item.span) as TyFn;
fnTy.params.forEach((param, i) => {
switch (param.kind) {
@ -268,7 +277,7 @@ export function typeck(ast: Ast): Ast {
fieldNames.add(name);
});
const ty = typeOfItem(item.id) as TyStruct;
const ty = typeOfItem(item.id, item.span) as TyStruct;
return {
...item,
@ -284,13 +293,38 @@ export function typeck(ast: Ast): Ast {
},
};
}
case "mod": {
switch (item.node.modKind.kind) {
case "inline":
const modKind: ModItemKind = {
kind: "inline",
contents: item.node.modKind.contents.map((item) =>
this.item(item)
),
};
return {
...item,
node: {
...item.node,
modKind,
},
};
case "extern":
// Nothing to check.
return {
...item,
node: { ...item.node, modKind: { ...item.node.modKind } },
};
}
}
}
},
};
const typecked = foldAst(ast, checker);
const main = typecked.items.find((item) => {
const main = typecked.rootItems.find((item) => {
if (item.kind === "function" && item.node.name === "main") {
const func = item.node;
if (func.returnType !== undefined) {
@ -316,7 +350,7 @@ export function typeck(ast: Ast): Ast {
}
typecked.typeckResults = {
main: { kind: "item", index: main.id },
main: { kind: "item", id: main.id },
};
return typecked;
@ -483,7 +517,7 @@ export class InferContext {
export function checkBody(
body: Expr,
fnTy: TyFn,
typeOfItem: (index: number) => Ty
typeOfItem: (index: number, cause: Span) => Ty
): Expr {
const localTys = [...fnTy.params];
const loopState: { hasBreak: boolean; loopId: LoopId }[] = [];
@ -497,7 +531,7 @@ export function checkBody(
return localTys[idx];
}
case "item": {
return typeOfItem(res.index);
return typeOfItem(res.id, span);
}
case "builtin":
return typeOfBuiltinValue(res.name, span);
@ -515,7 +549,7 @@ export function checkBody(
return localTys[idx];
}
case "item": {
return typeOfItem(res.index);
return typeOfItem(res.id, type.span);
}
case "builtin":
return builtinAsTy(res.name, ident.span);
@ -629,6 +663,10 @@ export function checkBody(
return { ...expr, ty };
}
case "path": {
const ty = typeOf(expr.res, expr.span);
return { ...expr, ty };
}
case "binary": {
const lhs = this.expr(expr.lhs);
const rhs = this.expr(expr.rhs);
@ -731,7 +769,7 @@ export function checkBody(
}
default: {
throw new CompilerError(
"only tuples and structs have fields",
`cannot access field \`${field.value}\` on type \`${printTy(lhs.ty)}\``,
expr.span
);
}