mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
start implementing module res
This commit is contained in:
parent
2da011caf4
commit
cbbda39688
9 changed files with 389 additions and 95 deletions
76
src/ast.ts
76
src/ast.ts
|
|
@ -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,
|
||||
|
|
|
|||
15
src/index.ts
15
src/index.ts
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
29
src/lexer.ts
29
src/lexer.ts
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
}
|
||||
|
|
|
|||
167
src/resolve.ts
167
src/resolve.ts
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue