fax-lang 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/package.json +6 -6
- package/src/index.ts +1 -1
- package/CNAME +0 -1
- package/dist/index.js +0 -88
- package/dist/modules/analysis/checker.js +0 -142
- package/dist/modules/analysis/memory-manager.js +0 -76
- package/dist/modules/analysis/scopes/scope-manager.js +0 -26
- package/dist/modules/backend/native/arm64.js +0 -28
- package/dist/modules/backend/native/codegen.js +0 -65
- package/dist/modules/backend/native/elf.js +0 -57
- package/dist/modules/codegen/index.js +0 -244
- package/dist/modules/lexer/constants/keywords.js +0 -36
- package/dist/modules/lexer/index.js +0 -300
- package/dist/modules/lexer/types.js +0 -80
- package/dist/modules/llvm/builder.js +0 -73
- package/dist/modules/llvm/module.js +0 -39
- package/dist/modules/llvm/types.js +0 -22
- package/dist/modules/parser/ast/nodes.js +0 -16
- package/dist/modules/parser/index.js +0 -413
- package/dist/shared/errors/compiler-error.js +0 -16
- package/dist/shared/logger/error-reporter.js +0 -41
- package/dist/shared/logger/index.js +0 -45
package/README.md
CHANGED
|
@@ -52,6 +52,15 @@ The compiler automatically analyzes access patterns to decide whether data shoul
|
|
|
52
52
|
- **LLVM** (llc & clang v14/18)
|
|
53
53
|
|
|
54
54
|
### Installation
|
|
55
|
+
|
|
56
|
+
You can install the Fax-lang compiler directly from **NPM**:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g fax-lang
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Or for local development:
|
|
63
|
+
|
|
55
64
|
```bash
|
|
56
65
|
git clone https://github.com/Luvion1/Fax-lang.git
|
|
57
66
|
cd Fax-lang
|
|
@@ -62,13 +71,16 @@ npm install
|
|
|
62
71
|
|
|
63
72
|
## 🚀 Usage
|
|
64
73
|
|
|
65
|
-
###
|
|
74
|
+
### Using the global command
|
|
75
|
+
If installed via NPM:
|
|
76
|
+
```bash
|
|
77
|
+
fax build examples/mvp.fx
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Compiling Programs (Local)
|
|
66
81
|
```bash
|
|
67
82
|
# Build a binary from a .fx file
|
|
68
83
|
npm run dev build examples/mvp.fx
|
|
69
|
-
|
|
70
|
-
# Execute the output
|
|
71
|
-
./examples/mvp
|
|
72
84
|
```
|
|
73
85
|
|
|
74
86
|
### AST Visualization
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fax-lang",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "The Fax Programming Language Reference Implementation",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "src/index.ts",
|
|
7
7
|
"bin": {
|
|
8
|
-
"fax": "./
|
|
8
|
+
"fax": "./src/index.ts"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"dev": "node --loader ts-node/esm src/index.ts",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^20.11.5",
|
|
37
|
-
"ts-node": "^10.9.2",
|
|
38
|
-
"typescript": "^5.3.3",
|
|
39
37
|
"vitest": "^1.2.1"
|
|
40
38
|
},
|
|
41
39
|
"dependencies": {
|
|
42
40
|
"chalk": "^5.3.0",
|
|
43
|
-
"commander": "^11.1.0"
|
|
41
|
+
"commander": "^11.1.0",
|
|
42
|
+
"ts-node": "^10.9.2",
|
|
43
|
+
"typescript": "^5.3.3"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/src/index.ts
CHANGED
package/CNAME
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
fax-lang.js.org
|
package/dist/index.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
|
-
import { Lexer } from './modules/lexer/index.js';
|
|
6
|
-
import { Parser } from './modules/parser/index.js';
|
|
7
|
-
import { CodeGenerator } from './modules/codegen/index.js';
|
|
8
|
-
import { SemanticChecker } from './modules/analysis/checker.js';
|
|
9
|
-
function findTool(names) {
|
|
10
|
-
for (const name of names) {
|
|
11
|
-
try {
|
|
12
|
-
const res = spawnSync(name, ['--version'], { stdio: 'ignore' });
|
|
13
|
-
if (res.status === 0)
|
|
14
|
-
return name;
|
|
15
|
-
}
|
|
16
|
-
catch (e) {
|
|
17
|
-
continue;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
async function main() {
|
|
23
|
-
const args = process.argv.slice(2);
|
|
24
|
-
const command = args[0];
|
|
25
|
-
if (args.length < 2 || (command !== 'ast' && command !== 'build')) {
|
|
26
|
-
console.log("Fax-lang Compiler v0.0.1");
|
|
27
|
-
console.log("Usage: fax build <file.fx>");
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
const file = args[1];
|
|
31
|
-
const filePath = path.resolve(process.cwd(), file);
|
|
32
|
-
const projectRoot = process.cwd();
|
|
33
|
-
if (!filePath.startsWith(projectRoot)) {
|
|
34
|
-
console.error(`Error: Access denied to path: ${filePath}`);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
if (!fs.existsSync(filePath)) {
|
|
38
|
-
console.error(`Error: File not found: ${filePath}`);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
const source = fs.readFileSync(filePath, 'utf-8');
|
|
42
|
-
const baseName = path.basename(file, '.fx');
|
|
43
|
-
const outputDir = path.dirname(filePath);
|
|
44
|
-
const outputBin = path.join(outputDir, baseName);
|
|
45
|
-
try {
|
|
46
|
-
const lexer = new Lexer(source);
|
|
47
|
-
const parser = new Parser(lexer);
|
|
48
|
-
const ast = parser.parseProgram();
|
|
49
|
-
if (parser.errors.length > 0) {
|
|
50
|
-
parser.errors.forEach(err => console.error(err));
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
if (command === 'ast') {
|
|
54
|
-
console.log(JSON.stringify(ast, (k, v) => (k === 'token' ? undefined : v), 2));
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const checker = new SemanticChecker();
|
|
58
|
-
checker.check(ast);
|
|
59
|
-
console.log(`\n[Fax] Compiling ${file}...`);
|
|
60
|
-
const codegen = new CodeGenerator(baseName);
|
|
61
|
-
const llvmIR = codegen.generate(ast);
|
|
62
|
-
const tempLl = path.join(outputDir, `.${baseName}.tmp.ll`);
|
|
63
|
-
const tempObj = path.join(outputDir, `.${baseName}.tmp.o`);
|
|
64
|
-
fs.writeFileSync(tempLl, llvmIR);
|
|
65
|
-
const llcBin = findTool(['llc-18', 'llc-14', 'llc']);
|
|
66
|
-
const clangBin = findTool(['clang-18', 'clang-14', 'clang']);
|
|
67
|
-
if (llcBin && clangBin) {
|
|
68
|
-
const llc = spawnSync(llcBin, ['-O3', '-filetype=obj', '-relocation-model=pic', '-o', tempObj, tempLl], { stdio: 'inherit' });
|
|
69
|
-
if (llc.status !== 0)
|
|
70
|
-
throw new Error("llc failed");
|
|
71
|
-
const clang = spawnSync(clangBin, ['-O3', '-fuse-ld=lld', '-o', outputBin, tempObj], { stdio: 'inherit' });
|
|
72
|
-
if (clang.status !== 0)
|
|
73
|
-
throw new Error("clang failed");
|
|
74
|
-
fs.unlinkSync(tempLl);
|
|
75
|
-
fs.unlinkSync(tempObj);
|
|
76
|
-
console.log(`✅ Build Successful: ${outputBin}`);
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
console.log("⚠️ Toolchain missing, IR saved.");
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
const { ErrorReporter } = await import('./shared/logger/error-reporter.js');
|
|
84
|
-
ErrorReporter.report(error, source, file);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
main();
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { MemoryManager } from "./memory-manager.js";
|
|
2
|
-
import { NodeType } from "../parser/ast/nodes.js";
|
|
3
|
-
import { CompilerError } from "../../shared/errors/compiler-error.js";
|
|
4
|
-
import { Logger } from "../../shared/logger/index.js";
|
|
5
|
-
export class SemanticChecker {
|
|
6
|
-
memManager = new MemoryManager();
|
|
7
|
-
functions = new Set();
|
|
8
|
-
check(ast) {
|
|
9
|
-
Logger.info("Validating Fax-lang Memory Rules...");
|
|
10
|
-
for (const stmt of ast.statements) {
|
|
11
|
-
if (stmt.type === NodeType.FunctionDeclaration) {
|
|
12
|
-
this.functions.add(stmt.name.value);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
for (const stmt of ast.statements) {
|
|
16
|
-
this.analyzeStatement(stmt);
|
|
17
|
-
}
|
|
18
|
-
Logger.success("All Memory Rules Satisfied.");
|
|
19
|
-
}
|
|
20
|
-
analyzeStatement(stmt) {
|
|
21
|
-
if (!stmt)
|
|
22
|
-
return;
|
|
23
|
-
switch (stmt.type) {
|
|
24
|
-
case NodeType.LetStatement:
|
|
25
|
-
const type = stmt.dataType || "i32";
|
|
26
|
-
this.memManager.analyzePlacement(stmt.name.value, type, 32, false, stmt.isMut || false);
|
|
27
|
-
this.analyzeExpression(stmt.value);
|
|
28
|
-
break;
|
|
29
|
-
case NodeType.FunctionDeclaration:
|
|
30
|
-
this.memManager.enterScope();
|
|
31
|
-
if (stmt.params) {
|
|
32
|
-
for (const p of stmt.params) {
|
|
33
|
-
const pType = p.dataType || "i32";
|
|
34
|
-
this.memManager.analyzePlacement(p.value, pType, 32, false, false);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
this.analyzeStatement(stmt.body);
|
|
38
|
-
this.memManager.exitScope();
|
|
39
|
-
break;
|
|
40
|
-
case "ShadowStatement":
|
|
41
|
-
const source = stmt.value.value;
|
|
42
|
-
const shadowName = stmt.name.value;
|
|
43
|
-
if (source === shadowName) {
|
|
44
|
-
throw new CompilerError("E005", `cannot shadow variable \`${source}\` with itself`, stmt.token.line, stmt.token.column, undefined, `Shadowing requires a new name to create a view. Try: \`shadow ${source}_view = ${source}\``);
|
|
45
|
-
}
|
|
46
|
-
if (!this.memManager.getMetadata(source)) {
|
|
47
|
-
throw new CompilerError("E003", `cannot shadow undefined variable \`${source}\``, stmt.token.line, stmt.token.column, undefined, `Variable \`${source}\` must be declared in the current scope before it can be shadowed.`);
|
|
48
|
-
}
|
|
49
|
-
this.memManager.createShadow(source, stmt.name.value);
|
|
50
|
-
break;
|
|
51
|
-
case "ConstStatement":
|
|
52
|
-
this.memManager.analyzePlacement(stmt.name.value, stmt.dataType || "i32", 32, true, false, true);
|
|
53
|
-
break;
|
|
54
|
-
case NodeType.BlockStatement:
|
|
55
|
-
this.memManager.enterScope();
|
|
56
|
-
for (const s of stmt.statements) {
|
|
57
|
-
this.analyzeStatement(s);
|
|
58
|
-
}
|
|
59
|
-
this.memManager.exitScope();
|
|
60
|
-
break;
|
|
61
|
-
case NodeType.StateMachine:
|
|
62
|
-
const validStates = new Set(stmt.states.map((s) => s.name.value));
|
|
63
|
-
const checkTransition = (t) => {
|
|
64
|
-
if (t.target && !validStates.has(t.target)) {
|
|
65
|
-
throw new CompilerError("E004", `transition targets undefined state \`${t.target}\``, t.token.line, t.token.column, undefined, `The state machine \`${stmt.name.value}\` does not have a state named \`${t.target}\`.`);
|
|
66
|
-
}
|
|
67
|
-
this.memManager.enterScope();
|
|
68
|
-
this.analyzeStatement(t.body);
|
|
69
|
-
this.memManager.exitScope();
|
|
70
|
-
};
|
|
71
|
-
stmt.states.forEach((state) => {
|
|
72
|
-
this.memManager.enterScope();
|
|
73
|
-
state.transitions.forEach((t) => checkTransition(t));
|
|
74
|
-
this.memManager.exitScope();
|
|
75
|
-
});
|
|
76
|
-
if (stmt.anyTransitions) {
|
|
77
|
-
stmt.anyTransitions.forEach((t) => checkTransition(t));
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
case NodeType.ReturnStatement:
|
|
81
|
-
this.analyzeExpression(stmt.returnValue);
|
|
82
|
-
break;
|
|
83
|
-
case NodeType.ExpressionStatement:
|
|
84
|
-
this.analyzeExpression(stmt.expression);
|
|
85
|
-
break;
|
|
86
|
-
case "WhileStatement":
|
|
87
|
-
this.analyzeExpression(stmt.condition);
|
|
88
|
-
this.analyzeStatement(stmt.body);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
analyzeExpression(expr) {
|
|
93
|
-
if (!expr)
|
|
94
|
-
return;
|
|
95
|
-
switch (expr.type) {
|
|
96
|
-
case NodeType.Identifier:
|
|
97
|
-
const meta = this.memManager.getMetadata(expr.value);
|
|
98
|
-
if (!meta && !this.functions.has(expr.value)) {
|
|
99
|
-
throw new CompilerError("E002", `use of exhausted or undefined variable \`${expr.value}\``, expr.token.line, expr.token.column, undefined, `Variable \`${expr.value}\` has no remaining Life-Force or is not defined in this scope.`);
|
|
100
|
-
}
|
|
101
|
-
if (meta) {
|
|
102
|
-
this.memManager.decay(expr.value, "read");
|
|
103
|
-
if (meta.lifeForce.current <= 0) {
|
|
104
|
-
throw new CompilerError("E002", `variable \`${expr.value}\` has been exhausted`, expr.token.line, expr.token.column, undefined, "Energy depleted. Reading from this variable costs 0.02 Life-Force.");
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
break;
|
|
108
|
-
case "AssignmentExpression":
|
|
109
|
-
const varName = expr.name.value;
|
|
110
|
-
const targetMeta = this.memManager.getMetadata(varName);
|
|
111
|
-
if (!targetMeta) {
|
|
112
|
-
throw new CompilerError("E002", `cannot assign to undefined variable \`${varName}\``, expr.token.line, expr.token.column);
|
|
113
|
-
}
|
|
114
|
-
if (!targetMeta.isMut) {
|
|
115
|
-
throw new CompilerError("E001", `cannot assign twice to immutable variable \`${varName}\``, expr.token.line, expr.token.column, undefined, `Make this variable mutable by writing \`let mut ${varName}\` instead of \`let ${varName}\`.`);
|
|
116
|
-
}
|
|
117
|
-
this.memManager.decay(varName, "write");
|
|
118
|
-
this.analyzeExpression(expr.value);
|
|
119
|
-
break;
|
|
120
|
-
case "IfExpression":
|
|
121
|
-
this.analyzeExpression(expr.condition);
|
|
122
|
-
this.analyzeStatement(expr.consequence);
|
|
123
|
-
if (expr.alternative) {
|
|
124
|
-
if (expr.alternative.type === "IfExpression") {
|
|
125
|
-
this.analyzeExpression(expr.alternative);
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
this.analyzeStatement(expr.alternative);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
break;
|
|
132
|
-
case NodeType.InfixExpression:
|
|
133
|
-
this.analyzeExpression(expr.left);
|
|
134
|
-
this.analyzeExpression(expr.right);
|
|
135
|
-
if (expr.operator === "&&" || expr.operator === "||") {
|
|
136
|
-
}
|
|
137
|
-
break;
|
|
138
|
-
case NodeType.IntegerLiteral:
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Scope } from "./scopes/scope-manager.js";
|
|
2
|
-
import { Logger } from "../../shared/logger/index.js";
|
|
3
|
-
export const MemoryPlacement = {
|
|
4
|
-
Stack: "Stack",
|
|
5
|
-
Heap: "Heap",
|
|
6
|
-
Hybrid: "Hybrid"
|
|
7
|
-
};
|
|
8
|
-
export class MemoryManager {
|
|
9
|
-
currentScope = new Scope();
|
|
10
|
-
enterScope() {
|
|
11
|
-
this.currentScope = this.currentScope.enterChild();
|
|
12
|
-
}
|
|
13
|
-
exitScope() {
|
|
14
|
-
if (this.currentScope.parent) {
|
|
15
|
-
this.currentScope = this.currentScope.parent;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
analyzePlacement(varName, type, size, isLongLived, isMut = false, isConst = false) {
|
|
19
|
-
let placement;
|
|
20
|
-
if (size <= 64 && !isLongLived) {
|
|
21
|
-
placement = MemoryPlacement.Stack;
|
|
22
|
-
}
|
|
23
|
-
else if (size > 1024) {
|
|
24
|
-
placement = MemoryPlacement.Heap;
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
placement = MemoryPlacement.Hybrid;
|
|
28
|
-
}
|
|
29
|
-
this.currentScope.define(varName, {
|
|
30
|
-
placement,
|
|
31
|
-
lifeForce: { current: 1.0 },
|
|
32
|
-
isShadow: false,
|
|
33
|
-
isMirror: false,
|
|
34
|
-
isMut,
|
|
35
|
-
isConst,
|
|
36
|
-
type,
|
|
37
|
-
associatedState: "Initial"
|
|
38
|
-
});
|
|
39
|
-
return placement;
|
|
40
|
-
}
|
|
41
|
-
decay(varName, type = "read") {
|
|
42
|
-
const meta = this.currentScope.lookup(varName);
|
|
43
|
-
if (meta && !meta.isConst) {
|
|
44
|
-
const rates = { read: 0.02, write: 0.08, heavy: 0.15 };
|
|
45
|
-
meta.lifeForce.current -= rates[type];
|
|
46
|
-
if (meta.lifeForce.current < 0)
|
|
47
|
-
meta.lifeForce.current = 0;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
boost(varName, amount = 0.1) {
|
|
51
|
-
const meta = this.currentScope.lookup(varName);
|
|
52
|
-
if (meta) {
|
|
53
|
-
meta.lifeForce.current = Math.min(1.0, meta.lifeForce.current + amount);
|
|
54
|
-
Logger.debug(`${varName} life-force boosted by ${amount}`, "Rule 2");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
updateState(varName, newState) {
|
|
58
|
-
const meta = this.currentScope.lookup(varName);
|
|
59
|
-
if (meta) {
|
|
60
|
-
meta.associatedState = newState;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
createShadow(sourceVar, shadowVar) {
|
|
64
|
-
const sourceMeta = this.currentScope.lookup(sourceVar);
|
|
65
|
-
if (sourceMeta) {
|
|
66
|
-
this.currentScope.define(shadowVar, {
|
|
67
|
-
...sourceMeta,
|
|
68
|
-
isShadow: true,
|
|
69
|
-
isMirror: false,
|
|
70
|
-
isMut: false
|
|
71
|
-
});
|
|
72
|
-
Logger.debug(`Shadow created: ${shadowVar} -> ${sourceVar}`, "Rule 1");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
getMetadata(varName) { return this.currentScope.lookup(varName); }
|
|
76
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export class Scope {
|
|
2
|
-
symbols = new Map();
|
|
3
|
-
parent = null;
|
|
4
|
-
constructor(parent = null) {
|
|
5
|
-
this.parent = parent;
|
|
6
|
-
}
|
|
7
|
-
define(name, value) {
|
|
8
|
-
if (this.symbols.has(name))
|
|
9
|
-
return false;
|
|
10
|
-
this.symbols.set(name, value);
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
lookup(name) {
|
|
14
|
-
let current = this;
|
|
15
|
-
while (current) {
|
|
16
|
-
const sym = current.symbols.get(name);
|
|
17
|
-
if (sym !== undefined)
|
|
18
|
-
return sym;
|
|
19
|
-
current = current.parent;
|
|
20
|
-
}
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
enterChild() {
|
|
24
|
-
return new Scope(this);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
function instr(val) {
|
|
2
|
-
const buf = Buffer.alloc(4);
|
|
3
|
-
buf.writeUInt32LE(val, 0);
|
|
4
|
-
return buf;
|
|
5
|
-
}
|
|
6
|
-
export class Arm64Emitter {
|
|
7
|
-
buffer = [];
|
|
8
|
-
emitSvc0() {
|
|
9
|
-
this.buffer.push(instr(0xD4000001));
|
|
10
|
-
}
|
|
11
|
-
emitMovX0(imm) {
|
|
12
|
-
if (imm > 65535) {
|
|
13
|
-
throw new Error("MVP only supports 16-bit integers for now");
|
|
14
|
-
}
|
|
15
|
-
const opcode = (0xD2800000 | (imm << 5) | 0) >>> 0;
|
|
16
|
-
this.buffer.push(instr(opcode));
|
|
17
|
-
}
|
|
18
|
-
emitMovX8(imm) {
|
|
19
|
-
if (imm > 65535) {
|
|
20
|
-
throw new Error("MVP only supports 16-bit integers for now");
|
|
21
|
-
}
|
|
22
|
-
const opcode = (0xD2800000 | (imm << 5) | 8) >>> 0;
|
|
23
|
-
this.buffer.push(instr(opcode));
|
|
24
|
-
}
|
|
25
|
-
getCode() {
|
|
26
|
-
return Buffer.concat(this.buffer);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { NodeType } from "../../parser/ast/nodes.js";
|
|
2
|
-
import { Arm64Emitter } from "./arm64.js";
|
|
3
|
-
import { ElfGenerator } from "./elf.js";
|
|
4
|
-
export class NativeCodegen {
|
|
5
|
-
emitter;
|
|
6
|
-
varValues = new Map();
|
|
7
|
-
constructor() {
|
|
8
|
-
this.emitter = new Arm64Emitter();
|
|
9
|
-
}
|
|
10
|
-
generate(ast) {
|
|
11
|
-
for (const stmt of ast.statements) {
|
|
12
|
-
this.visitStatement(stmt);
|
|
13
|
-
}
|
|
14
|
-
this.emitter.emitMovX0(0);
|
|
15
|
-
this.emitter.emitMovX8(93);
|
|
16
|
-
this.emitter.emitSvc0();
|
|
17
|
-
const machineCode = this.emitter.getCode();
|
|
18
|
-
return ElfGenerator.generate(machineCode);
|
|
19
|
-
}
|
|
20
|
-
visitStatement(stmt) {
|
|
21
|
-
switch (stmt.type) {
|
|
22
|
-
case NodeType.LetStatement:
|
|
23
|
-
if (stmt.value.type === NodeType.IntegerLiteral) {
|
|
24
|
-
this.varValues.set(stmt.name.value, stmt.value.value);
|
|
25
|
-
}
|
|
26
|
-
else if (stmt.value.type === NodeType.InfixExpression) {
|
|
27
|
-
const val = this.evaluateConstantExpr(stmt.value);
|
|
28
|
-
this.varValues.set(stmt.name.value, val);
|
|
29
|
-
}
|
|
30
|
-
break;
|
|
31
|
-
case NodeType.ReturnStatement:
|
|
32
|
-
const val = this.evaluateExpression(stmt.returnValue);
|
|
33
|
-
this.emitter.emitMovX0(val);
|
|
34
|
-
this.emitter.emitMovX8(93);
|
|
35
|
-
this.emitter.emitSvc0();
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
evaluateExpression(expr) {
|
|
40
|
-
if (expr.type === NodeType.IntegerLiteral) {
|
|
41
|
-
return expr.value;
|
|
42
|
-
}
|
|
43
|
-
else if (expr.type === NodeType.Identifier) {
|
|
44
|
-
const val = this.varValues.get(expr.value);
|
|
45
|
-
if (val === undefined)
|
|
46
|
-
throw new Error(`Variable ${expr.value} not found or not constant`);
|
|
47
|
-
return val;
|
|
48
|
-
}
|
|
49
|
-
else if (expr.type === NodeType.InfixExpression) {
|
|
50
|
-
return this.evaluateConstantExpr(expr);
|
|
51
|
-
}
|
|
52
|
-
return 0;
|
|
53
|
-
}
|
|
54
|
-
evaluateConstantExpr(expr) {
|
|
55
|
-
const left = this.evaluateExpression(expr.left);
|
|
56
|
-
const right = this.evaluateExpression(expr.right);
|
|
57
|
-
switch (expr.operator) {
|
|
58
|
-
case '+': return left + right;
|
|
59
|
-
case '-': return left - right;
|
|
60
|
-
case '*': return left * right;
|
|
61
|
-
case '/': return Math.floor(left / right);
|
|
62
|
-
default: return 0;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export const ELF_HEADER_SIZE = 0x40;
|
|
2
|
-
export const PROGRAM_HEADER_SIZE = 0x38;
|
|
3
|
-
export const BASE_ADDRESS = 0x400000;
|
|
4
|
-
export function writeU8(buf, val, offset) {
|
|
5
|
-
buf.writeUInt8(val, offset);
|
|
6
|
-
}
|
|
7
|
-
export function writeU16(buf, val, offset) {
|
|
8
|
-
buf.writeUInt16LE(val, offset);
|
|
9
|
-
}
|
|
10
|
-
export function writeU32(buf, val, offset) {
|
|
11
|
-
buf.writeUInt32LE(val, offset);
|
|
12
|
-
}
|
|
13
|
-
export function writeU64(buf, val, offset) {
|
|
14
|
-
buf.writeBigUInt64LE(val, offset);
|
|
15
|
-
}
|
|
16
|
-
export class ElfGenerator {
|
|
17
|
-
static generate(code) {
|
|
18
|
-
const fileSize = ELF_HEADER_SIZE + PROGRAM_HEADER_SIZE + code.length;
|
|
19
|
-
const buf = Buffer.alloc(fileSize);
|
|
20
|
-
writeU8(buf, 0x7F, 0);
|
|
21
|
-
writeU8(buf, 0x45, 1);
|
|
22
|
-
writeU8(buf, 0x4C, 2);
|
|
23
|
-
writeU8(buf, 0x46, 3);
|
|
24
|
-
writeU8(buf, 2, 4);
|
|
25
|
-
writeU8(buf, 1, 5);
|
|
26
|
-
writeU8(buf, 1, 6);
|
|
27
|
-
writeU8(buf, 0, 7);
|
|
28
|
-
writeU8(buf, 0, 8);
|
|
29
|
-
writeU16(buf, 2, 16);
|
|
30
|
-
writeU16(buf, 183, 18);
|
|
31
|
-
writeU32(buf, 1, 20);
|
|
32
|
-
const entryPoint = BigInt(BASE_ADDRESS + ELF_HEADER_SIZE + PROGRAM_HEADER_SIZE);
|
|
33
|
-
writeU64(buf, entryPoint, 24);
|
|
34
|
-
writeU64(buf, BigInt(ELF_HEADER_SIZE), 32);
|
|
35
|
-
writeU64(buf, BigInt(0), 40);
|
|
36
|
-
writeU32(buf, 0, 48);
|
|
37
|
-
writeU16(buf, ELF_HEADER_SIZE, 52);
|
|
38
|
-
writeU16(buf, PROGRAM_HEADER_SIZE, 54);
|
|
39
|
-
writeU16(buf, 1, 56);
|
|
40
|
-
writeU16(buf, 0, 58);
|
|
41
|
-
writeU16(buf, 0, 60);
|
|
42
|
-
writeU16(buf, 0, 62);
|
|
43
|
-
const phOffset = ELF_HEADER_SIZE;
|
|
44
|
-
writeU32(buf, 1, phOffset + 0);
|
|
45
|
-
writeU32(buf, 7, phOffset + 4);
|
|
46
|
-
writeU64(buf, BigInt(0), phOffset + 8);
|
|
47
|
-
writeU64(buf, BigInt(BASE_ADDRESS), phOffset + 16);
|
|
48
|
-
writeU64(buf, BigInt(BASE_ADDRESS), phOffset + 24);
|
|
49
|
-
const totalSize = BigInt(fileSize);
|
|
50
|
-
writeU64(buf, totalSize, phOffset + 32);
|
|
51
|
-
writeU64(buf, totalSize, phOffset + 40);
|
|
52
|
-
writeU64(buf, BigInt(0x1000), phOffset + 48);
|
|
53
|
-
const codeOffset = ELF_HEADER_SIZE + PROGRAM_HEADER_SIZE;
|
|
54
|
-
code.copy(buf, codeOffset);
|
|
55
|
-
return buf;
|
|
56
|
-
}
|
|
57
|
-
}
|