novac 1.0.0
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/LICENSE +0 -0
- package/README.md +0 -0
- package/bin/nv+ +84 -0
- package/cli.js +97 -0
- package/package.json +24 -0
- package/scripts/update-bin.js +24 -0
- package/src/core/bstd.js +14 -0
- package/src/core/describe.js +187 -0
- package/src/core/emitter.js +499 -0
- package/src/core/environment.js +0 -0
- package/src/core/error.js +86 -0
- package/src/core/executor.js +1005 -0
- package/src/core/lexer.js +506 -0
- package/src/core/parser.js +762 -0
- package/src/core/types.js +133 -0
- package/src/index.js +0 -0
- package/src/runtime/stdlib.js +3 -0
package/LICENSE
ADDED
|
File without changes
|
package/README.md
ADDED
|
File without changes
|
package/bin/nv+
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { Command } = require('commander');
|
|
3
|
+
const chalk = require('chalk').default;
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
let registery = '@nvplus/'
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('nv+')
|
|
13
|
+
.description('Nova package manager')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
function publishPackage(tag) {
|
|
17
|
+
try {
|
|
18
|
+
console.log(chalk.cyanBright('📦 Preparing to publish Nova package...'));
|
|
19
|
+
|
|
20
|
+
const pkgPath = fs.existsSync('package.json')
|
|
21
|
+
? 'package.json'
|
|
22
|
+
: fs.existsSync('nova.json')
|
|
23
|
+
? 'nova.json'
|
|
24
|
+
: null;
|
|
25
|
+
|
|
26
|
+
if (!pkgPath) {
|
|
27
|
+
console.log(chalk.red('No package.json or nova.json found in this directory.'));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
32
|
+
const name = pkg.name || path.basename(process.cwd());
|
|
33
|
+
|
|
34
|
+
if (!name.startsWith(registery)) {
|
|
35
|
+
pkg.name = `${registery}${name}`;
|
|
36
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
console.log(chalk.green(`🚀 Publishing ${name}@${pkg.version || '0.0.1'} ...`));
|
|
40
|
+
execSync(`npm publish --access public --tag ${tag}`, { stdio: 'inherit' });
|
|
41
|
+
|
|
42
|
+
console.log(chalk.greenBright('✅ Published successfully!'));
|
|
43
|
+
} catch (err) {
|
|
44
|
+
console.error(chalk.red('❌ Publish failed:\n'), err.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function installPackage(pkg, globalInstall = false) {
|
|
49
|
+
try {
|
|
50
|
+
let scoped = pkg.startsWith(registery) ? pkg : `${registery}${pkg}`;
|
|
51
|
+
console.log(chalk.cyan(`⬇️ Installing ${pkg} ...`));
|
|
52
|
+
|
|
53
|
+
const cmd = globalInstall
|
|
54
|
+
? `npm install -g ${scoped}`
|
|
55
|
+
: `npm install ${scoped}`;
|
|
56
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
57
|
+
|
|
58
|
+
console.log(chalk.greenBright(`✅ Installed ${pkg} successfully!`));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error(chalk.red('❌ Installation failed:\n'), err.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---- Commands ----
|
|
65
|
+
|
|
66
|
+
program
|
|
67
|
+
.command('publish')
|
|
68
|
+
.description('Publish current Nova package')
|
|
69
|
+
.option('-t, --tag <tag>', 'Optional release tag', 'latest')
|
|
70
|
+
.action((opts) => publishPackage(opts.tag));
|
|
71
|
+
|
|
72
|
+
program
|
|
73
|
+
.command('install <pkg>')
|
|
74
|
+
.description('Install a Nova package')
|
|
75
|
+
.option('-g, --global', 'Install globally', false)
|
|
76
|
+
.action((pkg, opts) => installPackage(pkg, opts.global));
|
|
77
|
+
|
|
78
|
+
// Parse args (Commander auto-handles --help and --version)
|
|
79
|
+
program.parse(process.argv);
|
|
80
|
+
|
|
81
|
+
// If no subcommand is passed, show help
|
|
82
|
+
if (!process.argv.slice(2).length) {
|
|
83
|
+
program.outputHelp();
|
|
84
|
+
}
|
package/cli.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Nova CLI — built by Wix ⚙️
|
|
4
|
+
* Provides both REPL and file execution support.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { Command } = require("commander");
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const readline = require("readline");
|
|
11
|
+
|
|
12
|
+
// Core modules
|
|
13
|
+
const { Parser } = require("./src/core/parser.js");
|
|
14
|
+
const { Executor } = require("./src/core/executor.js");
|
|
15
|
+
let StdLib = require('./src/runtime/stdlib.js');
|
|
16
|
+
const BStd = require('./src/core/bstd.js');
|
|
17
|
+
StdLib = { ...StdLib, ...BStd };
|
|
18
|
+
|
|
19
|
+
// ──────────────────────────────────────────────────────────────
|
|
20
|
+
// Utility helpers
|
|
21
|
+
// ──────────────────────────────────────────────────────────────
|
|
22
|
+
function runCode(source, filename = "<repl>") {
|
|
23
|
+
const parser = new Parser(source);
|
|
24
|
+
const ast = parser.parse();
|
|
25
|
+
|
|
26
|
+
const executor = new Executor(source, StdLib);
|
|
27
|
+
return executor.run(ast);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function startRepl() {
|
|
31
|
+
console.log("Nova REPL — type 'exit' or Ctrl+C to quit\n");
|
|
32
|
+
const rl = readline.createInterface({
|
|
33
|
+
input: process.stdin,
|
|
34
|
+
output: process.stdout,
|
|
35
|
+
prompt: "nova> ",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
rl.prompt();
|
|
39
|
+
for await (const line of rl) {
|
|
40
|
+
const input = line.trim();
|
|
41
|
+
if (input === ".exit" || input === ".quit") break;
|
|
42
|
+
if (!input) {
|
|
43
|
+
rl.prompt();
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const executor = new Executor(line, StdLib);
|
|
49
|
+
const parser = new Parser(input);
|
|
50
|
+
const ast = parser.parse();
|
|
51
|
+
const result = executor.run(ast);
|
|
52
|
+
if (result !== undefined) console.log(executor.stringify(result));
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('Error: ', err);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
rl.prompt();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
rl.close();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ──────────────────────────────────────────────────────────────
|
|
64
|
+
// CLI definition
|
|
65
|
+
// ──────────────────────────────────────────────────────────────
|
|
66
|
+
const program = new Command();
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.name("nova")
|
|
70
|
+
.description("The Nova language CLI — run, build, and explore your code.")
|
|
71
|
+
.version("1.0.0");
|
|
72
|
+
|
|
73
|
+
program
|
|
74
|
+
.argument("[file]", "Nova source file to execute")
|
|
75
|
+
.option("-r, --repl", "Start interactive REPL mode")
|
|
76
|
+
.action(async (file, options) => {
|
|
77
|
+
if (options.repl || !file) {
|
|
78
|
+
await startRepl();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const filepath = path.resolve(process.cwd(), file);
|
|
83
|
+
if (!fs.existsSync(filepath)) {
|
|
84
|
+
console.error(`Error: file not found: ${filepath}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const source = fs.readFileSync(filepath, "utf8");
|
|
89
|
+
try {
|
|
90
|
+
runCode(source, filepath);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error(`RuntimeError: ${err.message}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "novac",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "a rewrite version of my package nvlang",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "purcwix",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "cli.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"nv+": "bin/nv+"
|
|
11
|
+
},
|
|
12
|
+
"directories": {
|
|
13
|
+
"example": "examples",
|
|
14
|
+
"test": "tests"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"chalk": "^5.6.2",
|
|
21
|
+
"commander": "^14.0.1"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {}
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
6
|
+
const binDir = path.join(__dirname, '..', 'bin');
|
|
7
|
+
|
|
8
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
9
|
+
pkg.bin = pkg.bin || {};
|
|
10
|
+
|
|
11
|
+
if (fs.existsSync(binDir)) {
|
|
12
|
+
const files = fs.readdirSync(binDir);
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
const fullPath = `./bin/${file}`;
|
|
15
|
+
const name = path.parse(file).name;
|
|
16
|
+
pkg.bin[name] = fullPath;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// sort keys (optional, just for tidiness)
|
|
21
|
+
pkg.bin = Object.fromEntries(Object.entries(pkg.bin).sort(([a], [b]) => a.localeCompare(b)));
|
|
22
|
+
|
|
23
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
24
|
+
console.log('✅ package.json bin field updated automatically.');
|
package/src/core/bstd.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
let Air = {};
|
|
5
|
+
|
|
6
|
+
// read all .js files in the current directory
|
|
7
|
+
fs.readdirSync(__dirname)
|
|
8
|
+
.filter(file => file !== 'bstd.js' && file.endsWith('.js'))
|
|
9
|
+
.forEach(file => {
|
|
10
|
+
const name = path.basename(file, '.js');
|
|
11
|
+
Air[name] = require(path.join(__dirname, file));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
module.exports = { Air };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* describe.js
|
|
3
|
+
* Converts Nova Air AST into natural-language explanations.
|
|
4
|
+
* Style: clear, rhythmic, and human — with natural punctuation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
function describe(node, depth = 0) {
|
|
8
|
+
const indent = " ".repeat(depth);
|
|
9
|
+
if (!node) return "";
|
|
10
|
+
if (Array.isArray(node))
|
|
11
|
+
return node.map(n => describe(n, depth)).join("\n");
|
|
12
|
+
|
|
13
|
+
switch (node.kind) {
|
|
14
|
+
// ─────────── ROOT ───────────
|
|
15
|
+
case "program":
|
|
16
|
+
return node.nodes.map(n => describe(n, depth)).join("\n");
|
|
17
|
+
|
|
18
|
+
// ─────────── DECLARATION ───────────
|
|
19
|
+
case "declare": {
|
|
20
|
+
const kind = node.isConst ? "constant" : "variable";
|
|
21
|
+
const pointer = node.isPointer ? "pointer to " : "";
|
|
22
|
+
if (node.destructure)
|
|
23
|
+
return `${indent}Create a ${kind} pattern: unpack into { ${node.destructure.props
|
|
24
|
+
.map(p => p.alias)
|
|
25
|
+
.join(", ")} }${node.value ? `, using ${describe(node.value)}` : ""}.`;
|
|
26
|
+
if (node.value)
|
|
27
|
+
return `${indent}Declare ${kind}: ${pointer}${node.name}, and set it to ${describe(node.value)}.`;
|
|
28
|
+
return `${indent}Declare ${kind}: ${pointer}${node.name}.`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─────────── ASSIGNMENT ───────────
|
|
32
|
+
case "assign":
|
|
33
|
+
return `${indent}Set ${describe(node.name)} to ${describe(node.value)}.`;
|
|
34
|
+
|
|
35
|
+
// ─────────── REFERENCES ───────────
|
|
36
|
+
case "ref":
|
|
37
|
+
return node.name;
|
|
38
|
+
|
|
39
|
+
// ─────────── VALUES ───────────
|
|
40
|
+
case "value":
|
|
41
|
+
return JSON.stringify(node.value);
|
|
42
|
+
|
|
43
|
+
// ─────────── FUNCTIONS ───────────
|
|
44
|
+
case "function": {
|
|
45
|
+
const args = node.args.length
|
|
46
|
+
? node.args.map(a => describe(a)).join(", ")
|
|
47
|
+
: "no arguments";
|
|
48
|
+
return `${indent}${node.isAsync ? "Async " : ""}function '${node.name}': takes (${args}), and ${summarizeBody(node.body)}.`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case "arrowfunc": {
|
|
52
|
+
const args = node.args.length
|
|
53
|
+
? node.args.map(a => describe(a)).join(", ")
|
|
54
|
+
: "no arguments";
|
|
55
|
+
return `${indent}Arrow function: takes (${args}), and ${summarizeBody(node.body)}.`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─────────── RETURN ───────────
|
|
59
|
+
case "return":
|
|
60
|
+
return `${indent}Return ${node.value ? describe(node.value) : "nothing"}${node.terminate ? ", immediately" : ""}.`;
|
|
61
|
+
|
|
62
|
+
// ─────────── CLASS ───────────
|
|
63
|
+
case "class":
|
|
64
|
+
return `${indent}Define a class '${node.name}'${node.superClass ? `, extending ${describe(node.superClass)}` : ""}: \n${node.members
|
|
65
|
+
.map(m => describe(m, depth + 1))
|
|
66
|
+
.join("\n")}`;
|
|
67
|
+
|
|
68
|
+
// ─────────── BRANCHING ───────────
|
|
69
|
+
case "branch": {
|
|
70
|
+
const base = `${indent}${capitalize(node.type)} statement: ${
|
|
71
|
+
node.args ? `when (${describe(node.args)})` : ""
|
|
72
|
+
}, then:\n${describe(node.body, depth + 1)}`;
|
|
73
|
+
const next = node.next
|
|
74
|
+
? `\n${indent}else:\n${describe(node.next.body, depth + 1)}`
|
|
75
|
+
: "";
|
|
76
|
+
return base + next;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ─────────── LOOPS ───────────
|
|
80
|
+
case "while":
|
|
81
|
+
case "repeat":
|
|
82
|
+
case "for":
|
|
83
|
+
case "do":
|
|
84
|
+
case "until":
|
|
85
|
+
return `${indent}${capitalize(node.kind)} loop: ${
|
|
86
|
+
node.args ? `condition is ${describe(node.args)},` : ""
|
|
87
|
+
} running:\n${describe(node.body, depth + 1)}`;
|
|
88
|
+
|
|
89
|
+
// ─────────── CALLS ───────────
|
|
90
|
+
case "call": {
|
|
91
|
+
const name = typeof node.name === "string" ? node.name : describe(node.name);
|
|
92
|
+
const args = node.args.length
|
|
93
|
+
? `, with arguments: ${node.args.map(a => describe(a)).join(", ")}`
|
|
94
|
+
: "";
|
|
95
|
+
return `${indent}Call ${name}${args}.`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ─────────── PROPERTY ACCESS ───────────
|
|
99
|
+
case "prop":
|
|
100
|
+
return `${describe(node.object)}.${node.name}`;
|
|
101
|
+
|
|
102
|
+
// ─────────── INDEX ACCESS ───────────
|
|
103
|
+
case "subscript":
|
|
104
|
+
return `${describe(node.object)}[${describe(node.index)}]`;
|
|
105
|
+
|
|
106
|
+
// ─────────── ASYNC / AWAIT ───────────
|
|
107
|
+
case "await":
|
|
108
|
+
return `${indent}Await: wait for ${describe(node.operand)} to complete.`;
|
|
109
|
+
|
|
110
|
+
// ─────────── ERROR HANDLING ───────────
|
|
111
|
+
case "throw":
|
|
112
|
+
return `${indent}Throw ${describe(node.value)}.`;
|
|
113
|
+
|
|
114
|
+
case "try":
|
|
115
|
+
return `${indent}Try block:\n${describe(node.tryBody, depth + 1)}${
|
|
116
|
+
node.catchBody
|
|
117
|
+
? `\n${indent}If an error occurs: catch it as '${node.catchName}', then:\n${describe(node.catchBody, depth + 1)}`
|
|
118
|
+
: ""
|
|
119
|
+
}${node.finallyBody ? `\n${indent}Finally: always run\n${describe(node.finallyBody, depth + 1)}` : ""}`;
|
|
120
|
+
|
|
121
|
+
// ─────────── EXPRESSIONS ───────────
|
|
122
|
+
case "binary":
|
|
123
|
+
return `${describe(node.left)} ${node.operator} ${describe(node.right)}.`;
|
|
124
|
+
|
|
125
|
+
case "unary":
|
|
126
|
+
return `${node.operator}${describe(node.operand)}.`;
|
|
127
|
+
|
|
128
|
+
case "postfix":
|
|
129
|
+
return `${describe(node.operand)}${node.operator}.`;
|
|
130
|
+
|
|
131
|
+
// ─────────── MEMORY / POINTERS ───────────
|
|
132
|
+
case "deref":
|
|
133
|
+
return `the value pointed to by ${describe(node.operand)}.`;
|
|
134
|
+
|
|
135
|
+
case "currency":
|
|
136
|
+
return `${describe(node.operand)} (in ${node.name}).`;
|
|
137
|
+
|
|
138
|
+
// ─────────── STRUCTURES ───────────
|
|
139
|
+
case "object":
|
|
140
|
+
return `object: { ${Object.entries(node.props)
|
|
141
|
+
.map(([k, v]) => `${k}: ${describe(v)}`)
|
|
142
|
+
.join(", ")} }.`;
|
|
143
|
+
|
|
144
|
+
case "array":
|
|
145
|
+
return `array: [${node.elements.map(e => describe(e)).join(", ")}].`;
|
|
146
|
+
|
|
147
|
+
// ─────────── PATTERNS ───────────
|
|
148
|
+
case "objpattern":
|
|
149
|
+
return `{ ${node.props
|
|
150
|
+
.map(p => `${p.key}${p.key !== p.alias ? ` as ${p.alias}` : ""}`)
|
|
151
|
+
.join(", ")} }`;
|
|
152
|
+
|
|
153
|
+
case "arrpattern":
|
|
154
|
+
return `[${node.elements.map(e => describe(e)).join(", ")}]`;
|
|
155
|
+
|
|
156
|
+
// ─────────── TEMPLATES ───────────
|
|
157
|
+
case "template":
|
|
158
|
+
return node.parts.map(p => describe(p)).join("");
|
|
159
|
+
|
|
160
|
+
// ─────────── EXECUTION ───────────
|
|
161
|
+
case "exec":
|
|
162
|
+
return `${indent}Execute: ${describe(node.expr)}.`;
|
|
163
|
+
|
|
164
|
+
// ─────────── FALLBACK ───────────
|
|
165
|
+
case "EOF":
|
|
166
|
+
return "";
|
|
167
|
+
default:
|
|
168
|
+
return `${indent}(Unknown node type: '${node.kind}')`;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ─────────── HELPERS ───────────
|
|
173
|
+
function summarizeBody(body) {
|
|
174
|
+
if (!body || !body.length) return "does nothing";
|
|
175
|
+
const first = describe(body[0]);
|
|
176
|
+
if (body.length === 1) {
|
|
177
|
+
if (first.startsWith("Return")) return `returns ${first.replace(/^Return /, "").trim()}`;
|
|
178
|
+
return `does: ${first.toLowerCase()}`;
|
|
179
|
+
}
|
|
180
|
+
return "runs several steps in order";
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function capitalize(str) {
|
|
184
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = { describe };
|