parse generics

This commit is contained in:
sfja 2024-12-22 04:23:17 +01:00
parent 75b9b53fdd
commit 027cf8dfe8
6 changed files with 204 additions and 62 deletions

View File

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

View File

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

View File

@ -73,6 +73,18 @@ export class Resolver implements AstVisitor<[Syms]> {
throw new Error("expected fn statement");
}
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) {
if (fnScopeSyms.definedLocally(param.ident)) {
this.reportAlreadyDefined(param.ident, param.pos, syms);

View File

@ -56,12 +56,50 @@ fn input(prompt: string) -> string {
//
fn is_prime(n: int) -> bool {
if n == 1 or n == 0{
return false;
fn min(a: int, b: int) -> int {
if b < a { b } else { a }
}
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 {
return false;
}
@ -70,7 +108,7 @@ fn is_prime(n: int) -> bool {
}
fn main() {
for (let i = 1; i < 10000; i += 1) {
for (let i = 1; i <= 10000; i += 1) {
if is_prime(i) {
print(int_to_string(i) + " ");
}

View File

@ -3,7 +3,13 @@
set -e
echo Text:
if command -v pygmentize 2>&1 >/dev/null
then
pygmentize -l rust -Ostyle="gruvbox-dark",linenos=1 $1
else
cat $1
fi
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);
}