parse generics

This commit is contained in:
sfja 2024-12-22 04:23:17 +01:00
parent 7944c76a6a
commit bc82124601
6 changed files with 204 additions and 62 deletions

View File

@ -15,6 +15,7 @@ export type StmtKind =
| { | {
type: "fn"; type: "fn";
ident: string; ident: string;
etypeParams?: ETypeParam[];
params: Param[]; params: Param[];
returnType?: EType; returnType?: EType;
body: Expr; body: Expr;
@ -42,7 +43,9 @@ export type ExprKind =
| { type: "group"; expr: Expr } | { type: "group"; expr: Expr }
| { type: "field"; subject: Expr; value: string } | { type: "field"; subject: Expr; value: string }
| { type: "index"; subject: Expr; value: Expr } | { type: "index"; subject: Expr; value: Expr }
| { type: "call"; subject: Expr; args: Expr[] } | { type: "call"; subject: Expr; etypeArgs?: EType[]; args: Expr[] }
| { type: "path"; subject: Expr; value: string }
| { type: "etype_args"; subject: Expr; etypeArgs?: EType[] }
| { type: "unary"; unaryType: UnaryType; subject: Expr } | { type: "unary"; unaryType: UnaryType; subject: Expr }
| { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr } | { type: "binary"; binaryType: BinaryType; left: Expr; right: Expr }
| { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos } | { type: "if"; cond: Expr; truthy: Expr; falsy?: Expr; elsePos?: Pos }
@ -98,7 +101,7 @@ export type SymKind =
| { type: "fn"; stmt: Stmt } | { type: "fn"; stmt: Stmt }
| { type: "fn_param"; param: Param } | { type: "fn_param"; param: Param }
| { type: "closure"; inner: Sym } | { type: "closure"; inner: Sym }
| { type: "builtin"; builtinId: number }; | { type: "generic"; etypeParam: ETypeParam };
export type EType = { export type EType = {
kind: ETypeKind; kind: ETypeKind;

View File

@ -5,6 +5,7 @@ import {
BinaryType, BinaryType,
EType, EType,
ETypeKind, ETypeKind,
ETypeParam,
Expr, Expr,
ExprKind, ExprKind,
Param, Param,
@ -16,6 +17,8 @@ import { printStackTrace, Reporter } from "./info.ts";
import { Lexer } from "./lexer.ts"; import { Lexer } from "./lexer.ts";
import { Pos, Token } from "./token.ts"; import { Pos, Token } from "./token.ts";
type Res<T> = { ok: true; value: T } | { ok: false };
export class Parser { export class Parser {
private currentToken: Token | null; private currentToken: Token | null;
@ -172,20 +175,28 @@ export class Parser {
} }
const ident = this.current().identValue!; const ident = this.current().identValue!;
this.step(); this.step();
let etypeParams: ETypeParam[] | undefined;
if (this.test("<")) {
etypeParams = this.parseFnETypeParams();
}
if (!this.test("(")) { if (!this.test("(")) {
this.report("expected '('"); this.report("expected '('");
return this.stmt({ type: "error" }, pos); return this.stmt({ type: "error" }, pos);
} }
const params = this.parseFnParams(); const params = this.parseFnParams();
let returnType: EType | null = null; let returnType: EType | undefined;
if (this.test("->")) { if (this.test("->")) {
this.step(); this.step();
returnType = this.parseEType(); returnType = this.parseEType();
} }
let anno: Anno | null = null; let anno: Anno | undefined;
if (this.test("#")) { if (this.test("#")) {
anno = this.parseAnno(); const result = this.parseAnno();
if (!result.ok) {
return this.stmt({ type: "error" }, pos);
}
anno = result.value;
} }
if (!this.test("{")) { if (!this.test("{")) {
this.report("expected block"); this.report("expected block");
@ -196,10 +207,11 @@ export class Parser {
{ {
type: "fn", type: "fn",
ident, ident,
etypeParams,
params, params,
returnType: returnType !== null ? returnType : undefined, returnType,
body, body,
anno: anno != null ? anno : undefined, anno,
}, },
pos, pos,
); );
@ -231,60 +243,83 @@ export class Parser {
return annoArgs; return annoArgs;
} }
private parseAnno(): Anno | null { private parseAnno(): Res<Anno> {
const pos = this.pos(); const pos = this.pos();
this.step(); this.step();
if (!this.test("[")) { if (!this.test("[")) {
this.report("expected '['"); this.report("expected '['");
return null; return { ok: false };
} }
this.step(); this.step();
if (!this.test("ident")) { if (!this.test("ident")) {
this.report("expected identifier"); this.report("expected identifier");
return null; return { ok: false };
} }
const ident = this.current().identValue!; const ident = this.current().identValue!;
const values = this.parseAnnoArgs(); const values = this.parseAnnoArgs();
if (!this.test("]")) { if (!this.test("]")) {
this.report("expected ']'"); this.report("expected ']'");
return null; return { ok: false };
} }
this.step(); this.step();
return { ident, pos, values }; return { ok: true, value: { ident, pos, values } };
}
private parseFnETypeParams(): ETypeParam[] {
return this.parseDelimitedList(this.parseETypeParam, ">", ",");
}
private parseETypeParam(): Res<ETypeParam> {
const pos = this.pos();
if (this.test("ident")) {
const ident = this.current().identValue!;
this.step();
return { ok: true, value: { ident, pos } };
}
this.report("expected generic parameter");
return { ok: false };
} }
private parseFnParams(): Param[] { private parseFnParams(): Param[] {
this.step(); return this.parseDelimitedList(this.parseParam, ")", ",");
if (this.test(")")) {
this.step();
return [];
}
const params: Param[] = [];
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
params.push(paramResult.value);
while (this.test(",")) {
this.step();
if (this.test(")")) {
break;
}
const paramResult = this.parseParam();
if (!paramResult.ok) {
return [];
}
params.push(paramResult.value);
}
if (!this.test(")")) {
this.report("expected ')'");
return params;
}
this.step();
return params;
} }
private parseParam(): { ok: true; value: Param } | { ok: false } { private parseDelimitedList<T>(
parseElem: (this: Parser) => Res<T>,
endToken: string,
delimiter: string,
): T[] {
this.step();
if (this.test(endToken)) {
this.step();
return [];
}
const elems: T[] = [];
const elemRes = parseElem.call(this);
if (!elemRes.ok) {
return [];
}
elems.push(elemRes.value);
while (this.test(delimiter)) {
this.step();
if (this.test(endToken)) {
break;
}
const elemRes = parseElem.call(this);
if (!elemRes.ok) {
return [];
}
elems.push(elemRes.value);
}
if (!this.test(endToken)) {
this.report(`expected '${endToken}'`);
return elems;
}
this.step();
return elems;
}
private parseParam(): Res<Param> {
const pos = this.pos(); const pos = this.pos();
if (this.test("ident")) { if (this.test("ident")) {
const ident = this.current().identValue!; const ident = this.current().identValue!;
@ -616,24 +651,47 @@ export class Parser {
continue; continue;
} }
if (this.test("(")) { if (this.test("(")) {
const args = this.parseDelimitedList(
this.parseExprArg,
")",
",",
);
subject = this.expr({ type: "call", subject, args }, pos);
continue;
}
if (this.test("::")) {
this.step(); this.step();
let args: Expr[] = []; if (!this.test("ident")) {
if (!this.test(")")) { this.report("expected ident");
args.push(this.parseExpr());
while (this.test(",")) {
this.step();
if (this.test(")")) {
break;
}
args.push(this.parseExpr());
}
}
if (!this.test(")")) {
this.report("expected ')'");
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }
const value = this.current().identValue!;
this.step(); this.step();
subject = this.expr({ type: "call", subject, args }, pos); subject = this.expr({ type: "path", subject, value }, pos);
continue;
}
if (this.test("::<")) {
const etypeArgs = this.parseDelimitedList(
this.parseETypeArg,
">",
",",
);
if (this.test("(")) {
subject = this.expr(
{ type: "etype_args", subject, etypeArgs },
pos,
);
continue;
}
const args = this.parseDelimitedList(
this.parseExprArg,
")",
",",
);
subject = this.expr(
{ type: "call", subject, etypeArgs, args },
pos,
);
continue; continue;
} }
break; break;
@ -641,6 +699,14 @@ export class Parser {
return subject; return subject;
} }
private parseExprArg(): Res<Expr> {
return { ok: true, value: this.parseExpr() };
}
private parseETypeArg(): Res<EType> {
return { ok: true, value: this.parseEType() };
}
private parseOperand(): Expr { private parseOperand(): Expr {
const pos = this.pos(); const pos = this.pos();
if (this.test("ident")) { if (this.test("ident")) {
@ -690,7 +756,7 @@ export class Parser {
return this.parseLoop(); return this.parseLoop();
} }
this.report("expected expr", pos); this.report(`expected expr, got '${this.current().type}'`, pos);
this.step(); this.step();
return this.expr({ type: "error" }, pos); return this.expr({ type: "error" }, pos);
} }

View File

@ -73,6 +73,18 @@ export class Resolver implements AstVisitor<[Syms]> {
throw new Error("expected fn statement"); throw new Error("expected fn statement");
} }
const fnScopeSyms = new FnSyms(syms); const fnScopeSyms = new FnSyms(syms);
for (const param of stmt.kind.etypeParams ?? []) {
if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms);
continue;
}
fnScopeSyms.define(param.ident, {
ident: param.ident,
type: "generic",
pos: param.pos,
etypeParam: param,
});
}
for (const param of stmt.kind.params) { for (const param of stmt.kind.params) {
if (fnScopeSyms.definedLocally(param.ident)) { if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms); this.reportAlreadyDefined(param.ident, param.pos, syms);

View File

@ -56,12 +56,50 @@ fn input(prompt: string) -> string {
// //
fn is_prime(n: int) -> bool { fn min(a: int, b: int) -> int {
if n == 1 or n == 0{ if b < a { b } else { a }
return false;
} }
for (let i = 2; i < n; i += 1) { fn max(a: int, b: int) -> int {
if a < b { b } else { b }
}
fn sqrt(n: int) -> int {
let low = min(1, n);
let high = max(1, n);
let mid = 0;
while 100 * low * low < n {
low = low * 10;
}
while (high * high) / 100 > n {
high = high / 10;
}
for (let i = 0; i < 100; i += 1) {
mid = (low + high) / 2;
if mid * mid == n {
return mid;
}
if mid * mid > n {
high = mid;
} else {
low = mid;
}
}
mid
}
fn is_prime(n: int) -> bool {
if n == 0{
return false;
}
if n == 1 {
return true;
}
let n_root = sqrt(n);
for (let i = 2; i < n_root; i += 1) {
if remainder(n, i) == 0 { if remainder(n, i) == 0 {
return false; return false;
} }
@ -70,7 +108,7 @@ fn is_prime(n: int) -> bool {
} }
fn main() { fn main() {
for (let i = 1; i < 10000; i += 1) { for (let i = 1; i <= 10000; i += 1) {
if is_prime(i) { if is_prime(i) {
print(int_to_string(i) + " "); print(int_to_string(i) + " ");
} }

View File

@ -3,7 +3,13 @@
set -e set -e
echo Text: echo Text:
if command -v pygmentize 2>&1 >/dev/null
then
pygmentize -l rust -Ostyle="gruvbox-dark",linenos=1 $1
else
cat $1 cat $1
fi
echo Compiling $1... echo Compiling $1...

17
tests/generics.slg Normal file
View File

@ -0,0 +1,17 @@
fn exit(status_code: int) #[builtin(Exit)] {}
fn id<T>(v: T) -> T {
v
}
fn main() {
if id::<int>(123) != 123 {
exit(1);
}
if id::<bool>(true) != true {
exit(1);
}
exit(0);
}