initial commit
This commit is contained in:
commit
3317626540
42
.gitignore
vendored
Normal file
42
.gitignore
vendored
Normal 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
15
README.md
Normal 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
15
antlr4-tool
Executable 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
32
bun.lock
Normal 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
70
examples/test.plsm
Normal 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!";
|
15
package.json
Normal file
15
package.json
Normal 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
448
src/antlr/plsm.g4
Normal 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
1
src/const/const.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export abstract class Constant { }
|
72
src/const/float.ts
Normal file
72
src/const/float.ts
Normal 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
3
src/const/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './const';
|
||||||
|
export * from './float';
|
||||||
|
export * from './integer';
|
129
src/const/integer.test.ts
Normal file
129
src/const/integer.test.ts
Normal 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
158
src/const/integer.ts
Normal 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
38
src/errors.ts
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
9
src/frontend/ast/_index.ts
Normal file
9
src/frontend/ast/_index.ts
Normal 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
13
src/frontend/ast/block.ts
Normal 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
165
src/frontend/ast/decl.ts
Normal 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
112
src/frontend/ast/expr.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
3
src/frontend/ast/index.ts
Normal file
3
src/frontend/ast/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as AST from './_index';
|
||||||
|
|
||||||
|
export default AST;
|
37
src/frontend/ast/module.ts
Normal file
37
src/frontend/ast/module.ts
Normal 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
15
src/frontend/ast/node.ts
Normal 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
63
src/frontend/ast/stmt.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
73
src/frontend/ast/type-name.ts
Normal file
73
src/frontend/ast/type-name.ts
Normal 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
245
src/frontend/ast/visitor.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
0
src/frontend/semantics/symbol.ts
Normal file
0
src/frontend/semantics/symbol.ts
Normal file
45
src/frontend/semantics/type.ts
Normal file
45
src/frontend/semantics/type.ts
Normal 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
20
src/io/files.ts
Normal 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
8
src/main.ts
Normal 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
33
src/parser.ts
Normal 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
32
tsconfig.json
Normal 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/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user