@ugo-studio/jspp 0.2.5 → 0.2.6

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.
@@ -1,6 +1,8 @@
1
1
  import ts from "typescript";
2
2
  import { DeclaredSymbols } from "../../ast/symbols.js";
3
- import { collectBlockScopedDeclarations, collectFunctionScopedDeclarations, } from "./helpers.js";
3
+ import { constants } from "../constants.js";
4
+ import { CompilerError } from "../error.js";
5
+ import { collectBlockScopedDeclarations, collectFunctionScopedDeclarations, shouldIgnoreStatement, } from "./helpers.js";
4
6
  import { CodeGenerator } from "./index.js";
5
7
  export function visitSourceFile(node, context) {
6
8
  const sourceFile = node;
@@ -8,8 +10,9 @@ export function visitSourceFile(node, context) {
8
10
  // 1. Collect all var declarations (recursively) + top-level let/const
9
11
  const varDecls = collectFunctionScopedDeclarations(sourceFile);
10
12
  const topLevelLetConst = collectBlockScopedDeclarations(sourceFile.statements);
11
- const funcDecls = sourceFile.statements.filter(ts.isFunctionDeclaration);
13
+ const funcDecls = sourceFile.statements.filter((s) => ts.isFunctionDeclaration(s) && !!s.body);
12
14
  const classDecls = sourceFile.statements.filter(ts.isClassDeclaration);
15
+ const enumDecls = sourceFile.statements.filter(ts.isEnumDeclaration);
13
16
  const hoistedSymbols = new DeclaredSymbols();
14
17
  // Hoist function declarations
15
18
  funcDecls.forEach((func) => {
@@ -19,6 +22,10 @@ export function visitSourceFile(node, context) {
19
22
  classDecls.forEach((cls) => {
20
23
  code += this.hoistDeclaration(cls, hoistedSymbols, node);
21
24
  });
25
+ // Hoist enum declarations
26
+ enumDecls.forEach((enm) => {
27
+ code += this.hoistDeclaration(enm, hoistedSymbols, node);
28
+ });
22
29
  // Hoist variable declarations (var)
23
30
  varDecls.forEach((decl) => {
24
31
  code += this.hoistDeclaration(decl, hoistedSymbols, node);
@@ -68,19 +75,11 @@ export function visitSourceFile(node, context) {
68
75
  // Already handled
69
76
  }
70
77
  else if (ts.isVariableStatement(stmt)) {
71
- const isLetOrConst = (stmt.declarationList.flags &
72
- (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
73
- 0;
74
- const contextForVisit = {
78
+ code += this.visit(stmt, {
75
79
  ...context,
76
80
  globalScopeSymbols,
77
81
  localScopeSymbols,
78
- isAssignmentOnly: !isLetOrConst,
79
- };
80
- const assignments = this.visit(stmt.declarationList, contextForVisit);
81
- if (assignments) {
82
- code += `${this.indent()}${assignments};\n`;
83
- }
82
+ });
84
83
  }
85
84
  else {
86
85
  code += this.visit(stmt, {
@@ -100,8 +99,9 @@ export function visitBlock(node, context) {
100
99
  const block = node;
101
100
  // Collect ONLY block-scoped declarations (let/const)
102
101
  const blockScopedDecls = collectBlockScopedDeclarations(block.statements);
103
- const funcDecls = block.statements.filter(ts.isFunctionDeclaration);
102
+ const funcDecls = block.statements.filter((s) => ts.isFunctionDeclaration(s) && !!s.body);
104
103
  const classDecls = block.statements.filter(ts.isClassDeclaration);
104
+ const enumDecls = block.statements.filter(ts.isEnumDeclaration);
105
105
  const hoistedSymbols = new DeclaredSymbols();
106
106
  // 1. Hoist all function declarations
107
107
  funcDecls.forEach((func) => {
@@ -111,6 +111,10 @@ export function visitBlock(node, context) {
111
111
  classDecls.forEach((cls) => {
112
112
  code += this.hoistDeclaration(cls, hoistedSymbols, node);
113
113
  });
114
+ // Hoist enum declarations
115
+ enumDecls.forEach((enm) => {
116
+ code += this.hoistDeclaration(enm, hoistedSymbols, node);
117
+ });
114
118
  // Hoist variable declarations (let/const only)
115
119
  blockScopedDecls.forEach((decl) => {
116
120
  code += this.hoistDeclaration(decl, hoistedSymbols, node);
@@ -156,19 +160,11 @@ export function visitBlock(node, context) {
156
160
  // Do nothing, already handled
157
161
  }
158
162
  else if (ts.isVariableStatement(stmt)) {
159
- const isLetOrConst = (stmt.declarationList.flags &
160
- (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
161
- 0;
162
- const contextForVisit = {
163
+ code += this.visit(stmt, {
163
164
  ...context,
164
165
  globalScopeSymbols,
165
166
  localScopeSymbols,
166
- isAssignmentOnly: !isLetOrConst,
167
- };
168
- const assignments = this.visit(stmt.declarationList, contextForVisit);
169
- if (assignments) {
170
- code += `${this.indent()}${assignments};\n`;
171
- }
167
+ });
172
168
  }
173
169
  else {
174
170
  code += this.visit(stmt, {
@@ -190,9 +186,87 @@ export function visitBlock(node, context) {
190
186
  return code;
191
187
  }
192
188
  export function visitVariableStatement(node, context) {
193
- return (this.indent() +
194
- this.visit(node.declarationList, context) +
195
- ";\n");
189
+ if (shouldIgnoreStatement(node)) {
190
+ return "";
191
+ }
192
+ const isLetOrConst = (node.declarationList.flags &
193
+ (ts.NodeFlags.Let | ts.NodeFlags.Const)) !==
194
+ 0;
195
+ const visitContext = {
196
+ ...context,
197
+ isAssignmentOnly: !isLetOrConst,
198
+ };
199
+ const assignments = this.visit(node.declarationList, visitContext);
200
+ if (assignments) {
201
+ return `${this.indent()}${assignments};\n`;
202
+ }
203
+ return "";
204
+ }
205
+ export function visitTypeAliasDeclaration(node, context) {
206
+ return "";
207
+ }
208
+ export function visitInterfaceDeclaration(node, context) {
209
+ return "";
210
+ }
211
+ export function visitEnumDeclaration(node, context) {
212
+ const name = node.name.getText();
213
+ const scope = this.getScopeForNode(node);
214
+ const typeInfo = this.typeAnalyzer.scopeManager.lookupFromScope(name, scope);
215
+ // Mark as initialized
216
+ this.markSymbolAsInitialized(name, context.globalScopeSymbols, context.localScopeSymbols);
217
+ const enumVar = typeInfo.needsHeapAllocation ? `(*${name})` : name;
218
+ let code = `${this.indent()}${enumVar} = jspp::AnyValue::make_object({});\n`;
219
+ code += `${this.indent()}{\n`;
220
+ this.indentationLevel++;
221
+ code +=
222
+ `${this.indent()}jspp::AnyValue lastVal = jspp::AnyValue::make_number(-1);\n`; // Previous value tracker
223
+ for (const member of node.members) {
224
+ const memberName = member.name.getText();
225
+ let valueCode = "";
226
+ // Handle member name (it could be a string literal or identifier)
227
+ let key = "";
228
+ if (ts.isIdentifier(member.name)) {
229
+ key = `"${memberName}"`;
230
+ }
231
+ else if (ts.isStringLiteral(member.name)) {
232
+ key = member.name.getText(); // Includes quotes
233
+ }
234
+ else {
235
+ // Computed property names or numeric literals in enums are rarer but possible
236
+ // For now assume simple enum
237
+ key = `"${memberName}"`;
238
+ }
239
+ if (member.initializer) {
240
+ // Visit initializer
241
+ valueCode = this.visit(member.initializer, context);
242
+ }
243
+ else {
244
+ // Auto-increment
245
+ valueCode = `lastVal + 1`;
246
+ }
247
+ code += `${this.indent()}lastVal = ${valueCode};\n`;
248
+ code +=
249
+ `${this.indent()}${enumVar}.set_own_property(${key}, lastVal);\n`;
250
+ // Reverse mapping for numeric enums
251
+ code += `${this.indent()}if (lastVal.is_number()) {\n`;
252
+ this.indentationLevel++;
253
+ code +=
254
+ `${this.indent()}${enumVar}.set_own_property(lastVal, jspp::AnyValue::make_string(${key}));\n`;
255
+ this.indentationLevel--;
256
+ code += `${this.indent()}}\n`;
257
+ }
258
+ this.indentationLevel--;
259
+ code += `${this.indent()}}\n`;
260
+ return code;
261
+ }
262
+ export function visitModuleDeclaration(node, context) {
263
+ return "";
264
+ }
265
+ export function visitImportDeclaration(node, context) {
266
+ return "";
267
+ }
268
+ export function visitImportEqualsDeclaration(node, context) {
269
+ return "";
196
270
  }
197
271
  export function visitBreakStatement(node, context) {
198
272
  if (node.label) {
@@ -237,7 +311,12 @@ export function visitLabeledStatement(node, context) {
237
311
  }
238
312
  export function visitIfStatement(node, context) {
239
313
  const ifStmt = node;
240
- const condition = this.visit(ifStmt.expression, context);
314
+ const isBinaryExpression = ts.isBinaryExpression(ifStmt.expression) &&
315
+ constants.booleanOperators.includes(ifStmt.expression.operatorToken.kind);
316
+ const condition = this.visit(ifStmt.expression, {
317
+ ...context,
318
+ supportedNativeLiterals: isBinaryExpression ? ["boolean"] : undefined,
319
+ });
241
320
  const thenStmt = this.visit(ifStmt.thenStatement, {
242
321
  ...context,
243
322
  isFunctionBody: false,
@@ -250,15 +329,8 @@ export function visitIfStatement(node, context) {
250
329
  isFunctionBody: false,
251
330
  });
252
331
  }
253
- if (ts.isBinaryExpression(ifStmt.expression)) {
254
- const binExpr = ifStmt.expression;
255
- const op = binExpr.operatorToken.getText();
256
- const isBoolean = op === "==" || op === "!=" || op === "===" ||
257
- op === "!==" || op === "<" || op === ">" || op === "<=" ||
258
- op === ">=" || op === "instanceof" || op === "in";
259
- if (isBoolean) {
260
- return `${this.indent()}if ((${condition}).as_boolean()) ${thenStmt}${elseStmt}`;
261
- }
332
+ if (isBinaryExpression) {
333
+ return `${this.indent()}if (${condition}) ${thenStmt}${elseStmt}`;
262
334
  }
263
335
  return `${this.indent()}if (jspp::is_truthy(${condition})) ${thenStmt}${elseStmt}`;
264
336
  }
@@ -540,7 +612,7 @@ export function visitCatchClause(node, context) {
540
612
  const exceptionName = context.exceptionName;
541
613
  if (!exceptionName) {
542
614
  // This should not happen if it's coming from a TryStatement
543
- throw new Error("Compiler bug: exceptionName not found in context for CatchClause");
615
+ throw new CompilerError("exceptionName not found in context for CatchClause", node, "CompilerBug");
544
616
  }
545
617
  if (catchClause.variableDeclaration) {
546
618
  const varName = catchClause.variableDeclaration.name.getText();
@@ -2,11 +2,11 @@ import ts from "typescript";
2
2
  import { visitClassDeclaration } from "./class-handlers.js";
3
3
  import { visitCaseClause, visitDefaultClause, visitDoStatement, visitForInStatement, visitForOfStatement, visitForStatement, visitSwitchStatement, visitWhileStatement, } from "./control-flow-handlers.js";
4
4
  import { visitVariableDeclaration, visitVariableDeclarationList, } from "./declaration-handlers.js";
5
- import { visitArrayLiteralExpression, visitAwaitExpression, visitBinaryExpression, visitCallExpression, visitConditionalExpression, visitDeleteExpression, visitElementAccessExpression, visitNewExpression, visitObjectLiteralExpression, visitParenthesizedExpression, visitPostfixUnaryExpression, visitPrefixUnaryExpression, visitPropertyAccessExpression, visitTemplateExpression, visitTypeOfExpression, visitVoidExpression, } from "./expression-handlers.js";
5
+ import { visitArrayLiteralExpression, visitAsExpression, visitAwaitExpression, visitBinaryExpression, visitCallExpression, visitConditionalExpression, visitDeleteExpression, visitElementAccessExpression, visitNewExpression, visitNonNullExpression, visitObjectLiteralExpression, visitParenthesizedExpression, visitPostfixUnaryExpression, visitPrefixUnaryExpression, visitPropertyAccessExpression, visitSatisfiesExpression, visitTemplateExpression, visitTypeAssertionExpression, visitTypeOfExpression, visitVoidExpression, } from "./expression-handlers.js";
6
6
  import { visitArrowFunction, visitFunctionDeclaration, visitFunctionExpression, } from "./function-handlers.js";
7
7
  import { CodeGenerator } from "./index.js";
8
8
  import { visitFalseKeyword, visitIdentifier, visitNoSubstitutionTemplateLiteral, visitNullKeyword, visitNumericLiteral, visitStringLiteral, visitThisKeyword, visitTrueKeyword, visitUndefinedKeyword, } from "./literal-handlers.js";
9
- import { visitBlock, visitBreakStatement, visitCatchClause, visitContinueStatement, visitExpressionStatement, visitIfStatement, visitLabeledStatement, visitReturnStatement, visitSourceFile, visitThrowStatement, visitTryStatement, visitVariableStatement, visitYieldExpression, } from "./statement-handlers.js";
9
+ import { visitBlock, visitBreakStatement, visitCatchClause, visitContinueStatement, visitEnumDeclaration, visitExpressionStatement, visitIfStatement, visitImportDeclaration, visitImportEqualsDeclaration, visitInterfaceDeclaration, visitLabeledStatement, visitModuleDeclaration, visitReturnStatement, visitSourceFile, visitThrowStatement, visitTryStatement, visitTypeAliasDeclaration, visitVariableStatement, visitYieldExpression, } from "./statement-handlers.js";
10
10
  export function visit(node, context) {
11
11
  if (ts.isFunctionDeclaration(node)) {
12
12
  return visitFunctionDeclaration.call(this, node, context);
@@ -114,6 +114,26 @@ export function visit(node, context) {
114
114
  return visitNullKeyword.call(this);
115
115
  case ts.SyntaxKind.ThisKeyword:
116
116
  return visitThisKeyword.call(this);
117
+ case ts.SyntaxKind.AsExpression:
118
+ return visitAsExpression.call(this, node, context);
119
+ case ts.SyntaxKind.TypeAssertionExpression:
120
+ return visitTypeAssertionExpression.call(this, node, context);
121
+ case ts.SyntaxKind.NonNullExpression:
122
+ return visitNonNullExpression.call(this, node, context);
123
+ case ts.SyntaxKind.SatisfiesExpression:
124
+ return visitSatisfiesExpression.call(this, node, context);
125
+ case ts.SyntaxKind.TypeAliasDeclaration:
126
+ return visitTypeAliasDeclaration.call(this, node, context);
127
+ case ts.SyntaxKind.InterfaceDeclaration:
128
+ return visitInterfaceDeclaration.call(this, node, context);
129
+ case ts.SyntaxKind.EnumDeclaration:
130
+ return visitEnumDeclaration.call(this, node, context);
131
+ case ts.SyntaxKind.ModuleDeclaration:
132
+ return visitModuleDeclaration.call(this, node, context);
133
+ case ts.SyntaxKind.ImportDeclaration:
134
+ return visitImportDeclaration.call(this, node, context);
135
+ case ts.SyntaxKind.ImportEqualsDeclaration:
136
+ return visitImportEqualsDeclaration.call(this, node, context);
117
137
  default:
118
138
  return `/* Unhandled node: ${ts.SyntaxKind[node.kind]} */`;
119
139
  }
@@ -0,0 +1,16 @@
1
+ import ts from "typescript";
2
+ const booleanOperators = [
3
+ ts.SyntaxKind.EqualsEqualsEqualsToken,
4
+ ts.SyntaxKind.EqualsEqualsToken,
5
+ ts.SyntaxKind.ExclamationEqualsEqualsToken,
6
+ ts.SyntaxKind.ExclamationEqualsToken,
7
+ ts.SyntaxKind.InstanceOfKeyword,
8
+ ts.SyntaxKind.InKeyword,
9
+ ts.SyntaxKind.LessThanToken,
10
+ ts.SyntaxKind.LessThanEqualsToken,
11
+ ts.SyntaxKind.GreaterThanToken,
12
+ ts.SyntaxKind.GreaterThanEqualsToken,
13
+ ];
14
+ export const constants = Object.freeze({
15
+ booleanOperators,
16
+ });
@@ -0,0 +1,58 @@
1
+ import ts from "typescript";
2
+ const COLORS = {
3
+ reset: "\x1b[0m",
4
+ red: "\x1b[31m",
5
+ yellow: "\x1b[33m",
6
+ cyan: "\x1b[36m",
7
+ bold: "\x1b[1m",
8
+ dim: "\x1b[2m",
9
+ };
10
+ export class CompilerError extends Error {
11
+ node;
12
+ type;
13
+ constructor(message, node, type = "SyntaxError") {
14
+ super(message);
15
+ this.node = node;
16
+ this.type = type;
17
+ Object.setPrototypeOf(this, CompilerError.prototype);
18
+ }
19
+ getFormattedError() {
20
+ const sourceFile = this.node.getSourceFile();
21
+ if (!sourceFile) {
22
+ return `${COLORS.red}${this.type}: ${this.message}${COLORS.reset}`;
23
+ }
24
+ const start = this.node.getStart();
25
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
26
+ // Get the full line content safely
27
+ const lineStartPos = sourceFile.getPositionOfLineAndCharacter(line, 0);
28
+ let lineEndPos;
29
+ try {
30
+ lineEndPos = sourceFile.getLineEndOfPosition(start);
31
+ }
32
+ catch {
33
+ lineEndPos = sourceFile.text.length;
34
+ }
35
+ const lineContent = sourceFile.text.substring(lineStartPos, lineEndPos).replace(/\r/g, ''); // Remove CR
36
+ const fileName = sourceFile.fileName;
37
+ const lineNum = line + 1;
38
+ const charNum = character + 1;
39
+ let output = `\n${COLORS.red}${COLORS.bold}${this.type}:${COLORS.reset} ${this.message}\n`;
40
+ output += ` ${COLORS.dim}at ${fileName}:${lineNum}:${charNum}${COLORS.reset}\n\n`;
41
+ // Code frame
42
+ const lineNumStr = `${lineNum} | `;
43
+ output += `${COLORS.dim}${lineNumStr}${COLORS.reset}${lineContent}\n`;
44
+ // Adjust pointer position if there are tabs
45
+ let pointerPadding = "";
46
+ for (let i = 0; i < character; i++) {
47
+ if (lineContent[i] === '\t') {
48
+ pointerPadding += "\t";
49
+ }
50
+ else {
51
+ pointerPadding += " ";
52
+ }
53
+ }
54
+ const padding = " ".repeat(lineNumStr.length) + pointerPadding;
55
+ output += `${padding}${COLORS.red}^${COLORS.reset}\n`;
56
+ return output;
57
+ }
58
+ }
package/dist/index.js CHANGED
@@ -6,10 +6,13 @@ export class Interpreter {
6
6
  parser = new Parser();
7
7
  analyzer = new TypeAnalyzer();
8
8
  generator = new CodeGenerator();
9
- interpret(jsCode, fileName) {
10
- const ast = this.parser.parse(jsCode, fileName);
9
+ interpret(code, fileName) {
10
+ const ast = this.parser.parse(code, fileName);
11
11
  this.analyzer.analyze(ast);
12
- const cppCode = this.generator.generate(ast, this.analyzer);
12
+ const isTypescript = fileName
13
+ ? path.extname(fileName) === ".ts"
14
+ : false;
15
+ const cppCode = this.generator.generate(ast, this.analyzer, isTypescript);
13
16
  const preludePath = path.resolve(import.meta.dirname, "..", "src", "prelude");
14
17
  return { cppCode, preludePath };
15
18
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@ugo-studio/jspp",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "A modern transpiler that converts JavaScript code into high-performance, standard C++23.",
5
5
  "main": "dist/index.js",
6
6
  "module": "src/index.ts",
7
7
  "type": "module",
8
8
  "bin": {
9
- "jspp": "dist/cli.js"
9
+ "jspp": "dist/cli/index.js"
10
10
  },
11
11
  "files": [
12
12
  "dist",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "postinstall": "bun run scripts/setup-compiler.ts",
18
- "dev": "bun run src/cli.ts",
18
+ "dev": "bun run src/cli/index.ts",
19
19
  "typecheck": "tsc --noEmit",
20
20
  "precompile": "bun run scripts/precompile-headers.ts",
21
21
  "test": "bun test",