initial commit

This commit is contained in:
Ludwig Lehnert 2025-03-04 20:54:46 +01:00
commit 3317626540
30 changed files with 1911 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@ -0,0 +1,42 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store
.antlr/
src/antlr/*
!src/antlr/plsm.g4
/test*
/*.test

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# plsm-typescript
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.3. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

15
antlr4-tool Executable file
View File

@ -0,0 +1,15 @@
#!/bin/env bash
DIR=$(dirname "$(realpath $0)")
mkdir -p .antlr
ANTLR_PATH="$DIR/.antlr/antlr4-complete.jar"
if [ ! -f "$ANTLR_PATH" ]; then
wget -qO "$ANTLR_PATH" https://www.antlr.org/download/antlr-4.13.2-complete.jar
fi
java -jar "$ANTLR_PATH" $@

32
bun.lock Normal file
View File

@ -0,0 +1,32 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "plsm-typescript",
"dependencies": {
"antlr4": "^4.13.2",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
"@types/node": ["@types/node@22.13.8", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ=="],
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
"antlr4": ["antlr4@4.13.2", "", {}, "sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg=="],
"bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
}
}

70
examples/test.plsm Normal file
View File

@ -0,0 +1,70 @@
module test;
import stdlib/io;
fun main -> {
io.println(foo() + 10);
}
trait Expr {
visit<T>(visitor: Visitor<T>): T;
}
trait ExprVisitor<T> {
visitString(string: String): T;
}
trait List<T : any> {
size: int;
get(index: int): T;
set(index: int, value: T): void;
}
struct LinkedList<T : any> {
head: T?;
tail: LinkedList<T>?;
init -> {
self.head = null;
self.tail = null;
}
}
impl List<T> for LinkedList<T> {
size -> {
if self.head == null {
return 0;
}
if self.tail == null {
return 1;
}
return 1 + self.tail.size();
}
get(index) -> {
if index == 0 {
return self.head;
}
return self.tail.get(index - 1);
}
set(index, value) -> {
if index == 0 {
self.head = value;
return;
}
if self.tail == null {
raise StringError(
"LinkedList<T>.set(...): index out of bounds",
);
}
self.tail.set(index - 1, value,);
}
}
fun foo -> "Hello World!";

0
lib/mem.c Normal file
View File

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "plsm-typescript",
"module": "src/main.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"dependencies": {
"antlr4": "^4.13.2"
}
}

448
src/antlr/plsm.g4 Normal file
View File

@ -0,0 +1,448 @@
grammar plsm;
options {
language = TypeScript;
}
@header {
import AST from "~/frontend/ast";
import { Integer, Float } from "~/const";
}
@parser::members {
private fileName: string = '';
public setFileName(fileName: string) { this.fileName = fileName };
private getSourceRange(ctx: ParserRuleContext): AST.SourceRange {
const startToken = ctx.start;
const stopToken = this.getCurrentToken();
// const stopToken = ctx.stop ? ctx.stop : ctx.start;
return new AST.SourceRange(
this.fileName,
[startToken.line, startToken.column],
[stopToken.line, stopToken.column + stopToken.text.length - 1],
);
}
}
module
returns[AST.Module ast = null as any]:
moduleStmt imports += importStmt* decls += topLevelDecl* EOF {
$ast = new AST.Module(
this.getSourceRange($ctx),
$moduleStmt.ast,
$imports.map(import_ => import_.ast),
$decls.map(decl => decl.ast),
);
};
moduleStmt
returns[AST.ModuleDecl ast = null as any]:
'module' qualifiers += ID ('/' qualifiers += ID)* ';' {
$ast = new AST.ModuleDecl(
this.getSourceRange($ctx),
$qualifiers.map(q => q.text),
);
};
importStmt
returns[AST.ImportStmt ast = null as any]:
'import' qualifiers += ID ('/' qualifiers += ID)* (
'as' alias = ID
)? ';' {
$ast = new AST.ImportStmt(
this.getSourceRange($ctx),
$qualifiers.map(q => q.text),
);
};
topLevelDecl
returns[AST.Decl ast = null as any]:
varDecl { $ast = $varDecl.ast }
| funDecl { $ast = $funDecl.ast }
| structDecl { $ast = $structDecl.ast }
| traitDecl { $ast = $traitDecl.ast }
| implDecl { $ast = $implDecl.ast };
block
returns[AST.Block ast = null as any]:
'{' stmts += innerStmt* '}' {
$ast = new AST.Block(
this.getSourceRange($ctx),
$stmts.map(stmt => stmt.ast),
);
};
innerStmt
returns[AST.Stmt ast = null as any]:
expr ';' {
$ast = new AST.ExprStmt(
this.getSourceRange($ctx),
$expr.ast,
);
}
| varDecl { $ast = $varDecl.ast }
| returnStmt { $ast = $returnStmt.ast }
| raiseStmt { $ast = $raiseStmt.ast }
| ifStmt { $ast = $ifStmt.ast }
| whileStmt { $ast = $whileStmt.ast };
varDecl
returns[AST.VarDecl ast = null as any]:
'let' (name = ID) (':' typeName)? '=' rval = expr ';' {
$ast = new AST.VarDecl(
this.getSourceRange($ctx),
$name.text!,
$ctx.typeName()?.ast ?? null,
$rval.ast,
);
};
funDecl
returns[AST.FunDecl ast = null as any]:
'fun' name = ID (
'(' (params += funParam (',' params += funParam)* ','?)? ')'
)? (':' returnType = typeName)? '->' (
exprBody = expr ';'
| blockBody = block
) {
let block: AST.Block;
if ($ctx._blockBody?.ast) block = $blockBody.ast;
else block = new AST.Block($exprBody.ast.sourceRange, [
new AST.ReturnStmt($exprBody.ast.sourceRange, $exprBody.ast),
]);
$ast = new AST.FunDecl(
this.getSourceRange($ctx),
$name.text!,
$ctx._returnType?.ast ?? null,
$params.map(p => p.ast),
block,
);
};
funParam
returns[AST.FunParam ast = null as any]:
name = ID ':' typeName {
$ast = new AST.FunParam(
this.getSourceRange($ctx),
$name.text!,
$typeName.ast,
);
};
structDecl
returns[AST.StructDecl ast = null as any]:
'struct' name = ID (
'<' typeParams += structTypeParam (
',' typeParams += structTypeParam
)* '>'
)? '{' fields += structField* initializers += structInitializer* '}' {
$ast = new AST.StructDecl(
this.getSourceRange($ctx),
$name.text!,
$typeParams.map(p => p.ast),
$fields.map(f => f.ast),
$initializers.map(i => i.ast),
);
};
structField
returns[AST.StructField ast = null as any]:
name = ID ':' typeName ';' {
$ast = new AST.StructField(
this.getSourceRange($ctx),
$name.text!,
$typeName.ast,
);
};
structInitializer
returns[AST.StructInitializer ast = null as any]:
'init' (
'(' (params += funParam (',' params += funParam)* ','?)? ')'
)? '->' body = block {
$ast = new AST.StructInitializer(
this.getSourceRange($ctx),
$params.map(p => p.ast),
$body.ast,
);
};
structTypeParam
returns[AST.StructTypeParam ast = null as any]:
name = ID (':' bound = typeName)? {
$ast = new AST.StructTypeParam(
this.getSourceRange($ctx),
$name.text!,
$ctx._bound?.ast ?? null,
);
};
traitDecl
returns[AST.TraitDecl ast = null as any]:
'trait' name = ID (
'<' typeParams += traitTypeParam (
',' typeParams += traitTypeParam
)* '>'
)? '{' attrs += traitAttr* '}' {
$ast = new AST.TraitDecl(
this.getSourceRange($ctx),
$name.text!,
$typeParams.map(tp => tp.ast),
$attrs.map(attr => attr.ast),
);
};
traitAttr
returns[AST.TraitAttr ast = null as any]:
isConst = 'const'? name = ID (
'(' (params += funParam (',' params += funParam)* ','?)? ')'
)? ':' returnType = typeName (
'->' (exprBody = expr ';' | blockBody = block)
| ';'
) {
let block: AST.Block | null = null;
if ($ctx._blockBody?.ast) block = $blockBody.ast;
else if ($ctx._exprBody?.ast) block = new AST.Block($exprBody.ast.sourceRange, [
new AST.ReturnStmt($exprBody.ast.sourceRange, $exprBody.ast),
]);
$ast = new AST.TraitAttr(
this.getSourceRange($ctx),
!!$isConst.text,
$name.text!,
$params.map(p => p.ast),
$returnType.ast,
block,
);
};
traitTypeParam
returns[AST.TraitTypeParam ast = null as any]:
name = ID (':' bound = typeName)? {
$ast = new AST.TraitTypeParam(
this.getSourceRange($ctx),
$name.text!,
$ctx._bound?.ast ?? null,
);
};
implDecl
returns[AST.ImplDecl ast = null as any]:
'impl' traitName = qualifiedName (
'<' traitTypeArgs += typeArg (
',' traitTypeArgs += typeArg
)* '>'
)? 'for' structName = qualifiedName (
'<' structTypeParams += ID (',' structTypeParams += ID)* '>'
)? '{' attrs += implAttr* '}' {
$ast = new AST.ImplDecl(
this.getSourceRange($ctx),
$traitName.text!,
$traitTypeArgs.map(arg => arg.ast),
$structName.text!,
$structTypeParams.map(p => p.text),
$attrs.map(attr => attr.ast),
);
};
implAttr
returns[AST.ImplAttr ast = null as any]:
name = ID ('(' (params += ID (',' params += ID)* ','?)? ')')? '->' (
exprBody = expr ';'
| blockBody = block
) {
let block: AST.Block;
if ($ctx._blockBody?.ast) block = $blockBody.ast;
else block = new AST.Block($exprBody.ast.sourceRange, [
new AST.ReturnStmt($exprBody.ast.sourceRange, $exprBody.ast),
]);
$ast = new AST.ImplAttr(
this.getSourceRange($ctx),
$name.text!,
$params.map(p => p.text),
block,
);
};
returnStmt
returns[AST.ReturnStmt ast = null as any]:
'return' expr? ';' {
$ast = new AST.ReturnStmt(
this.getSourceRange($ctx),
$ctx.expr()?.ast ?? null,
);
};
whileStmt
returns[AST.WhileStmt ast = null as any]:
'while' cond = expr body = block {
$ast = new AST.WhileStmt(
this.getSourceRange($ctx),
$cond.ast,
$body.ast,
);
};
ifStmt
returns[AST.IfStmt ast = null as any]:
'if' cond = expr then = block ('else' els = block)? {
$ast = new AST.IfStmt(
this.getSourceRange($ctx),
$cond.ast,
$then.ast,
$ctx._els?.ast ?? null,
);
};
raiseStmt
returns[AST.RaiseStmt ast = null as any]:
'raise' value = expr ';' {
$ast = new AST.RaiseStmt(
this.getSourceRange($ctx),
$value.ast,
);
};
expr
returns[AST.Expr ast = null as any]:
name = qualifiedName { $ast = new AST.Identifier(this.getSourceRange($ctx), $name.name) }
| val = INT { $ast = new AST.IntExpr(this.getSourceRange($ctx), new Integer($val.text!)) }
| val = FLOAT { $ast = new AST.FloatExpr(this.getSourceRange($ctx), new Float($val.text!)) }
| val = STRING { $ast = new AST.StringExpr(this.getSourceRange($ctx), JSON.parse($val.text!)) }
| lhs = expr op = BINOP rhs = expr {
$ast = new AST.BinExpr(this.getSourceRange($ctx), $op.text!, $lhs.ast, $rhs.ast) ;
}
| cond = expr '?' ifTrue = expr ':' ifFalse = expr {
$ast = new AST.CondExpr(this.getSourceRange($ctx), $cond.ast, $ifTrue.ast, $ifFalse.ast);
}
| callee = expr '(' (args += expr (',' args += expr)* ','?)? ')' {
$ast = new AST.CallExpr(
this.getSourceRange($ctx),
$callee.ast,
$args.map(arg => arg.ast),
);
}
| lval = expr '=' rval = expr {
$ast = new AST.AssignExpr(
this.getSourceRange($ctx),
$lval.ast,
$rval.ast,
);
}
| '(' (params += funParam (',' params += funParam)* ','?)? ')' '->' (
exprBody = expr
| blockBody = block
) {
let block: AST.Block;
if ($ctx._blockBody?.ast) block = $blockBody.ast;
else block = new AST.Block($exprBody.ast.sourceRange, [
new AST.ReturnStmt($exprBody.ast.sourceRange, $exprBody.ast),
]);
$ast = new AST.LambdaExpr(
this.getSourceRange($ctx),
$params.map(p => p.ast),
block,
);
}
| '(' expr ')' { $ast = $expr.ast };
typeName
returns[AST.TypeName ast = null as any]:
name = ID { $ast = new AST.NamedTypeName(this.getSourceRange($ctx), $name.text!) }
| name = ID '<' typeArgs += typeArg (',' typeArgs += typeArg)* '>' {
$ast = new AST.GenericTypeName(
this.getSourceRange($ctx),
$name.text!,
$typeArgs.map(arg => arg.ast),
);
}
| type_ = typeName '?' {
$ast = new AST.NullableTypeName(
this.getSourceRange($ctx),
$type_.ast,
);
}
| type_ = typeName '[' ']' {
$ast = new AST.ArrayTypeName(
this.getSourceRange($ctx),
$type_.ast,
);
}
| '(' typeNames += typeName (',' typeNames += typeName)+ ','? ')' {
$ast = new AST.TupleTypeName(
this.getSourceRange($ctx),
$typeNames.map(typeName => typeName.ast),
);
}
| '(' (
params += lambdaTypeNameParam (
',' params += lambdaTypeNameParam
)* ','?
)? ')' '->' returnType = typeName {
$ast = new AST.LambdaTypeName(
this.getSourceRange($ctx),
$params.map(p => p.ast),
$returnType.ast,
);
};
lambdaTypeNameParam
returns[[string | null, AST.TypeName] ast = null as any]:
(name = ID ':')? type_ = typeName {
$ast = [$name.text ?? null, $type_.ast];
};
typeArg
returns[AST.TypeName ast = null as any]:
typeName { $ast = $typeName.ast };
qualifiedName
returns[string[] name = []]:
qualifiers += ID ('.' qualifiers += ID)* {
$name = $qualifiers.map(q => q.text);
};
BINOP:
'+'
| '-'
| '*'
| '/'
| '%'
| '|'
| '&'
| '^'
| '&&'
| '||'
| '<<'
| '>>'
| '??'
| '**'
| '=='
| '>='
| '<='
| '>'
| '<';
FLOAT: ('+' | '-')? ([0-9]+ '.' [0-9]* | [0-9]* '.' [0-9]+);
INT: ('+' | '-')? (
'0x' [0-9a-fA-F]+
| '0o' [0-7]+
| '0b' [0-1]+
| [0-9]+
);
ID: [_a-zA-Z] [_a-zA-Z0-9]*;
STRING: '"' (ESC | ~["\\\r\n])* '"';
fragment ESC: '\\' (["\\/bfnrt] | UNICODE);
fragment UNICODE: 'u' HEX HEX HEX HEX;
fragment HEX: [0-9a-fA-F];
WHITESPACE: [ \t\r\n]+ -> skip;
COMMENT: ('/*' .*? '*/' | '//' ~[\r\n]*) -> skip;

1
src/const/const.ts Normal file
View File

@ -0,0 +1 @@
export abstract class Constant { }

72
src/const/float.ts Normal file
View File

@ -0,0 +1,72 @@
import { Constant } from "./const";
import { makeBigInt, type Integer, type IntegerLike } from "./integer";
type FloatLike = Float | IntegerLike;
export class Float extends Constant {
public readonly man: bigint;
public readonly exp: bigint;
constructor(man: IntegerLike, exp: IntegerLike = 0) {
super();
man = makeBigInt(man);
exp = makeBigInt(exp);
while (man % 10n === 0n) {
man /= 10n;
exp += 1n;
}
this.man = man;
this.exp = exp;
}
public add(other: FloatLike) {
const otherDec = makeFloat(other);
if (otherDec.exp === this.exp) return new Float(this.man + otherDec.man, this.exp);
}
public equals(other: FloatLike) {
const otherDec = makeFloat(other);
return otherDec.exp === this.exp && otherDec.man === this.man;
}
public gt(other: FloatLike) {
const otherDec = makeFloat(other);
if (otherDec.exp > this.exp) return true;
if (otherDec.exp < this.exp) return false;
return otherDec.man > this.man;
}
public toString(): string {
let str = this.man.toString();
if (this.exp < 0) {
const arr = str.split('');
arr.splice(str.length + Number(this.exp), 0, '.');
str = arr.join('');
} else if (this.exp > 0) {
for (let i = 0n; i < this.exp; i++) {
str += '0';
}
}
return str;
}
}
function makeFloat(value: FloatLike) {
if (value instanceof Float) return value;
if (typeof value === 'number') value = value.toString();
if (typeof value === 'string') {
if (!value.includes('.')) value += '.0';
}
return new Float(value);
}

3
src/const/index.ts Normal file
View File

@ -0,0 +1,3 @@
export * from './const';
export * from './float';
export * from './integer';

129
src/const/integer.test.ts Normal file
View File

@ -0,0 +1,129 @@
import { test, expect } from 'bun:test';
import { Integer } from './integer';
const ITERATIONS = 5000 as const;
function getRandomBool(): boolean {
return Math.random() < 0.5;
}
function getRandomInt(factor: number = 500): number {
return Math.floor(Math.random() * factor);
}
test('Integer.add', () => {
for (let i = ITERATIONS; i > 0; i--) {
const [aSign, bSign] = [getRandomBool(), getRandomBool()];
const [aVal, bVal] = [getRandomInt(), getRandomInt()];
const a = aSign ? -aVal : aVal;
const b = bSign ? -bVal : bVal;
const expected = a + b;
const result = new Integer(BigInt(a)).add(new Integer(BigInt(b)));
expect(result.toString(), `${a} + ${b}; expected: ${expected}; got: ${result}`).toBe(expected.toString());
}
});
test('Integer.sub', () => {
for (let i = ITERATIONS; i > 0; i--) {
const [aSign, bSign] = [getRandomBool(), getRandomBool()];
const [aVal, bVal] = [getRandomInt(), getRandomInt()];
const a = aSign ? -aVal : aVal;
const b = bSign ? -bVal : bVal;
const expected = a - b;
const result = new Integer(a).sub(new Integer(b));
expect(result.toString(), `${a} - ${b}; expected: ${expected}; got: ${result}`).toBe(expected.toString());
}
});
test('Integer.mul', () => {
for (let i = ITERATIONS; i > 0; i--) {
const [aSign, bSign] = [getRandomBool(), getRandomBool()];
const [aVal, bVal] = [getRandomInt(), getRandomInt()];
const a = aSign ? -aVal : aVal;
const b = bSign ? -bVal : bVal;
const expected = a * b;
const result = new Integer(a).mul(new Integer(b));
expect(result.toString(), `${a} * ${b}; expected: ${expected}; got: ${result}`).toBe(expected.toString());
}
});
test('Integer.div', () => {
for (let i = ITERATIONS; i > 0; i--) {
const [aSign, bSign] = [getRandomBool(), getRandomBool()];
const [aVal, bVal] = [getRandomInt(), getRandomInt()];
if (bVal === 0) continue;
const a = BigInt(aSign ? -aVal : aVal);
const b = BigInt(bSign ? -bVal : bVal);
const expected = a / b;
const result = new Integer(a).div(new Integer(b));
expect(result.toString(), `${a} / ${b}; expected: ${expected}; got: ${result}`).toBe(expected.toString());
}
});
test('Integer.mod', () => {
for (let i = ITERATIONS; i > 0; i--) {
const [aSign, bSign] = [getRandomBool(), getRandomBool()];
const [aVal, bVal] = [getRandomInt(), getRandomInt()];
if (bVal === 0) continue;
const a = aSign ? -aVal : aVal;
const b = bSign ? -bVal : bVal;
const expected = a % b;
const result = new Integer(a).mod(new Integer(b));
expect(result.toString(), `${a} % ${b}; expected: ${expected}; got: ${result}`).toBe(expected.toString());
}
});
test('Integer.pow', () => {
for (let i = ITERATIONS; i > 0; i--) {
const aSign = getRandomBool();
const [aVal, b] = [getRandomInt(50), getRandomInt(10)];
const a = aSign ? -aVal : aVal;
const expected = a ** b;
const result = new Integer(a).pow(new Integer(b));
expect(result.toString(), `${a} ** ${b}; expected: ${expected}; got: ${result}`).toBe(expected.toString());
}
});
test('Integer.gcd', () => {
const TEST_CASES = [
[48, 18, 6],
[0, 0, 0],
[0, 5, 5],
[7, 0, 7],
[101, 103, 1],
[54, 24, 6],
[36, 60, 12],
[81, 27, 27],
[17, 19, 1],
[100, 80, 20],
[1000000, 2, 2],
[-48, 18, 6],
[48, -18, 6],
[-48, -18, 6],
[-7, 0, 7],
[0, -5, 5],
[-101, 103, 1],
[-54, -24, 6]
] as const;
for (const [a, b, expected] of TEST_CASES) {
const result = new Integer(a).gcd(b);
expect(result.toString()).toBe(expected.toString());
}
});

158
src/const/integer.ts Normal file
View File

@ -0,0 +1,158 @@
import { Constant } from "./const";
const BINARY_DIGITS = [
'0',
'1',
] as const;
const OCTAL_DIGITS = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
] as const;
const HEX_DIGITS = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'a',
'b',
'c',
'd',
'e',
'f',
] as const;
export type IntegerLike = Integer | bigint | number | string;
export class Integer extends Constant {
public readonly value: bigint;
constructor(value: IntegerLike) {
super();
this.value = makeBigInt(value);
}
private static fromSystem(value: string, methodName: string, prefix: string, digits: readonly string[]): Integer {
value = value.toLowerCase();
if (value.startsWith(prefix)) value = value.slice(prefix.length);
if (!value.split('').every(c => digits.includes(c as any))) throw new Error(`Integer.${methodName}(...) called with an invalid value`);
return new Integer(BigInt(prefix + value));
}
static fromBinary(value: string): Integer {
return Integer.fromSystem(value, 'fromBinary', '0b', BINARY_DIGITS);
}
static fromOctal(value: string): Integer {
return Integer.fromSystem(value, 'fromOctal', '0o', OCTAL_DIGITS);
}
static fromHex(value: string): Integer {
return Integer.fromSystem(value, 'fromHex', '0x', HEX_DIGITS);
}
public add(other: IntegerLike): Integer {
return new Integer(this.value + makeBigInt(other));
}
public sub(other: IntegerLike): Integer {
return new Integer(this.value - makeBigInt(other));
}
public mul(other: IntegerLike): Integer {
return new Integer(this.value * makeBigInt(other));
}
public div(other: IntegerLike): Integer {
if (makeBigInt(other) === 0n) throw new Error('Integer.div(...) called with divisor of zero');
return new Integer(this.value / makeBigInt(other));
}
public mod(other: IntegerLike): Integer {
if (makeBigInt(other) === 0n) throw new Error('Integer.mod(...) called with divisor of zero');
return new Integer(this.value % makeBigInt(other));
}
public pow(other: IntegerLike): Integer {
if (makeBigInt(other) < 0n) throw new Error('Integer.pow(...) called with negative exponent');
return new Integer(this.value ** makeBigInt(other));
}
public eq(other: IntegerLike): boolean {
return this.value === makeBigInt(other);
}
public gt(other: IntegerLike): boolean {
return this.value > makeBigInt(other);
}
public ge(other: IntegerLike): boolean {
return this.value >= makeBigInt(other);
}
public lt(other: IntegerLike): boolean {
return this.value < makeBigInt(other);
}
public le(other: IntegerLike): boolean {
return this.value <= makeBigInt(other);
}
public gcd(other: IntegerLike): Integer {
let a = this.value;
let b = makeBigInt(other);
if (a < 0n) a = -a;
if (b < 0n) b = -b;
if (a === 0n) return new Integer(b);
if (b === 0n) return new Integer(a);
let shift = 0n;
while (((a | b) & 1n) == 0n) {
shift++;
a >>= 1n;
b >>= 1n;
}
while (b != 0n) {
while ((b & 1n) == 0n) b >>= 1n;
if (a > b) [a, b] = [b, a];
b -= a;
}
let result = a << shift;
if (result < 0n) result = -result;
return new Integer(result);
}
public toString(): string {
return this.value.toString();
}
}
export function makeBigInt(value: IntegerLike) {
if (value instanceof Integer) return value.value;
return BigInt(value);
}
// function makeInt(value: IntegerLike): Integer {
// if (value instanceof Integer) return value;
// return new Integer(value);
// }

38
src/errors.ts Normal file
View File

@ -0,0 +1,38 @@
import type { SourceRange } from "./frontend/ast/node";
import { getFile } from "./io/files";
const errors: [SourceRange, string][] = []
export function raiseError(sourceRange: SourceRange, message: string) {
errors.push([sourceRange, message]);
}
export function hasErrors() {
return errors.length > 0;
}
export function getErrors() {
return [...errors];
}
function getPreviousLines(content: string, line: number, count: number = 6) {
let lines = content.split("\n");
lines = lines.slice(Math.max(0, line - count), line);
while (!lines[0].trim().length) lines.shift();
while (!lines[lines.length - 1].trim().length) lines.pop();
return lines.join('\n');
}
export function printErrors() {
for (let i = 0; i < errors.length; i++) {
const [sourceRange, message] = errors[i];
const { path, content } = getFile(sourceRange.file);
const ctx = getPreviousLines(content, sourceRange.start[0]);
process.stderr.write(`in ${path}:${sourceRange.start[0]}:${sourceRange.start[1] + 1}\n`);
process.stderr.write('\x1b[90m' + ctx + '\x1b[0m\n');
process.stderr.write('\x1b[91m' + message + '\x1b[0m\n');
if (i < errors.length - 1) process.stderr.write('\n');
}
}

View File

@ -0,0 +1,9 @@
export * from './block';
export * from './decl';
export * from './expr';
export * from './module';
export * from './node';
export * from './stmt';
export * from './type-name';
export * from './visitor';

13
src/frontend/ast/block.ts Normal file
View File

@ -0,0 +1,13 @@
import { Node, SourceRange } from "./node";
import type { Stmt } from "./stmt";
import type { Visitor } from "./visitor";
export class Block extends Node {
constructor(sourceRange: SourceRange, public stmts: Stmt[]) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitBlock(this);
}
}

165
src/frontend/ast/decl.ts Normal file
View File

@ -0,0 +1,165 @@
import type { Block } from "./block";
import type { Expr } from "./expr";
import { Node, SourceRange } from "./node";
import { Stmt } from "./stmt";
import type { TypeName } from "./type-name";
import type { Visitor } from "./visitor";
export abstract class Decl extends Stmt { }
export class VarDecl extends Decl {
constructor(
sourceRange: SourceRange,
public name: string,
public typeName: TypeName | null,
public value: Expr,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitVarDecl(this);
}
}
export class FunDecl extends Decl {
constructor(
sourceRange: SourceRange,
public name: string,
public returnTypeName: TypeName | null,
public params: FunParam[],
public body: Block,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitFunDecl(this);
}
}
export class FunParam extends Node {
constructor(
sourceRange: SourceRange,
public name: string,
public typeName: TypeName,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitFunParam(this);
}
}
export class TraitDecl extends Decl {
constructor(
sourceRange: SourceRange,
public name: string,
public typeParams: TraitTypeParam[],
public attrs: TraitAttr[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitTraitDecl(this);
}
}
export class TraitAttr extends Node {
constructor(
sourceRange: SourceRange,
public isConst: boolean,
public name: string,
public params: FunParam[],
public returnTypeName: TypeName,
public defaultBody: Block | null,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitTraitAttr(this);
}
}
export class TraitTypeParam extends Node {
constructor(
sourceRange: SourceRange,
public name: string,
public bound: TypeName | null,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitTraitTypeParam(this);
}
}
export class ImplDecl extends Decl {
constructor(
sourceRange: SourceRange,
public traitName: string,
public traitTypeArgs: TypeName[],
public structName: string,
public structTypeParams: string[],
public attrs: ImplAttr[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitImplDecl(this);
}
}
export class ImplAttr extends Decl {
constructor(
sourceRange: SourceRange,
public name: string,
public paramNames: string[],
public body: Block,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitImplAttr(this);
}
}
export class StructDecl extends Decl {
constructor(
sourceRange: SourceRange,
public name: string,
public typeParams: StructTypeParam[],
public fields: StructField[],
public initializers: StructInitializer[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitStructDecl(this);
}
}
export class StructField extends Node {
constructor(
sourceRange: SourceRange,
public name: string,
public typeName: TypeName,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitStructField(this);
}
}
export class StructInitializer extends Node {
constructor(
sourceRange: SourceRange,
public params: FunParam[],
public body: Block,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitStructInitializer(this);
}
}
export class StructTypeParam extends Node {
constructor(
sourceRange: SourceRange,
public name: string,
public bound: TypeName | null,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitStructTypeParam(this);
}
}

112
src/frontend/ast/expr.ts Normal file
View File

@ -0,0 +1,112 @@
import { Node, SourceRange } from "./node";
import type { Visitor } from "./visitor";
import type { FunParam } from "./decl";
import type { Block } from "./block";
import type { Float, Integer } from "~/const";
export abstract class Expr extends Node { }
export class Identifier extends Expr {
constructor(sourceRange: SourceRange, public paths: string[]) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitIdentifier(this);
}
}
export class IntExpr extends Expr {
constructor(sourceRange: SourceRange, public value: Integer) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitIntExpr(this);
}
}
export class FloatExpr extends Expr {
constructor(sourceRange: SourceRange, public value: Float) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitFloatExpr(this);
}
}
export class StringExpr extends Expr {
constructor(sourceRange: SourceRange, public value: string) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitStringExpr(this);
}
}
export class BinExpr extends Expr {
constructor(
sourceRange: SourceRange,
public op: string,
public lhs: Expr,
public rhs: Expr
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitBinExpr(this);
}
}
export class CallExpr extends Expr {
constructor(
sourceRange: SourceRange,
public callee: Expr,
public args: Expr[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitCallExpr(this);
}
}
export class CondExpr extends Expr {
constructor(
sourceRange: SourceRange,
public cond: Expr,
public then: Expr,
public els: Expr,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitCondExpr(this);
}
}
export class LambdaExpr extends Expr {
constructor(
sourceRange: SourceRange,
public params: FunParam[],
public body: Block,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitLambdaExpr(this);
}
}
export class AssignExpr extends Expr {
constructor(
sourceRange: SourceRange,
public lval: Expr,
public rval: Expr,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitAssignExpr(this);
}
}

View File

@ -0,0 +1,3 @@
import * as AST from './_index';
export default AST;

View File

@ -0,0 +1,37 @@
import type { Decl } from "./decl";
import { Node, SourceRange } from "./node";
import { Stmt } from "./stmt";
import type { Visitor } from "./visitor";
export class Module extends Node {
constructor(
sourceRange: SourceRange,
public moduleDecl: ModuleDecl,
public imports: ImportStmt[],
public decls: Decl[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitModule(this);
}
}
export class ModuleDecl extends Stmt {
constructor(sourceRange: SourceRange, public path: string[]) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitModuleDecl(this);
}
}
export class ImportStmt extends Stmt {
constructor(sourceRange: SourceRange, public path: string[]) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitImportStmt(this);
}
}

15
src/frontend/ast/node.ts Normal file
View File

@ -0,0 +1,15 @@
import type { Visitor } from "./visitor";
export abstract class Node {
constructor(public sourceRange: SourceRange) { }
abstract accept<ResultT>(visitor: Visitor<ResultT>): ResultT;
}
export class SourceRange {
constructor(
public file: string,
public start: [number, number],
public end: [number, number],
) { }
}

63
src/frontend/ast/stmt.ts Normal file
View File

@ -0,0 +1,63 @@
import type { Block } from "./block";
import type { Expr } from "./expr";
import { Node, SourceRange } from "./node";
import type { Visitor } from "./visitor";
export abstract class Stmt extends Node { }
export class ExprStmt extends Stmt {
constructor(sourceRange: SourceRange, public expr: Expr) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitExprStmt(this);
}
}
export class ReturnStmt extends Stmt {
constructor(sourceRange: SourceRange, public value: Expr | null) {
super(sourceRange);
}
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitReturnStmt(this);
}
}
export class IfStmt extends Stmt {
constructor(
sourceRange: SourceRange,
public cond: Expr,
public then: Block,
public els: Block | null
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitIfStmt(this);
}
}
export class WhileStmt extends Stmt {
constructor(
sourceRange: SourceRange,
public cond: Expr,
public body: Block
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitWhileStmt(this);
}
}
export class RaiseStmt extends Stmt {
constructor(
sourceRange: SourceRange,
public value: Expr
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitRaiseStmt(this);
}
}

View File

@ -0,0 +1,73 @@
import type { Expr } from "./expr";
import { Node, SourceRange } from "./node";
import type { Visitor } from "./visitor";
export abstract class TypeName extends Node { }
export class NamedTypeName extends TypeName {
constructor(
sourceRange: SourceRange,
public name: string,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitNamedTypeName(this);
}
}
export class GenericTypeName extends TypeName {
constructor(
sourceRange: SourceRange,
public name: string,
public args: TypeName[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitGenericTypeName(this);
}
}
export class NullableTypeName extends TypeName {
constructor(
sourceRange: SourceRange,
public typeName: TypeName,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitNullableTypeName(this);
}
}
export class ArrayTypeName extends TypeName {
constructor(
sourceRange: SourceRange,
public typeName: TypeName,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitArrayTypeName(this);
}
}
export class TupleTypeName extends TypeName {
constructor(
sourceRange: SourceRange,
public typeNames: TypeName[],
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitTupleTypeName(this);
}
}
export class LambdaTypeName extends TypeName {
constructor(
sourceRange: SourceRange,
public params: [string | null, TypeName][],
public returnTypeName: TypeName,
) { super(sourceRange); }
accept<ResultT>(visitor: Visitor<ResultT>): ResultT {
return visitor.visitLambdaTypeName(this);
}
}

245
src/frontend/ast/visitor.ts Normal file
View File

@ -0,0 +1,245 @@
import type { Block } from "./block";
import type { FunDecl, FunParam, ImplAttr, ImplDecl, StructDecl, StructField, StructInitializer, StructTypeParam, TraitAttr, TraitDecl, TraitTypeParam, VarDecl } from "./decl";
import type { AssignExpr, BinExpr, CallExpr, CondExpr, FloatExpr, Identifier, IntExpr, LambdaExpr, StringExpr } from "./expr";
import type { ImportStmt, Module, ModuleDecl } from "./module";
import { IfStmt, RaiseStmt, ReturnStmt, WhileStmt, type ExprStmt } from "./stmt";
import type { ArrayTypeName, GenericTypeName, LambdaTypeName, NamedTypeName, NullableTypeName, TupleTypeName } from "./type-name";
export abstract class Visitor<ResultT = any> {
public abstract visitModule(module_: Module): ResultT;
public abstract visitModuleDecl(moduleDecl: ModuleDecl): ResultT;
public abstract visitImportStmt(importStmt: ImportStmt): ResultT;
public abstract visitBlock(block: Block): ResultT;
public abstract visitExprStmt(exprStmt: ExprStmt): ResultT;
public abstract visitReturnStmt(returnStmt: ReturnStmt): ResultT;
public abstract visitIfStmt(ifStmt: IfStmt): ResultT;
public abstract visitWhileStmt(whileStmt: WhileStmt): ResultT;
public abstract visitRaiseStmt(raiseStmt: RaiseStmt): ResultT;
public abstract visitVarDecl(varDecl: VarDecl): ResultT;
public abstract visitFunDecl(funDecl: FunDecl): ResultT;
public abstract visitFunParam(funParam: FunParam): ResultT;
public abstract visitTraitDecl(traitDecl: TraitDecl): ResultT;
public abstract visitTraitAttr(traitAttr: TraitAttr): ResultT;
public abstract visitTraitTypeParam(traitTypeParam: TraitTypeParam): ResultT;
public abstract visitImplDecl(implDecl: ImplDecl): ResultT;
public abstract visitImplAttr(implAttr: ImplAttr): ResultT;
public abstract visitStructDecl(structDecl: StructDecl): ResultT;
public abstract visitStructField(structField: StructField): ResultT;
public abstract visitStructTypeParam(structTypeParam: StructTypeParam): ResultT;
public abstract visitStructInitializer(structInitializer: StructInitializer): ResultT;
public abstract visitIdentifier(identifier: Identifier): ResultT;
public abstract visitIntExpr(intExpr: IntExpr): ResultT;
public abstract visitFloatExpr(floatExpr: FloatExpr): ResultT;
public abstract visitStringExpr(stringExpr: StringExpr): ResultT;
public abstract visitBinExpr(binExpr: BinExpr): ResultT;
public abstract visitCallExpr(callExpr: CallExpr): ResultT;
public abstract visitCondExpr(condExpr: CondExpr): ResultT;
public abstract visitLambdaExpr(lambdaExpr: LambdaExpr): ResultT;
public abstract visitAssignExpr(assignExpr: AssignExpr): ResultT;
public abstract visitNamedTypeName(namedTypeName: NamedTypeName): ResultT;
public abstract visitGenericTypeName(genericTypeName: GenericTypeName): ResultT;
public abstract visitNullableTypeName(nullableTypeName: NullableTypeName): ResultT;
public abstract visitArrayTypeName(arrayTypeName: ArrayTypeName): ResultT;
public abstract visitTupleTypeName(tupleTypeName: TupleTypeName): ResultT;
public abstract visitLambdaTypeName(lambdaTypeName: LambdaTypeName): ResultT;
}
export abstract class BaseVisitor<ResultT = any> implements Visitor<ResultT> {
public visitModule(module_: Module): ResultT {
module_.imports.forEach((import_) => import_.accept(this));
module_.decls.forEach((decl) => decl.accept(this));
return null as any;
}
public visitModuleDecl(moduleDecl: ModuleDecl): ResultT {
return null as any;
}
public visitImportStmt(importStmt: ImportStmt): ResultT {
return null as any;
}
public visitBlock(block: Block): ResultT {
block.stmts.forEach((stmt) => stmt.accept(this));
return null as any;
}
public visitExprStmt(exprStmt: ExprStmt): ResultT {
exprStmt.expr.accept(this);
return null as any;
}
public visitReturnStmt(returnStmt: ReturnStmt): ResultT {
returnStmt.value?.accept(this);
return null as any;
}
public visitIfStmt(ifStmt: IfStmt): ResultT {
ifStmt.cond.accept(this);
ifStmt.then.accept(this);
ifStmt.els?.accept(this);
return null as any;
}
public visitWhileStmt(whileStmt: WhileStmt): ResultT {
whileStmt.cond.accept(this);
whileStmt.body.accept(this);
return null as any;
}
public visitRaiseStmt(raiseStmt: RaiseStmt): ResultT {
raiseStmt.value.accept(this);
return null as any;
}
public visitVarDecl(varDecl: VarDecl): ResultT {
varDecl.typeName?.accept(this);
varDecl.value.accept(this);
return null as any;
}
public visitFunDecl(funDecl: FunDecl): ResultT {
funDecl.returnTypeName?.accept(this);
funDecl.params.forEach((param) => param.accept(this));
funDecl.body.accept(this);
return null as any;
}
public visitFunParam(funParam: FunParam): ResultT {
funParam.typeName.accept(this);
return null as any;
}
public visitTraitDecl(traitDecl: TraitDecl): ResultT {
traitDecl.typeParams.forEach((typeParam) => typeParam.accept(this));
traitDecl.attrs.forEach((attr) => attr.accept(this));
return null as any;
}
public visitTraitAttr(traitAttr: TraitAttr): ResultT {
traitAttr.params.forEach((param) => param.accept(this));
traitAttr.returnTypeName.accept(this);
traitAttr.defaultBody?.accept(this);
return null as any;
}
public visitTraitTypeParam(traitTypeParam: TraitTypeParam): ResultT {
traitTypeParam.bound?.accept(this);
return null as any;
}
public visitImplDecl(implDecl: ImplDecl): ResultT {
implDecl.traitTypeArgs.forEach((traitTypeArg) => traitTypeArg.accept(this));
implDecl.attrs.forEach((attr) => attr.accept(this));
return null as any;
}
public visitImplAttr(implAttr: ImplAttr): ResultT {
implAttr.body.accept(this);
return null as any;
}
public visitStructDecl(structDecl: StructDecl): ResultT {
structDecl.typeParams.forEach((typeParam) => typeParam.accept(this));
structDecl.fields.forEach((field) => field.accept(this));
return null as any;
}
public visitStructTypeParam(structTypeParam: StructTypeParam): ResultT {
structTypeParam.bound?.accept(this);
return null as any;
}
public visitStructField(structField: StructField): ResultT {
structField.typeName.accept(this);
return null as any;
}
public visitStructInitializer(structInitializer: StructInitializer): ResultT {
structInitializer.params.forEach((param) => param.accept(this));
structInitializer.body.accept(this);
return null as any;
}
public visitIdentifier(identifier: Identifier): ResultT {
return null as any;
}
public visitIntExpr(intExpr: IntExpr): ResultT {
return null as any;
}
public visitFloatExpr(floatExpr: FloatExpr): ResultT {
return null as any;
}
public visitStringExpr(stringExpr: StringExpr): ResultT {
return null as any;
}
public visitBinExpr(binExpr: BinExpr): ResultT {
binExpr.lhs.accept(this);
binExpr.rhs.accept(this);
return null as any;
}
public visitCallExpr(callExpr: CallExpr): ResultT {
callExpr.callee.accept(this);
callExpr.args.forEach((arg) => arg.accept(this));
return null as any;
}
public visitCondExpr(condExpr: CondExpr): ResultT {
condExpr.cond.accept(this);
condExpr.then.accept(this);
condExpr.els.accept(this);
return null as any;
}
public visitLambdaExpr(lambdaExpr: LambdaExpr): ResultT {
lambdaExpr.params.forEach((param) => param.accept(this));
lambdaExpr.body.accept(this);
return null as any;
}
public visitAssignExpr(assignExpr: AssignExpr): ResultT {
assignExpr.lval.accept(this);
assignExpr.rval.accept(this);
return null as any;
}
public visitNamedTypeName(namedTypeName: NamedTypeName): ResultT {
return null as any;
}
public visitGenericTypeName(genericTypeName: GenericTypeName): ResultT {
genericTypeName.args.forEach((arg) => arg.accept(this));
return null as any;
}
public visitNullableTypeName(nullableTypeName: NullableTypeName): ResultT {
nullableTypeName.typeName.accept(this);
return null as any;
}
public visitArrayTypeName(arrayTypeName: ArrayTypeName): ResultT {
arrayTypeName.typeName.accept(this);
return null as any;
}
public visitTupleTypeName(tupleTypeName: TupleTypeName): ResultT {
tupleTypeName.typeNames.forEach((typeName) => typeName.accept(this));
return null as any;
}
public visitLambdaTypeName(lambdaTypeName: LambdaTypeName): ResultT {
lambdaTypeName.params.forEach((param) => param[1].accept(this));
lambdaTypeName.returnTypeName.accept(this);
return null as any;
}
}

View File

View File

@ -0,0 +1,45 @@
export abstract class Type {
}
export class OpaqueType extends Type {
constructor() { super(); }
}
export class StructType extends Type {
constructor(
public name: string,
public fields: [string, Type][],
) { super(); }
public hasField(field: string) {
for (const [name] of this.fields) {
if (name === field) return true;
}
return false;
}
public getFieldType(field: string) {
for (const [name, type] of this.fields) {
if (name === field) return type;
}
throw new Error(`StructType.getFieldType(...): field '${field}' not found'`);
}
public getFieldOffset(field: string) {
for (let i = 0; i < this.fields.length; i++) {
if (this.fields[i][0] === field) return i;
}
throw new Error(`StructType.getFieldOffset(...): field '${field}' not found'`);
}
}
export class TraitType extends Type {
constructor(
public name: string,
public args: Type[],
) { super(); }
}

20
src/io/files.ts Normal file
View File

@ -0,0 +1,20 @@
import fs from 'fs';
import path from 'path';
const files: Record<string, string> = {};
type File = {
path: string;
content: string;
};
export function getFile(filePath: string): File {
const abspath = path.normalize(path.resolve(filePath));
if (files[abspath]) return { path: abspath, content: files[abspath] };
const content = fs.readFileSync(abspath, 'utf8');
files[abspath] = content;
return { path: abspath, content };
}

8
src/main.ts Normal file
View File

@ -0,0 +1,8 @@
import { inspect } from "bun";
import { parseModule } from "./parser";
import { printErrors } from "./errors";
const module = parseModule('examples/test.plsm');
console.log(inspect(module, { depth: 1000, colors: false }));
printErrors();

33
src/parser.ts Normal file
View File

@ -0,0 +1,33 @@
import plsmLexer from "~/antlr/plsmLexer";
import plsmParser from "~/antlr/plsmParser";
import AST from "./frontend/ast/index";
import { CharStream, CommonToken, CommonTokenStream, ErrorListener, InputStream, RecognitionException, Recognizer, TokenStream } from 'antlr4';
import { getFile } from "./io/files";
import { SourceRange } from "./frontend/ast/node";
import { raiseError } from "./errors";
class MyErrorListener extends ErrorListener<any> {
constructor(private path: string) {
super();
}
syntaxError(recognizer: Recognizer<any>, offendingSymbol: any, line: number, column: number, msg: string, e: RecognitionException | undefined): void {
const sourceRange = new SourceRange(this.path, [line, column], [line, column + offendingSymbol.text.length]);
raiseError(sourceRange, msg);
}
}
export function parseModule(moduleFilePath: string): AST.Module {
const { content, path } = getFile(moduleFilePath);
const charStream = new CharStream(content);
const lexer = new plsmLexer(charStream);
const tokenStream = new CommonTokenStream(lexer);
const parser = new plsmParser(tokenStream);
parser.setFileName(path);
parser.removeErrorListeners();
parser.addErrorListener(new MyErrorListener(path));
return parser.module_().ast;
}

32
tsconfig.json Normal file
View File

@ -0,0 +1,32 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
}
}
}