@ugo-studio/jspp 0.2.5 → 0.2.7

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.
Files changed (54) hide show
  1. package/README.md +51 -36
  2. package/dist/analysis/scope.js +7 -0
  3. package/dist/analysis/typeAnalyzer.js +96 -43
  4. package/dist/ast/symbols.js +34 -24
  5. package/dist/cli/args.js +59 -0
  6. package/dist/cli/colors.js +9 -0
  7. package/dist/cli/file-utils.js +20 -0
  8. package/dist/cli/index.js +160 -0
  9. package/dist/cli/spinner.js +55 -0
  10. package/dist/core/codegen/class-handlers.js +8 -8
  11. package/dist/core/codegen/control-flow-handlers.js +19 -9
  12. package/dist/core/codegen/declaration-handlers.js +30 -10
  13. package/dist/core/codegen/expression-handlers.js +649 -161
  14. package/dist/core/codegen/function-handlers.js +107 -103
  15. package/dist/core/codegen/helpers.js +61 -14
  16. package/dist/core/codegen/index.js +13 -9
  17. package/dist/core/codegen/literal-handlers.js +4 -2
  18. package/dist/core/codegen/statement-handlers.js +147 -55
  19. package/dist/core/codegen/visitor.js +22 -2
  20. package/dist/core/constants.js +16 -0
  21. package/dist/core/error.js +58 -0
  22. package/dist/index.js +6 -3
  23. package/package.json +3 -3
  24. package/src/prelude/any_value.hpp +89 -59
  25. package/src/prelude/any_value_access.hpp +1 -1
  26. package/src/prelude/any_value_helpers.hpp +85 -43
  27. package/src/prelude/index.hpp +1 -0
  28. package/src/prelude/library/array.hpp +3 -2
  29. package/src/prelude/scheduler.hpp +144 -144
  30. package/src/prelude/types.hpp +8 -8
  31. package/src/prelude/utils/access.hpp +62 -6
  32. package/src/prelude/utils/assignment_operators.hpp +14 -14
  33. package/src/prelude/utils/log_any_value/array.hpp +0 -15
  34. package/src/prelude/utils/log_any_value/object.hpp +12 -10
  35. package/src/prelude/utils/log_any_value/primitives.hpp +2 -0
  36. package/src/prelude/utils/operators.hpp +117 -474
  37. package/src/prelude/utils/operators_primitive.hpp +337 -0
  38. package/src/prelude/values/helpers/array.hpp +4 -4
  39. package/src/prelude/values/helpers/async_iterator.hpp +2 -2
  40. package/src/prelude/values/helpers/function.hpp +3 -3
  41. package/src/prelude/values/helpers/iterator.hpp +2 -2
  42. package/src/prelude/values/helpers/object.hpp +3 -3
  43. package/src/prelude/values/helpers/promise.hpp +1 -1
  44. package/src/prelude/values/helpers/string.hpp +1 -1
  45. package/src/prelude/values/helpers/symbol.hpp +1 -1
  46. package/src/prelude/values/prototypes/array.hpp +1125 -853
  47. package/src/prelude/values/prototypes/async_iterator.hpp +32 -14
  48. package/src/prelude/values/prototypes/function.hpp +30 -18
  49. package/src/prelude/values/prototypes/iterator.hpp +40 -17
  50. package/src/prelude/values/prototypes/number.hpp +119 -62
  51. package/src/prelude/values/prototypes/object.hpp +10 -4
  52. package/src/prelude/values/prototypes/promise.hpp +167 -109
  53. package/src/prelude/values/prototypes/string.hpp +407 -231
  54. package/src/prelude/values/prototypes/symbol.hpp +45 -23
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "child_process";
3
+ import fs from "fs/promises";
4
+ import path from "path";
5
+ import pkg from "../../package.json" with { type: "json" };
6
+ import { CompilerError } from "../core/error.js";
7
+ import { Interpreter } from "../index.js";
8
+ import { parseArgs } from "./args.js";
9
+ import { COLORS } from "./colors.js";
10
+ import { getLatestMtime } from "./file-utils.js";
11
+ import { Spinner } from "./spinner.js";
12
+ const pkgDir = path.dirname(path.dirname(import.meta.dirname));
13
+ async function main() {
14
+ const { jsFilePath, isRelease, keepCpp, outputExePath, scriptArgs } = parseArgs(process.argv.slice(2));
15
+ const ext = path.extname(jsFilePath);
16
+ const jsFileName = path.basename(jsFilePath, ext);
17
+ const sourceDir = path.dirname(jsFilePath);
18
+ // Intermediate C++ file goes alongside the source JS file
19
+ const cppFilePath = path.join(sourceDir, `${jsFileName}.cpp`);
20
+ // Determine output executable path
21
+ let exeFilePath;
22
+ if (outputExePath) {
23
+ exeFilePath = outputExePath;
24
+ }
25
+ else {
26
+ const ext = process.platform === "win32" ? ".exe" : "";
27
+ exeFilePath = path.join(sourceDir, `${jsFileName}${ext}`);
28
+ }
29
+ // Mode Configuration
30
+ const mode = isRelease ? "release" : "debug";
31
+ console.log(`${COLORS.bold}JSPP Compiler${COLORS.reset} ${COLORS.dim}v${pkg.version}${COLORS.reset}`);
32
+ console.log(`Mode: ${isRelease ? COLORS.green : COLORS.yellow}${mode.toUpperCase()}${COLORS.reset}\n`);
33
+ const flags = isRelease ? ["-O3", "-DNDEBUG"] : ["-O0"];
34
+ if (process.platform === "win32") {
35
+ flags.push("-Wa,-mbig-obj");
36
+ }
37
+ const pchDir = path.resolve(pkgDir, "prelude-build", mode);
38
+ const spinner = new Spinner("Initializing...");
39
+ try {
40
+ spinner.start();
41
+ // 1. Interpreter Phase
42
+ spinner.update(`Reading ${path.basename(jsFilePath)}...`);
43
+ const jsCode = await fs.readFile(jsFilePath, "utf-8");
44
+ spinner.update("Transpiling to C++...");
45
+ const interpreter = new Interpreter();
46
+ const { cppCode, preludePath } = interpreter.interpret(jsCode, jsFilePath);
47
+ // Ensure directory for cpp file exists (should exist as it's source dir, but for safety if we change logic)
48
+ await fs.mkdir(path.dirname(cppFilePath), { recursive: true });
49
+ await fs.writeFile(cppFilePath, cppCode);
50
+ spinner.succeed(`Generated cpp`);
51
+ // 2. Precompiled Header Check
52
+ spinner.text = "Checking precompiled headers...";
53
+ spinner.start();
54
+ const pchFile = path.join(pchDir, "index.hpp.gch");
55
+ let shouldRebuild = false;
56
+ try {
57
+ const pchStats = await fs.stat(pchFile);
58
+ const sourceMtime = await getLatestMtime(preludePath);
59
+ if (sourceMtime > pchStats.mtimeMs) {
60
+ shouldRebuild = true;
61
+ }
62
+ }
63
+ catch (e) {
64
+ shouldRebuild = true;
65
+ }
66
+ if (shouldRebuild) {
67
+ spinner.update("Rebuilding precompiled headers (this may take a while)...");
68
+ // Use spawn (async) instead of spawnSync to keep spinner alive
69
+ const rebuild = spawn("bun", [
70
+ "run",
71
+ "scripts/precompile-headers.ts",
72
+ ], {
73
+ cwd: pkgDir,
74
+ stdio: ["ignore", "pipe", "pipe"],
75
+ });
76
+ const stderrChunks = [];
77
+ if (rebuild.stderr) {
78
+ rebuild.stderr.on("data", (chunk) => stderrChunks.push(chunk));
79
+ }
80
+ const exitCode = await new Promise((resolve) => {
81
+ rebuild.on("close", (code) => resolve(code ?? 1));
82
+ });
83
+ if (exitCode !== 0) {
84
+ const stderr = Buffer.concat(stderrChunks).toString();
85
+ spinner.fail("Failed to rebuild precompiled headers");
86
+ console.error(stderr);
87
+ process.exit(1);
88
+ }
89
+ spinner.succeed("Precompiled headers updated");
90
+ }
91
+ else {
92
+ spinner.succeed("Precompiled headers");
93
+ }
94
+ // 3. Compilation Phase
95
+ spinner.text = `Compiling binary...`;
96
+ spinner.start();
97
+ // Ensure output directory exists
98
+ await fs.mkdir(path.dirname(exeFilePath), { recursive: true });
99
+ const compile = spawn("g++", [
100
+ "-std=c++23",
101
+ ...flags,
102
+ cppFilePath,
103
+ "-o",
104
+ exeFilePath,
105
+ "-I",
106
+ pchDir,
107
+ "-I",
108
+ preludePath,
109
+ ], {
110
+ stdio: ["ignore", "pipe", "pipe"],
111
+ });
112
+ const compileStderrChunks = [];
113
+ if (compile.stderr) {
114
+ compile.stderr.on("data", (chunk) => compileStderrChunks.push(chunk));
115
+ }
116
+ const compileExitCode = await new Promise((resolve) => {
117
+ compile.on("close", (code) => resolve(code ?? 1));
118
+ });
119
+ if (compileExitCode !== 0) {
120
+ const stderr = Buffer.concat(compileStderrChunks).toString();
121
+ spinner.fail(`Compilation failed`);
122
+ console.error(stderr);
123
+ process.exit(1);
124
+ }
125
+ spinner.succeed(`Compiled to ${COLORS.green}${COLORS.bold}${path.basename(exeFilePath)}${COLORS.reset}`);
126
+ // Clean up C++ file if not requested to keep
127
+ if (!keepCpp) {
128
+ try {
129
+ await fs.unlink(cppFilePath);
130
+ }
131
+ catch (e) {
132
+ // Ignore error if file cannot be deleted
133
+ }
134
+ }
135
+ // 4. Execution Phase
136
+ console.log(`\n${COLORS.cyan}--- Running Output ---${COLORS.reset}`);
137
+ const run = spawn(exeFilePath, scriptArgs, {
138
+ stdio: "inherit",
139
+ });
140
+ const runExitCode = await new Promise((resolve) => {
141
+ run.on("close", (code) => resolve(code ?? 1));
142
+ });
143
+ console.log(`${COLORS.cyan}----------------------${COLORS.reset}\n`);
144
+ if (runExitCode !== 0) {
145
+ console.error(`${COLORS.red}Execution failed with exit code ${runExitCode}${COLORS.reset}`);
146
+ process.exit(1);
147
+ }
148
+ }
149
+ catch (error) {
150
+ if (error instanceof CompilerError) {
151
+ spinner.fail("Compilation failed");
152
+ console.error(error.getFormattedError());
153
+ process.exit(1);
154
+ }
155
+ spinner.fail("An unexpected error occurred");
156
+ console.error(error);
157
+ process.exit(1);
158
+ }
159
+ }
160
+ main();
@@ -0,0 +1,55 @@
1
+ import { COLORS } from "./colors.js";
2
+ export class Spinner {
3
+ frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
4
+ interval = null;
5
+ frameIndex = 0;
6
+ text;
7
+ constructor(text) {
8
+ this.text = text;
9
+ }
10
+ start() {
11
+ process.stdout.write("\x1b[?25l"); // Hide cursor
12
+ this.frameIndex = 0;
13
+ this.render();
14
+ this.interval = setInterval(() => {
15
+ this.frameIndex = (this.frameIndex + 1) % this.frames.length;
16
+ this.render();
17
+ }, 80);
18
+ }
19
+ update(text) {
20
+ this.text = text;
21
+ this.render();
22
+ }
23
+ stop(symbol = "", color = COLORS.reset) {
24
+ if (this.interval) {
25
+ clearInterval(this.interval);
26
+ this.interval = null;
27
+ }
28
+ this.clearLine();
29
+ process.stdout.write(`${color}${symbol} ${COLORS.reset} ${this.text}\n`);
30
+ process.stdout.write("\x1b[?25h"); // Show cursor
31
+ }
32
+ succeed(text) {
33
+ if (text)
34
+ this.text = text;
35
+ this.stop("✔", COLORS.green);
36
+ }
37
+ fail(text) {
38
+ if (text)
39
+ this.text = text;
40
+ this.stop("✖", COLORS.red);
41
+ }
42
+ info(text) {
43
+ if (text)
44
+ this.text = text;
45
+ this.stop("ℹ", COLORS.cyan);
46
+ }
47
+ render() {
48
+ this.clearLine();
49
+ const frame = this.frames[this.frameIndex];
50
+ process.stdout.write(`${COLORS.cyan}${frame} ${COLORS.reset} ${this.text}`);
51
+ }
52
+ clearLine() {
53
+ process.stdout.write("\r\x1b[K");
54
+ }
55
+ }
@@ -32,11 +32,11 @@ export function visitClassDeclaration(node, context) {
32
32
  const constructor = node.members.find(ts.isConstructorDeclaration);
33
33
  let constructorLambda = "";
34
34
  if (constructor) {
35
- constructorLambda = this.generateLambda(constructor, {
35
+ constructorLambda = this.generateWrappedLambda(this.generateLambdaComponents(constructor, {
36
36
  ...classContext,
37
37
  isInsideFunction: true,
38
38
  lambdaName: className,
39
- }, { isClass: true });
39
+ }, { isClass: true }));
40
40
  }
41
41
  else {
42
42
  // Default constructor
@@ -75,10 +75,10 @@ export function visitClassDeclaration(node, context) {
75
75
  isObjectLiteralExpression: true, // Reuse this flag to handle computed properties
76
76
  });
77
77
  const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
78
- const methodLambda = this.generateLambda(member, {
78
+ const methodLambda = this.generateWrappedLambda(this.generateLambdaComponents(member, {
79
79
  ...classContext,
80
80
  isInsideFunction: true,
81
- });
81
+ }));
82
82
  if (isStatic) {
83
83
  code +=
84
84
  `${this.indent()}(*${className}).set_own_property(${methodName}, ${methodLambda});\n`;
@@ -94,10 +94,10 @@ export function visitClassDeclaration(node, context) {
94
94
  isObjectLiteralExpression: true,
95
95
  });
96
96
  const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
97
- const lambda = this.generateLambda(member, {
97
+ const lambda = this.generateWrappedLambda(this.generateLambdaComponents(member, {
98
98
  ...classContext,
99
99
  isInsideFunction: true,
100
- });
100
+ }));
101
101
  if (isStatic) {
102
102
  code +=
103
103
  `${this.indent()}(*${className}).define_getter(${methodName}, ${lambda});\n`;
@@ -113,10 +113,10 @@ export function visitClassDeclaration(node, context) {
113
113
  isObjectLiteralExpression: true,
114
114
  });
115
115
  const isStatic = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword);
116
- const lambda = this.generateLambda(member, {
116
+ const lambda = this.generateWrappedLambda(this.generateLambdaComponents(member, {
117
117
  ...classContext,
118
118
  isInsideFunction: true,
119
- });
119
+ }));
120
120
  if (isStatic) {
121
121
  code +=
122
122
  `${this.indent()}(*${className}).define_setter(${methodName}, ${lambda});\n`;
@@ -44,11 +44,11 @@ export function visitForStatement(node, context) {
44
44
  : DeclarationType.const;
45
45
  conditionContext.localScopeSymbols.add(name, {
46
46
  type: declType,
47
- checked: { initialized: true },
47
+ checks: { initialized: true },
48
48
  });
49
49
  statementContext.localScopeSymbols.add(name, {
50
50
  type: declType,
51
- checked: { initialized: true },
51
+ checks: { initialized: true },
52
52
  });
53
53
  if (typeInfo.needsHeapAllocation) {
54
54
  initializerCode =
@@ -440,19 +440,29 @@ export function visitSwitchStatement(node, context) {
440
440
  this.markSymbolAsInitialized(funcName, globalScopeSymbols, localScopeSymbols);
441
441
  // Generate native name
442
442
  const nativeName = this.generateUniqueName(`__${funcName}_native_`, hoistedSymbols);
443
- hoistedSymbols.update(funcName, { func: { nativeName } });
444
- // Generate lambda
445
- const lambda = this.generateLambda(stmt, contextForFunctions, {
443
+ hoistedSymbols.update(funcName, {
444
+ features: {
445
+ native: {
446
+ type: "lambda",
447
+ name: nativeName,
448
+ parameters: this.validateFunctionParams(stmt.parameters),
449
+ },
450
+ },
451
+ });
452
+ // Generate lambda components
453
+ const lambdaComps = this.generateLambdaComponents(stmt, contextForFunctions, {
446
454
  isAssignment: true,
447
- generateOnlyLambda: true,
448
455
  nativeName,
456
+ noTypeSignature: true,
449
457
  });
450
- code += `${this.indent()}auto ${nativeName} = ${lambda};\n`;
458
+ // Generate native lambda
459
+ const nativeLambda = this.generateNativeLambda(lambdaComps);
460
+ code += `${this.indent()}auto ${nativeName} = ${nativeLambda};\n`;
451
461
  // Generate AnyValue wrapper
452
462
  if (this.isFunctionUsedAsValue(stmt, node) ||
453
463
  this.isFunctionUsedBeforeDeclaration(funcName, node)) {
454
- const fullExpression = this.generateFullLambdaExpression(stmt, contextForFunctions, nativeName, { isAssignment: true, noTypeSignature: true });
455
- code += `${this.indent()}*${funcName} = ${fullExpression};\n`;
464
+ const wrappedLambda = this.generateWrappedLambda(lambdaComps);
465
+ code += `${this.indent()}*${funcName} = ${wrappedLambda};\n`;
456
466
  }
457
467
  });
458
468
  let firstIf = true;
@@ -1,4 +1,5 @@
1
1
  import ts from "typescript";
2
+ import { CompilerError } from "../error.js";
2
3
  import { CodeGenerator } from "./index.js";
3
4
  export function visitVariableDeclarationList(node, context) {
4
5
  return node.declarations
@@ -37,24 +38,34 @@ export function visitVariableDeclaration(node, context) {
37
38
  initText = this.getDerefCode(initText, varName, context, initTypeInfo);
38
39
  }
39
40
  }
40
- else if (ts.isArrowFunction(initExpr)) {
41
+ else if (ts.isArrowFunction(initExpr) ||
42
+ (ts.isFunctionExpression(initExpr) && !initExpr.name)) {
41
43
  const initContext = {
42
44
  ...context,
43
45
  lambdaName: name, // Use the variable name as function name
44
46
  };
45
- // Generate and update self name
46
47
  const nativeName = this.generateUniqueName(`__${name}_native_`, context.localScopeSymbols, context.globalScopeSymbols);
47
- context.localScopeSymbols.update(name, { func: { nativeName } });
48
- // Generate lambda
49
- const lambda = this.generateLambda(initExpr, initContext, {
48
+ // Mark before further visits
49
+ context.localScopeSymbols.update(name, {
50
+ features: {
51
+ native: {
52
+ type: "lambda",
53
+ name: nativeName,
54
+ parameters: this.validateFunctionParams(initExpr.parameters),
55
+ },
56
+ },
57
+ });
58
+ // Generate lambda components
59
+ const lambdaComps = this.generateLambdaComponents(initExpr, initContext, {
50
60
  isAssignment: true,
51
- generateOnlyLambda: true,
52
61
  nativeName,
62
+ noTypeSignature: true,
53
63
  });
54
- nativeLambdaCode =
55
- `auto ${nativeName} = ${lambda};\n${this.indent()}`;
64
+ const nativeLambda = this.generateNativeLambda(lambdaComps);
65
+ // Generate native lambda
66
+ nativeLambdaCode = `auto ${nativeName} = ${nativeLambda}`;
56
67
  // Generate AnyValue wrapper
57
- initText = this.generateFullLambdaExpression(initExpr, initContext, nativeName, { isAssignment: true, noTypeSignature: true });
68
+ initText = this.generateWrappedLambda(lambdaComps);
58
69
  }
59
70
  initializer = " = " + initText;
60
71
  }
@@ -66,10 +77,19 @@ export function visitVariableDeclaration(node, context) {
66
77
  : (typeInfo.needsHeapAllocation && !shouldSkipDeref
67
78
  ? `*${name}`
68
79
  : name);
80
+ if (nativeLambdaCode)
81
+ nativeLambdaCode += `;\n${this.indent()}`;
69
82
  if (isLetOrConst) {
70
83
  // If there's no initializer, it should be assigned undefined.
71
84
  if (!initializer) {
72
- return `${nativeLambdaCode}${assignmentTarget} = jspp::Constants::UNDEFINED`;
85
+ // Constant variables must be initialized
86
+ const isConst = (varDecl.parent.flags & (ts.NodeFlags.Const)) !== 0;
87
+ if (isConst) {
88
+ throw new CompilerError(`The constant "${name}" must be initialized`, varDecl, "SyntaxError");
89
+ }
90
+ else {
91
+ return `${nativeLambdaCode}${assignmentTarget} = jspp::Constants::UNDEFINED`;
92
+ }
73
93
  }
74
94
  return `${nativeLambdaCode}${assignmentTarget}${initializer}`;
75
95
  }