mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-14 16:35:03 +01:00
Change let
This commit is contained in:
parent
40543b501c
commit
7c2faaecb8
9 changed files with 200 additions and 100 deletions
20
src/ast.ts
20
src/ast.ts
|
|
@ -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": {
|
||||
|
|
|
|||
11
src/error.ts
11
src/error.ts
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
17
src/index.ts
17
src/index.ts
|
|
@ -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);
|
||||
|
|
|
|||
18
src/lexer.ts
18
src/lexer.ts
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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":
|
||||
|
|
|
|||
146
src/parser.ts
146
src/parser.ts
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue