Change let

This commit is contained in:
nora 2023-07-29 17:03:38 +02:00
parent 40543b501c
commit 7c2faaecb8
9 changed files with 200 additions and 100 deletions

View file

@ -54,15 +54,18 @@ export type ExprEmpty = { kind: "empty" };
export type ExprLet = {
kind: "let";
name: string;
name: Identifier;
type?: Type;
rhs: Expr;
after: Expr;
// IMPORTANT: This is (sadly) shared with ExprBlock.
local?: LocalInfo,
};
export type ExprBlock = {
kind: "block";
exprs: Expr[];
// IMPORTANT: This is (sadly) shared with ExprLet.
locals?: LocalInfo[];
};
export type ExprLiteral = {
@ -206,13 +209,15 @@ export type Type = TypeKind & {
ty?: Ty;
};
// name resolution stuff
export type Resolution =
| {
kind: "local";
/**
* The index of the local variable, from inside out.
* ```
* let a in let b in (a, b);
* let a = 0; let b; (a, b);
* ^ ^
* 1 0
* ```
@ -246,6 +251,14 @@ export const BUILTINS = [
export type BuiltinName = (typeof BUILTINS)[number];
export type LocalInfo = {
name: string;
span: Span;
ty?: Ty;
};
// types
export type TyString = {
kind: "string";
};
@ -397,7 +410,6 @@ export function superFoldExpr(expr: Expr, folder: Folder): Expr {
name: expr.name,
type: expr.type && folder.type(expr.type),
rhs: folder.expr(expr.rhs),
after: folder.expr(expr.after),
};
}
case "block": {

View file

@ -52,8 +52,13 @@ function renderError(input: string, e: CompilerError) {
console.error(`${lineIdx} | ${spanToSnippet(input, line)}`);
const startRelLine =
e.span.start === Number.MAX_SAFE_INTEGER ? 0 : e.span.start - line.start;
const spanLength = min(e.span.end, line.end) - e.span.start;
console.error(
`${" ".repeat(String(lineIdx).length)} ${" ".repeat(startRelLine)}^`
`${" ".repeat(String(lineIdx).length)} ${" ".repeat(
startRelLine
)}${"^".repeat(spanLength)}`
);
}
@ -81,3 +86,7 @@ export function lines(input: string): Span[] {
export function todo(msg: string): never {
throw new CompilerError(`TODO: ${msg}`, { start: 0, end: 0 });
}
function min(a: number, b: number): number {
return a < b ? a : b;
}

View file

@ -10,14 +10,13 @@ import fs from "fs";
import { exec } from "child_process";
const input = `
type Uwu = (
meow: String,
oops: Int,
aaa: (),
);
function printInt(a: Int) = ;
function aa(a: Int, b: Uwu): Uwu = Uwu {meow: "",oops:0,aaa:()};
function main() = ();
function main() = (
let a = 0;
let b = 0;
printInt(a + b);
);
`;
function main() {
@ -44,8 +43,8 @@ function main() {
console.log("-----AST typecked------");
const typecked = typeck(resolved);
return;
console.dir(typecked, {depth: 8});
console.log("-----wasm--------------");
const wasmModule = lowerToWasm(typecked);

View file

@ -3,7 +3,6 @@ import { CompilerError, Span } from "./error";
export type DatalessToken =
| "function"
| "let"
| "in"
| "if"
| "then"
| "else"
@ -79,6 +78,14 @@ export function tokenize(input: string): Token[] {
const next = input[i];
const span: Span = { start: i, end: 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 });
} else {
@ -207,15 +214,16 @@ function isWhitespace(char: string): boolean {
return char === " " || char === "\t" || char === "\n" || char === "\r";
}
const keywords = new Set<string>([
const KEYOWRDS: DatalessToken[] = [
"function",
"let",
"in",
"if",
"then",
"else",
"type",
]);
];
const KEYWORD_SET = new Set<string>(KEYOWRDS);
function isKeyword(kw: string): DatalessToken | undefined {
return keywords.has(kw) ? (kw as DatalessToken) : undefined;
return KEYWORD_SET.has(kw) ? (kw as DatalessToken) : undefined;
}

View file

@ -121,6 +121,8 @@ function lowerExpr(fcx: FuncContext, instrs: wasm.Instr[], expr: Expr) {
case "block":
if (expr.exprs.length === 1) {
lowerExpr(fcx, instrs, expr.exprs[0]);
} else {
todo("complex blocks");
}
break;
case "literal":

View file

@ -4,9 +4,11 @@ import {
Ast,
BinaryKind,
COMPARISON_KINDS,
DEFAULT_FOLDER,
Expr,
ExprStructLiteral,
FieldDef,
Folder,
FunctionArg,
FunctionDef,
Identifier,
@ -16,9 +18,11 @@ import {
TypeDef,
UNARY_KINDS,
UnaryKind,
foldAst,
superFoldExpr,
} from "./ast";
import { CompilerError, Span, todo } from "./error";
import { BaseToken, DatalessToken, Token, TokenIdent } from "./lexer";
import { CompilerError, Span, spanMerge } from "./error";
import { BaseToken, Token, TokenIdent } from "./lexer";
type Parser<T> = (t: Token[]) => [Token[], T];
@ -33,7 +37,11 @@ export function parse(t: Token[]): Ast {
const withIds = items.map((item, i) => ({ ...item, id: i }));
return { items: withIds };
const ast = { items: withIds };
validateAst(ast);
return ast;
}
function parseItem(t: Token[]): [Token[], Item] {
@ -127,7 +135,7 @@ function parseItem(t: Token[]): [Token[], Item] {
function parseExpr(t: Token[]): [Token[], Expr] {
/*
EXPR = { LET | COMPARISON | IF }
EXPR = COMPARISON
LET = "let" NAME { ":" TYPE } "=" EXPR "in" EXPR
IF = "if" EXPR "then" EXPR { "else" EXPR }
@ -145,56 +153,11 @@ function parseExpr(t: Token[]): [Token[], Expr] {
CALL = ATOM { "(" EXPR_LIST ")" }
ATOM = "(" { EXPR ";" } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY
ATOM = "(" { EXPR ";" } EXPR ")" | IDENT { STRUCT_INIT } | LITERAL | EMPTY | LET | IF
EMPTY =
STRUCT_INIT = "{" { NAME ":" EXPR } { "," NAME ":" EXPR } { "," } "}"
EXPR_LIST = { EXPR { "," EXPR } { "," } }
*/
const [, peak] = next(t);
if (peak.kind === "let") {
[t] = expectNext(t, "let");
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
let type = undefined;
let colon;
[t, colon] = eat(t, ":");
if (colon) {
[t, type] = parseType(t);
}
[t] = expectNext(t, "=");
let rhs;
[t, rhs] = parseExpr(t);
[t] = expectNext(t, "in");
let after;
[t, after] = parseExpr(t);
return [
t,
{ kind: "let", name: name.ident, type, rhs, after, span: name.span },
];
}
if (peak.kind === "if") {
[t] = expectNext(t, "if");
let cond;
[t, cond] = parseExpr(t);
[t] = expectNext(t, "then");
let then;
[t, then] = parseExpr(t);
let elseTok;
[t, elseTok] = eat(t, "else");
let elsePart = undefined;
if (elseTok) {
[t, elsePart] = parseExpr(t);
}
return [t, { kind: "if", cond, then, else: elsePart, span: peak.span }];
}
return parseExprComparison(t);
}
@ -206,15 +169,15 @@ function mkParserExprBinary(
let lhs;
[t, lhs] = lower(t);
const [, peak] = next(t);
if (kinds.includes(peak.kind)) {
const [, peek] = next(t);
if (kinds.includes(peek.kind)) {
[t] = next(t);
let rhs;
[t, rhs] = parser(t);
const span = peak.span;
const span = spanMerge(lhs.span, rhs.span);
return [
t,
{ kind: "binary", binaryKind: peak.kind as BinaryKind, lhs, rhs, span },
{ kind: "binary", binaryKind: peek.kind as BinaryKind, lhs, rhs, span },
];
}
@ -318,8 +281,6 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
}
if (tok.kind === "identifier") {
console.log(t);
if (maybeNextT(t)[1]?.kind === "{") {
let fields;
[t, fields] = parseStructInit(t);
@ -344,6 +305,52 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
];
}
if (tok.kind === "let") {
let name;
[t, name] = expectNext<TokenIdent>(t, "identifier");
let type = undefined;
let colon;
[t, colon] = eat(t, ":");
if (colon) {
[t, type] = parseType(t);
}
[t] = expectNext(t, "=");
let rhs;
[t, rhs] = parseExpr(t);
const nameIdent: Identifier = { name: name.ident, span: name.span };
return [
t,
{
kind: "let",
name: nameIdent,
type,
rhs,
span: name.span,
},
];
}
if (tok.kind === "if") {
let cond;
[t, cond] = parseExpr(t);
[t] = expectNext(t, "then");
let then;
[t, then] = parseExpr(t);
let elseTok;
[t, elseTok] = eat(t, "else");
let elsePart = undefined;
if (elseTok) {
[t, elsePart] = parseExpr(t);
}
return [t, { kind: "if", cond, then, else: elsePart, span: tok.span }];
}
// Parse nothing at all.
return [startT, { kind: "empty", span }];
}
@ -496,3 +503,30 @@ function maybeNextT(t: Token[]): [Token[], Token | undefined] {
function unexpectedToken(token: Token): never {
throw new CompilerError("unexpected token", token.span);
}
function validateAst(ast: Ast) {
const validator: Folder = {
...DEFAULT_FOLDER,
expr(value: Expr): Expr {
if (value.kind === "block") {
value.exprs.forEach((expr) => {
if (expr.kind === "let") {
this.expr(expr.rhs);
if (expr.type) {
this.type(expr.type);
}
} else {
this.expr(expr);
}
});
return value;
} else if (value.kind === "let") {
throw new CompilerError("let is only allowed in blocks", value.span);
} else {
return superFoldExpr(value, this);
}
},
};
foldAst(ast, validator);
}

View file

@ -53,10 +53,10 @@ function printExpr(expr: Expr, indent: number): string {
case "let": {
const type = expr.type ? `: ${printType(expr.type)}` : "";
return `let ${expr.name}${type} = ${printExpr(
return `let ${expr.name.name}${type} = ${printExpr(
expr.rhs,
indent + 1
)} in${linebreak(indent)}${printExpr(expr.after, indent)}`;
)}`;
}
case "block": {
const exprs = expr.exprs.map((expr) => printExpr(expr, indent + 1));

View file

@ -3,8 +3,10 @@ import {
BUILTINS,
BuiltinName,
DEFAULT_FOLDER,
Expr,
Folder,
Identifier,
LocalInfo,
Resolution,
foldAst,
superFoldExpr,
@ -68,6 +70,8 @@ export function resolve(ast: Ast): Ast {
throw new CompilerError(`cannot find ${ident.name}`, ident.span);
};
const blockLocals: LocalInfo[][] = [];
const resolver: Folder = {
...DEFAULT_FOLDER,
item(item) {
@ -82,7 +86,7 @@ export function resolve(ast: Ast): Ast {
item.node.returnType && this.type(item.node.returnType);
item.node.params.forEach(({ name }) => scopes.push(name));
const body = superFoldExpr(item.node.body, this);
const body = this.expr(item.node.body);
const revParams = item.node.params.slice();
revParams.reverse();
revParams.forEach(({ name }) => popScope(name));
@ -104,24 +108,39 @@ export function resolve(ast: Ast): Ast {
return superFoldItem(item, this);
},
expr(expr) {
if (expr.kind === "let") {
const rhs = this.expr(expr.rhs);
const type = expr.type && this.type(expr.type);
scopes.push(expr.name);
const after = this.expr(expr.after);
popScope(expr.name);
if (expr.kind === "block") {
const prevScopeLength = scopes.length;
blockLocals.push([]);
const exprs = expr.exprs.map<Expr>((inner) => this.expr(inner));
scopes.length = prevScopeLength;
const locals = blockLocals.pop();
return {
kind: "let",
name: expr.name,
rhs,
after,
type,
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 superFoldExpr(expr, this);
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);
}
},
ident(ident) {
const res = resolveIdent(ident);

View file

@ -425,9 +425,11 @@ export function checkBody(
const rhs = this.expr(expr.rhs);
infcx.assign(bindingTy, rhs.ty!, expr.span);
// AST validation ensures that lets can only be in blocks, where
// the types will be popped.
localTys.push(bindingTy);
const after = this.expr(expr.after);
localTys.pop();
expr.local!.ty = bindingTy;
const type: Type | undefined = loweredBindingTy && {
...expr.type!,
@ -439,20 +441,23 @@ export function checkBody(
name: expr.name,
type,
rhs,
after,
ty: after.ty!,
ty: TY_UNIT,
span: expr.span,
};
}
case "block": {
const prevLocalTysLen = localTys.length;
const exprs = expr.exprs.map((expr) => this.expr(expr));
const ty = exprs.length > 0 ? exprs[exprs.length - 1].ty! : TY_UNIT;
localTys.length = prevLocalTysLen;
return {
kind: "block",
...expr,
exprs,
ty,
span: expr.span,
};
}
case "literal": {
@ -592,13 +597,25 @@ export function checkBody(
infcx.assign(fnTy.returnTy, checked.ty!, body.span);
const resolveTy = (ty: Ty, span: Span) => {
const resTy = infcx.resolveIfPossible(ty);
if (!resTy) {
throw new CompilerError("cannot infer type", span);
}
return resTy;
};
const resolver: Folder = {
...DEFAULT_FOLDER,
expr(expr) {
const ty = infcx.resolveIfPossible(expr.ty!);
if (!ty) {
throw new CompilerError("cannot infer type", expr.span);
const ty = resolveTy(expr.ty!, expr.span);
if (expr.kind === "block") {
expr.locals!.forEach((local) => {
local.ty = resolveTy(local.ty!, local.span);
});
}
return { ...expr, ty };
},
};