From bc82124601724990d53b3f6b1ae03f7bc609abd9 Mon Sep 17 00:00:00 2001 From: sfja Date: Sun, 22 Dec 2024 04:23:17 +0100 Subject: [PATCH] parse generics --- compiler/ast.ts | 7 +- compiler/parser.ts | 174 ++++++++++++++++++++++++++------------ compiler/resolver.ts | 12 +++ examples/primes_10000.slg | 48 +++++++++-- slige-run.sh | 8 +- tests/generics.slg | 17 ++++ 6 files changed, 204 insertions(+), 62 deletions(-) create mode 100644 tests/generics.slg diff --git a/compiler/ast.ts b/compiler/ast.ts index aeb278d..0728cbf 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -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; diff --git a/compiler/parser.ts b/compiler/parser.ts index 6904f7e..831b314 100644 --- a/compiler/parser.ts +++ b/compiler/parser.ts @@ -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 = { 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 { 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 { + 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( + parseElem: (this: Parser) => Res, + 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 { 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 { + return { ok: true, value: this.parseExpr() }; + } + + private parseETypeArg(): Res { + 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); } diff --git a/compiler/resolver.ts b/compiler/resolver.ts index f58c67c..577d57d 100644 --- a/compiler/resolver.ts +++ b/compiler/resolver.ts @@ -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); diff --git a/examples/primes_10000.slg b/examples/primes_10000.slg index 26afc7c..a1d7ee6 100644 --- a/examples/primes_10000.slg +++ b/examples/primes_10000.slg @@ -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 } +} + +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; } - for (let i = 2; i < n; i += 1) { + 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) + " "); } diff --git a/slige-run.sh b/slige-run.sh index 7490706..610eabd 100755 --- a/slige-run.sh +++ b/slige-run.sh @@ -3,7 +3,13 @@ set -e echo Text: -cat $1 + +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... diff --git a/tests/generics.slg b/tests/generics.slg new file mode 100644 index 0000000..f396eed --- /dev/null +++ b/tests/generics.slg @@ -0,0 +1,17 @@ + +fn exit(status_code: int) #[builtin(Exit)] {} + +fn id(v: T) -> T { + v +} + +fn main() { + if id::(123) != 123 { + exit(1); + } + if id::(true) != true { + exit(1); + } + exit(0); +} +