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