This commit is contained in:
nora 2023-07-30 16:54:04 +02:00
parent 50e82066c9
commit 6d2a2fe474
9 changed files with 443 additions and 98 deletions

View file

@ -1,4 +1,5 @@
import { Span } from "./error";
import { LitIntType } from "./lexer";
export type Ast = { items: Item[]; typeckResults?: TypeckResults };
@ -18,6 +19,10 @@ export type ItemKind =
| {
kind: "type";
node: TypeDef;
}
| {
kind: "import";
node: ImportDef;
};
export type Item = ItemKind & {
@ -50,6 +55,15 @@ export type FieldDef = {
type: Type;
};
export type ImportDef = {
module: StringLiteral;
func: StringLiteral;
name: string;
params: FunctionArg[];
returnType?: Type;
ty?: TyFn;
};
export type ExprEmpty = { kind: "empty" };
export type ExprLet = {
@ -143,14 +157,18 @@ export type Expr = ExprKind & {
ty?: Ty;
};
export type StringLiteral = {
kind: "str";
value: string;
span: Span;
};
export type Literal =
| {
kind: "str";
value: string;
}
| StringLiteral
| {
kind: "int";
value: number;
type: LitIntType;
};
export type BinaryKind =
@ -265,6 +283,13 @@ export const BUILTINS = [
"Bool",
"true",
"false",
// Intrinsics:
"__i32_store",
"__i64_store",
"__i32_load",
"__i64_load",
"__string_ptr",
"__string_len",
] as const;
export type BuiltinName = (typeof BUILTINS)[number];
@ -399,15 +424,14 @@ export function superFoldItem(item: Item, folder: Folder): Item {
}));
return {
...item,
kind: "function",
span: item.span,
node: {
name: item.node.name,
params: args,
body: folder.expr(item.node.body),
returnType: item.node.returnType && folder.type(item.node.returnType),
},
id: item.id,
};
}
case "type": {
@ -417,10 +441,27 @@ export function superFoldItem(item: Item, folder: Folder): Item {
}));
return {
...item,
kind: "type",
span: item.span,
node: { name: item.node.name, fields },
id: item.id,
};
}
case "import": {
const args = item.node.params.map(({ name, type, span }) => ({
name,
type: folder.type(type),
span,
}));
return {
...item,
kind: "import",
node: {
module: item.node.module,
func: item.node.func,
name: item.node.name,
params: args,
returnType: item.node.returnType && folder.type(item.node.returnType),
},
};
}
}

View file

@ -10,20 +10,32 @@ import fs from "fs";
import { exec } from "child_process";
const input = `
// import "wasi_snapshot_preview1" "fd_write"(a: I32, b: I32, c: I32, d: I32): I32;
import("wasi_snapshot_preview1" "fd_write")
fd_write(a: I32, b: I32, c: I32, d: I32): I32;
function main() = (
loop (no(break););
uwu(10);
function coolerPrint(a: String) = (
let ptr = __string_ptr(a);
let len = __string_len(a);
let mem = 1024_I32;
__i32_store(mem + 4_I32, ptr);
__i32_store(mem + 8_I32, 2_I32);
fd_write(
// stdout
1_I32,
// iovec
mem + 4_I32,
// len
len,
// return value
mem,
);
);
function meow(a: I32): I32 = a;
function no(a: !): String = a;
function uwu(a: Int) = if a != 0 then (
print("uwu\n");
uwu(a - 1);
function main() = (
coolerPrint("uwu\\n");
);
`;

View file

@ -9,6 +9,7 @@ export type DatalessToken =
| "type"
| "loop"
| "break"
| "import"
| "("
| ")"
| "{"
@ -36,14 +37,19 @@ export type DatalessToken =
export type TokenIdent = { kind: "identifier"; ident: string };
export type TokenLitString = {
kind: "lit_string";
value: string;
};
export type LitIntType = "Int" | "I32";
export type TokenLit =
| {
kind: "lit_string";
value: string;
}
| TokenLitString
| {
kind: "lit_int";
value: number;
type: LitIntType;
};
export type TokenKind = { kind: DatalessToken } | TokenIdent | TokenLit;
@ -80,13 +86,13 @@ 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 (SINGLE_PUNCT.includes(next)) {
tokens.push({ kind: next as DatalessToken, span });
@ -207,7 +213,21 @@ export function tokenize(input: string): Token[] {
);
}
tokens.push({ kind: "lit_int", value: int, span });
let type: LitIntType = "Int";
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") {
i += 4;
type = "Int";
} else if (input.slice(i+2, i+5) === "I32") {
i += 4;
type = "I32";
}
}
tokens.push({ kind: "lit_int", value: int, span, type });
} else if (isIdentStart(next)) {
while (isIdentContinue(input[i + 1])) {
span.end++;
@ -267,6 +287,7 @@ const KEYOWRDS: DatalessToken[] = [
"type",
"loop",
"break",
"import",
];
const KEYWORD_SET = new Set<string>(KEYOWRDS);

View file

@ -3,6 +3,7 @@ import {
Expr,
ExprBlock,
FunctionDef,
ImportDef,
Item,
Resolution,
Ty,
@ -12,8 +13,6 @@ import {
import { encodeUtf8 } from "./utils";
import * as wasm from "./wasm/defs";
type StringifiedForMap<T> = string;
const USIZE: wasm.ValType = "i32";
// POINTERS ARE JUST INTEGERS
const POINTER: wasm.ValType = USIZE;
@ -28,22 +27,25 @@ type Relocation = {
instr: wasm.Instr & { func: wasm.FuncIdx };
} & { res: Resolution };
function setMap<K, V>(map: Map<StringifiedForMap<K>, V>, key: K, value: V) {
map.set(JSON.stringify(key), value);
type StringifiedMap<K, V> = { _map: Map<string, V> };
function setMap<K, V>(map: StringifiedMap<K, V>, key: K, value: V) {
map._map.set(JSON.stringify(key), value);
}
function getMap<K, V>(
map: Map<StringifiedForMap<K>, V>,
key: K
): V | undefined {
return map.get(JSON.stringify(key));
function getMap<K, V>(map: StringifiedMap<K, V>, key: K): V | undefined {
return map._map.get(JSON.stringify(key));
}
type FuncOrImport =
| { kind: "func"; idx: wasm.FuncIdx }
| { kind: "import"; idx: number };
export type Context = {
mod: wasm.Module;
funcTypes: Map<StringifiedForMap<wasm.FuncType>, wasm.TypeIdx>;
funcTypes: StringifiedMap<wasm.FuncType, wasm.TypeIdx>;
reservedHeapMemoryStart: number;
funcIndices: Map<StringifiedForMap<Resolution>, wasm.FuncIdx>;
funcIndices: StringifiedMap<Resolution, FuncOrImport>;
ast: Ast;
relocations: Relocation[];
};
@ -117,8 +119,8 @@ export function lower(ast: Ast): wasm.Module {
const cx: Context = {
mod,
funcTypes: new Map(),
funcIndices: new Map(),
funcTypes: { _map: new Map() },
funcIndices: { _map: new Map() },
reservedHeapMemoryStart: 0,
ast,
relocations: [],
@ -128,6 +130,11 @@ export function lower(ast: Ast): wasm.Module {
switch (item.kind) {
case "function": {
lowerFunc(cx, item, item.node);
break;
}
case "import": {
lowerImport(cx, item, item.node);
break;
}
}
});
@ -145,13 +152,13 @@ export function lower(ast: Ast): wasm.Module {
cx.relocations.forEach((rel) => {
switch (rel.kind) {
case "funccall": {
const idx = getMap<Resolution, number>(cx.funcIndices, rel.res);
const idx = getMap<Resolution, FuncOrImport>(cx.funcIndices, rel.res);
if (idx === undefined) {
throw new Error(
`no function found for relocation '${JSON.stringify(rel.res)}'`
);
}
rel.instr.func = offset + idx;
rel.instr.func = idx.kind === "func" ? offset + idx.idx : idx.idx;
}
}
});
@ -160,6 +167,37 @@ export function lower(ast: Ast): wasm.Module {
return mod;
}
function lowerImport(cx: Context, item: Item, def: ImportDef) {
const existing = cx.mod.imports.findIndex(
(imp) => imp.module === def.module.value && imp.name === def.func.value
);
let idx;
if (existing !== -1) {
idx = existing;
} else {
const abi = computeAbi(def.ty!);
const { type: wasmType } = wasmTypeForAbi(abi);
const type = internFuncType(cx, wasmType);
idx = cx.mod.imports.length;
cx.mod.imports.push({
module: def.module.value,
name: def.func.value,
desc: {
kind: "func",
type,
},
});
}
setMap<Resolution, FuncOrImport>(
cx.funcIndices,
{ kind: "item", index: item.id },
{ kind: "import", idx }
);
}
type FuncContext = {
cx: Context;
item: Item;
@ -198,10 +236,10 @@ function lowerFunc(cx: Context, item: Item, func: FunctionDef) {
const idx = fcx.cx.mod.funcs.length;
fcx.cx.mod.funcs.push(wasmFunc);
setMap<Resolution, number>(
setMap<Resolution, FuncOrImport>(
fcx.cx.funcIndices,
{ kind: "item", index: fcx.item.id },
idx
{ kind: "func", idx }
);
}
@ -213,7 +251,7 @@ Expression lowering.
function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
const ty = expr.ty!;
switch (expr.kind) {
exprKind: switch (expr.kind) {
case "empty": {
// A ZST, do nothing.
return;
@ -265,7 +303,14 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
break;
case "int":
instrs.push({ kind: "i64.const", imm: expr.value.value });
switch (expr.value.type) {
case "Int":
instrs.push({ kind: "i64.const", imm: expr.value.value });
break;
case "I32":
instrs.push({ kind: "i32.const", imm: expr.value.value });
break;
}
break;
}
break;
@ -306,49 +351,55 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
lowerExpr(fcx, instrs, expr.lhs);
lowerExpr(fcx, instrs, expr.rhs);
if (expr.lhs.ty!.kind === "int" && expr.rhs.ty!.kind === "int") {
const lhsTy = expr.lhs.ty!;
const rhsTy = expr.rhs.ty!;
if (
(lhsTy.kind === "int" && rhsTy.kind === "int") ||
(lhsTy.kind === "i32" && rhsTy.kind === "i32")
) {
let kind: wasm.Instr["kind"];
const valty = lhsTy.kind === "int" ? "i64" : "i32";
switch (expr.binaryKind) {
case "+":
kind = "i64.add";
kind = `${valty}.add`;
break;
case "-":
kind = "i64.sub";
kind = `${valty}.sub`;
break;
case "*":
kind = "i64.mul";
kind = `${valty}.mul`;
break;
case "/":
kind = "i64.div_u";
kind = `${valty}.div_u`;
break;
case "&":
kind = "i64.and";
kind = `${valty}.and`;
break;
case "|":
kind = "i64.or";
kind = `${valty}.or`;
break;
case "<":
kind = "i64.lt_u";
kind = `${valty}.lt_u`;
break;
case ">":
kind = "i64.gt_u";
kind = `${valty}.gt_u`;
// errs
break;
case "==":
kind = "i64.eq";
kind = `${valty}.eq`;
break;
case "<=":
kind = "i64.le_u";
kind = `${valty}.le_u`;
break;
case ">=":
kind = "i64.ge_u";
kind = `${valty}.ge_u`;
break;
case "!=":
kind = "i64.ne";
kind = `${valty}.ne`;
break;
}
instrs.push({ kind });
} else if (expr.lhs.ty!.kind === "bool" && expr.rhs.ty!.kind === "bool") {
} else if (lhsTy.kind === "bool" && rhsTy.kind === "bool") {
let kind: wasm.Instr["kind"];
switch (expr.binaryKind) {
@ -404,6 +455,50 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
if (expr.lhs.kind !== "ident") {
todo("non constant calls");
}
if (expr.lhs.value.res!.kind === "builtin") {
switch (expr.lhs.value.res!.name) {
case "__i32_load": {
lowerExpr(fcx, instrs, expr.args[0]);
instrs.push({ kind: "i64.load", imm: {} });
break exprKind;
}
case "__i64_load": {
lowerExpr(fcx, instrs, expr.args[0]);
instrs.push({ kind: "i64.load", imm: {} });
break exprKind;
}
case "__i32_store": {
lowerExpr(fcx, instrs, expr.args[0]);
lowerExpr(fcx, instrs, expr.args[1]);
instrs.push({ kind: "i32.store", imm: {} });
break exprKind;
}
case "__i64_store": {
lowerExpr(fcx, instrs, expr.args[0]);
lowerExpr(fcx, instrs, expr.args[1]);
instrs.push({ kind: "i64.store", imm: {} });
break exprKind;
}
case "__string_ptr": {
lowerExpr(fcx, instrs, expr.args[0]);
// ptr, len
instrs.push({ kind: "drop" });
// ptr
break exprKind;
}
case "__string_len": {
lowerExpr(fcx, instrs, expr.args[0]);
// ptr, len
instrs.push({ kind: "i32.const", imm: 0 });
// ptr, len, 0
instrs.push({ kind: "select" });
// len
break exprKind;
}
}
}
const callInstr: wasm.Instr = { kind: "call", func: 9999999999 };
fcx.cx.relocations.push({
kind: "funccall",
@ -676,9 +771,10 @@ function addRt(cx: Context, ast: Ast) {
const printIdx = cx.mod.funcs.length;
cx.mod.funcs.push(print);
cx.funcIndices.set(
JSON.stringify({ kind: "builtin", name: "print" }),
printIdx
setMap(
cx.funcIndices,
{ kind: "builtin", name: "print" },
{ kind: "func", idx: printIdx }
);
mod.exports.push({

View file

@ -12,6 +12,7 @@ import {
FunctionArg,
FunctionDef,
Identifier,
ImportDef,
Item,
LOGICAL_KINDS,
Type,
@ -23,7 +24,13 @@ import {
superFoldExpr,
} from "./ast";
import { CompilerError, Span, spanMerge } from "./error";
import { BaseToken, Token, TokenIdent } from "./lexer";
import {
BaseToken,
Token,
TokenIdent,
TokenLit,
TokenLitString,
} from "./lexer";
type Parser<T> = (t: Token[]) => [Token[], T];
@ -49,28 +56,8 @@ function parseItem(t: Token[]): [Token[], Item] {
let tok;
[t, tok] = next(t);
if (tok.kind === "function") {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, "(");
let args: FunctionArg[];
[t, args] = parseCommaSeparatedList(t, ")", (t) => {
let name;
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
[t] = expectNext(t, ":");
let type;
[t, type] = parseType(t);
return [t, { name: name.ident, type, span: name.span }];
});
let colon;
let returnType = undefined;
[t, colon] = eat(t, ":");
if (colon) {
[t, returnType] = parseType(t);
}
let sig;
[t, sig] = parseFunctionSig(t);
[t] = expectNext(t, "=");
@ -80,9 +67,7 @@ function parseItem(t: Token[]): [Token[], Item] {
[t] = expectNext(t, ";");
const def: FunctionDef = {
name: name.ident,
params: args,
returnType,
...sig,
body,
};
@ -129,11 +114,64 @@ function parseItem(t: Token[]): [Token[], Item] {
};
return [t, { kind: "type", node: def, span: name.span, id: 0 }];
} else if (tok.kind === "import") {
[t] = expectNext(t, "(");
let module;
[t, module] = expectNext<TokenLitString>(t, "lit_string");
let func;
[t, func] = expectNext<TokenLitString>(t, "lit_string");
[t] = expectNext(t, ")");
let sig;
[t, sig] = parseFunctionSig(t);
[t] = expectNext(t, ";");
const def: ImportDef = {
module: { kind: "str", value: module.value, span: module.span },
func: { kind: "str", value: func.value, span: func.span },
...sig,
};
return [t, { kind: "import", node: def, span: tok.span, id: 0 }];
} else {
unexpectedToken(tok, "item");
}
}
type FunctionSig = {
name: string;
params: FunctionArg[];
returnType?: Type;
};
function parseFunctionSig(t: Token[]): [Token[], FunctionSig] {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
[t] = expectNext(t, "(");
let params: FunctionArg[];
[t, params] = parseCommaSeparatedList(t, ")", (t) => {
let name;
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
[t] = expectNext(t, ":");
let type;
[t, type] = parseType(t);
return [t, { name: name.ident, type, span: name.span }];
});
let colon;
let returnType = undefined;
[t, colon] = eat(t, ":");
if (colon) {
[t, returnType] = parseType(t);
}
return [t, { name: name.ident, params, returnType }];
}
function parseExpr(t: Token[]): [Token[], Expr] {
/*
EXPR = COMPARISON
@ -267,7 +305,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
{
kind: "literal",
span,
value: { kind: "str", value: tok.value },
value: { kind: "str", value: tok.value, span: tok.span },
},
];
}
@ -278,7 +316,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
{
kind: "literal",
span,
value: { kind: "int", value: tok.value },
value: { kind: "int", value: tok.value, type: tok.type },
},
];
}
@ -340,7 +378,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
if (tok.kind === "if") {
let cond;
[t, cond] = parseExpr(t);
[t] = expectNext(t, "then");
let then;
[t, then] = parseExpr(t);

View file

@ -3,8 +3,10 @@ import {
Expr,
FunctionDef,
Identifier,
ImportDef,
Item,
Resolution,
StringLiteral,
Ty,
Type,
TypeDef,
@ -15,6 +17,10 @@ export function printAst(ast: Ast): string {
return ast.items.map(printItem).join("\n");
}
function printStringLiteral(lit: StringLiteral): string {
return `"${lit.value}"`;
}
function printItem(item: Item): string {
switch (item.kind) {
case "function": {
@ -23,6 +29,9 @@ function printItem(item: Item): string {
case "type": {
return printTypeDef(item.node);
}
case "import": {
return printImportDef(item.node);
}
}
}
@ -45,6 +54,17 @@ function printTypeDef(type: TypeDef): string {
return `type ${type.name} = ${fieldPart};`;
}
function printImportDef(def: ImportDef): string {
const args = def.params
.map(({ name, type }) => `${name}: ${printType(type)}`)
.join(", ");
const ret = def.returnType ? `: ${printType(def.returnType)}` : "";
return `import ${printStringLiteral(def.module)} ${printStringLiteral(
def.func
)}(${args})${ret};`;
}
function printExpr(expr: Expr, indent: number): string {
switch (expr.kind) {
case "empty": {
@ -84,10 +104,10 @@ function printExpr(expr: Expr, indent: number): string {
case "literal": {
switch (expr.value.kind) {
case "str": {
return `"${expr.value.value}"`;
return printStringLiteral(expr.value);
}
case "int": {
return `${expr.value.value}`;
return `${expr.value.value}_${expr.value.type}`;
}
}
}

View file

@ -1,6 +1,7 @@
import {
Ast,
binaryExprPrecedenceClass,
BuiltinName,
COMPARISON_KINDS,
DEFAULT_FOLDER,
EQUALITY_KINDS,
@ -21,12 +22,17 @@ import {
TY_STRING,
TY_UNIT,
TyFn,
tyIsUnit,
Type,
TyStruct,
} from "./ast";
import { CompilerError, Span } from "./error";
import { printTy } from "./printer";
function mkTyFn(params: Ty[], returnTy: Ty): Ty {
return { kind: "fn", params, returnTy };
}
function builtinAsTy(name: string, span: Span): Ty {
switch (name) {
case "String": {
@ -47,13 +53,25 @@ function builtinAsTy(name: string, span: Span): Ty {
}
}
function typeOfBuiltinValue(name: string, span: Span): Ty {
function typeOfBuiltinValue(name: BuiltinName, span: Span): Ty {
switch (name) {
case "false":
case "true":
return TY_BOOL;
case "print":
return { kind: "fn", params: [TY_STRING], returnTy: TY_UNIT };
return mkTyFn([TY_STRING], TY_UNIT);
case "__i32_store":
return mkTyFn([TY_I32, TY_I32], TY_UNIT);
case "__i64_store":
return mkTyFn([TY_I32, TY_INT], TY_UNIT);
case "__i32_load":
return mkTyFn([TY_I32], TY_I32);
case "__i64_load":
return mkTyFn([TY_I32], TY_INT);
case "__string_ptr":
return mkTyFn([TY_STRING], TY_I32);
case "__string_len":
return mkTyFn([TY_STRING], TY_I32);
default: {
throw new CompilerError(`\`${name}\` cannot be used as a value`, span);
}
@ -103,7 +121,8 @@ export function typeck(ast: Ast): Ast {
}
itemTys.set(index, null);
switch (item.kind) {
case "function": {
case "function":
case "import": {
const args = item.node.params.map((arg) => lowerAstTy(arg.type));
const returnTy: Ty = item.node.returnType
? lowerAstTy(item.node.returnType)
@ -182,6 +201,58 @@ export function typeck(ast: Ast): Ast {
},
};
}
case "import": {
const fnTy = typeOfItem(item.id) as TyFn;
fnTy.params.forEach((param, i) => {
switch (param.kind) {
case "int":
case "i32":
break;
default: {
throw new CompilerError(
`import parameters must be I32 or Int`,
item.node.params[i].span
);
}
}
});
if (!tyIsUnit(fnTy.returnTy)) {
switch (fnTy.returnTy.kind) {
case "int":
case "i32":
break;
default: {
throw new CompilerError(
`import return must be I32 or Int`,
item.node.returnType!.span
);
}
}
}
const returnType = item.node.returnType && {
...item.node.returnType,
ty: fnTy.returnTy,
};
return {
...item,
kind: "import",
node: {
module: item.node.module,
func: item.node.func,
name: item.node.name,
params: item.node.params.map((arg, i) => ({
...arg,
type: { ...arg.type, ty: fnTy.params[i] },
})),
returnType,
ty: fnTy,
},
};
}
case "type": {
const fieldNames = new Set();
item.node.fields.forEach(({ name }) => {
@ -436,7 +507,17 @@ export function checkBody(
type,
(ident) => {
const res = ident.res!;
return typeOf(res, ident.span);
switch (res.kind) {
case "local": {
const idx = localTys.length - 1 - res.index;
return localTys[idx];
}
case "item": {
return typeOfItem(res.index);
}
case "builtin":
return builtinAsTy(res.name, ident.span);
}
},
typeOfItem
);
@ -502,7 +583,14 @@ export function checkBody(
break;
}
case "int": {
ty = TY_INT;
switch (expr.value.type) {
case "Int":
ty = TY_INT;
break;
case "I32":
ty = TY_I32;
break;
}
break;
}
}
@ -721,6 +809,9 @@ function checkBinary(expr: Expr & ExprBinary): Expr {
if (lhsTy.kind === "int" && rhsTy.kind === "int") {
return { ...expr, ty: TY_INT };
}
if (lhsTy.kind === "i32" && rhsTy.kind === "i32") {
return { ...expr, ty: TY_I32 };
}
if (LOGICAL_KINDS.includes(expr.binaryKind)) {
if (lhsTy.kind === "bool" && rhsTy.kind === "bool") {