From 3d35b248c406f9b47c7c2d103e623e3bd6a2aa4b Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Thu, 24 Sep 2026 06:04:32 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Compile=20t?=
=?UTF-8?q?he=20CLI=20through=20the=20BynixScript=20build=20path?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 4 +++
docs/markdown/command.md | 2 +-
scripts/build.cjs | 18 ++----------
scripts/compile.cjs | 23 ++++++++++++++++
src/bsd.bs | 6 ++--
src/bsp.bs | 6 ++--
src/bsr.bs | 11 +++-----
src/bst.bs | 9 +++---
src/index.bs | 45 ++++++++++++++++++------------
src/translate.bs | 59 ++++++++++++++++++----------------------
tests/cli.test.cjs | 24 ++++++++++++++++
11 files changed, 123 insertions(+), 84 deletions(-)
create mode 100644 scripts/compile.cjs
diff --git a/README.md b/README.md
index 73b58c7..82dc81a 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,10 @@ npm ci
npm run build
npm test
```
+The build bootstraps the `.bs` CLI sources with the BynixScript compiler from the
+`bynixscript` development dependency, then bundles the compiled JavaScript with
+esbuild into `dist/index.min.cjs`. `npm test` also checks that every CLI source
+compiles through the same compiler path.
### Using CDN
```html
diff --git a/docs/markdown/command.md b/docs/markdown/command.md
index 9f1e796..2e9ded2 100644
--- a/docs/markdown/command.md
+++ b/docs/markdown/command.md
@@ -13,4 +13,4 @@ By default, input files use `.bys`, `.bynixscript`, or `.mbs`. Paths are relativ
Use `bynix --help` for all commands, `bynix --help` for command-specific help, and `bynix --version` for the version. The earlier option forms `bynix -r `, `bynix -c `, `bynix -p `, and `bynix -d ` remain available.
-For development, run `npm ci` followed by `npm run build` to produce `dist/index.min.cjs`. `npm test` builds it and runs the CLI tests.
+For development, run `npm ci` followed by `npm run build`. The build uses the `bynixscript` development dependency as a bootstrap compiler to translate the `.bs` CLI sources through BynixScript's normal parser and replacement passes, then bundles the resulting JavaScript with esbuild into `dist/index.min.cjs`. `npm test` builds the executable, checks that all CLI sources compile through that same path, and runs the command tests.
diff --git a/scripts/build.cjs b/scripts/build.cjs
index bfeb90f..a411f77 100644
--- a/scripts/build.cjs
+++ b/scripts/build.cjs
@@ -1,33 +1,19 @@
const fs = require('node:fs')
const path = require('node:path')
const { build } = require('esbuild')
-const { parseCode, addSemicolons } = require('../js/parser.js')
-const { funcReplace } = require('bynixscript/dist/parser/funcDecl.js')
+const { compileSource } = require('./compile.cjs')
const root = path.resolve(__dirname, '..')
const temporary = fs.mkdtempSync(path.join(root, '.bynix-build-'))
const source = path.join(root, 'src')
const output = path.join(root, 'dist', 'index.min.cjs')
-function translateParser(sourceCode) {
- return funcReplace(addSemicolons(parseCode(sourceCode))).replace(/\.change\(/g, '.replace(')
-}
-
async function main() {
try {
for (const name of fs.readdirSync(source, { withFileTypes: true })) {
if (name.isFile() && name.name.endsWith('.bs')) {
fs.writeFileSync(path.join(temporary, name.name.replace(/\.bs$/, '.js')),
- fs.readFileSync(path.join(source, name.name), 'utf8'))
- }
- }
-
- const parserOutput = path.join(temporary, 'parser')
- fs.mkdirSync(parserOutput)
- for (const name of fs.readdirSync(path.join(source, 'parser'))) {
- if (name.endsWith('.bs')) {
- const code = fs.readFileSync(path.join(source, 'parser', name), 'utf8')
- fs.writeFileSync(path.join(parserOutput, name.replace(/\.bs$/, '.js')), translateParser(code))
+ compileSource(fs.readFileSync(path.join(source, name.name), 'utf8')))
}
}
diff --git a/scripts/compile.cjs b/scripts/compile.cjs
new file mode 100644
index 0000000..853c895
--- /dev/null
+++ b/scripts/compile.cjs
@@ -0,0 +1,23 @@
+const { parsing, parsingMsg, parseCode, addSemicolons } = require('bynixscript/js/parser.js')
+const replacements = require('bynixscript/dist/parser/parsingDecl.js')
+
+const passes = [
+ 'flowReplace', 'utilityReplace', 'funcReplace', 'condReplace',
+ 'forEachReplace', 'reassignReplace', 'assignReplace', 'logReplace',
+ 'interactReplace', 'mathReplace', 'commentReplace', 'asyncReplace',
+ 'forReplace', 'convReplace', 'checkReplace', 'oopReplace',
+ 'excepReplace', 'domReplace'
+]
+
+function compileSource(source) {
+ let code = addSemicolons(parseCode(source))
+ if (parsing(code) === false) {
+ throw new Error(parsingMsg.message)
+ }
+ for (const pass of passes) {
+ code = replacements[pass](code)
+ }
+ return code
+}
+
+module.exports = { compileSource }
diff --git a/src/bsd.bs b/src/bsd.bs
index 4fa860c..3542db6 100644
--- a/src/bsd.bs
+++ b/src/bsd.bs
@@ -1,10 +1,10 @@
const fs = require('node:fs')
const { resolveFile } = require('./translate.js')
-function bsdCommand(file) {
+func bsdCommand(file):
const filePath = resolveFile(file)
fs.unlinkSync(filePath)
- console.log(`Deleted ${file}`)
-}
+ print(`Deleted ${file}`)
+end
module.exports = { bsdCommand }
diff --git a/src/bsp.bs b/src/bsp.bs
index ebd2903..d17bcda 100644
--- a/src/bsp.bs
+++ b/src/bsp.bs
@@ -1,8 +1,8 @@
const { readSource, translate } = require('./translate.js')
-function bspCommand(file) {
+func bspCommand(file):
const { source } = readSource(file)
- console.log(translate(source))
-}
+ print(translate(source))
+end
module.exports = { bspCommand }
diff --git a/src/bsr.bs b/src/bsr.bs
index a367e2d..2aaae85 100644
--- a/src/bsr.bs
+++ b/src/bsr.bs
@@ -2,14 +2,11 @@ const vm = require('node:vm')
const { createRequire } = require('node:module')
const { readSource, translate } = require('./translate.js')
-function bsrCommand(file) {
+func bsrCommand(file):
const { source, filePath } = readSource(file)
const code = translate(source)
- vm.runInNewContext(code, {
- console,
- process,
- require: createRequire(filePath)
- }, { filename: filePath })
-}
+ const sandbox = { console, process, require: createRequire(filePath) }
+ vm.runInNewContext(code, sandbox, { filename: filePath })
+end
module.exports = { bsrCommand }
diff --git a/src/bst.bs b/src/bst.bs
index 798e0db..67d01db 100644
--- a/src/bst.bs
+++ b/src/bst.bs
@@ -2,14 +2,13 @@ const fs = require('node:fs')
const path = require('node:path')
const { readSource, translate, extensions } = require('./translate.js')
-function bstCommand(file) {
+func bstCommand(file):
const { source, filePath } = readSource(file)
const code = translate(source)
const extension = path.extname(filePath)
- const output = filePath.slice(0, -extension.length) +
- (extension === extensions.module ? '.mjs' : '.js')
+ const output = filePath.slice(0, -extension.length) + (extension === extensions.module ? '.mjs' : '.js')
fs.writeFileSync(output, code, 'utf8')
- console.log(`Compiled ${file} to ${output}`)
-}
+ print(`Compiled ${file} to ${output}`)
+end
module.exports = { bstCommand }
diff --git a/src/index.bs b/src/index.bs
index 0aed073..e41a652 100644
--- a/src/index.bs
+++ b/src/index.bs
@@ -20,26 +20,37 @@ program.option('-r, --run ', 'Run a file')
program.option('-c, --compile ', 'Compile a file')
program.option('-p, --print ', 'Print translated JavaScript')
program.option('-d, --delete ', 'Delete a file')
-program.action(() => {})
+func noOp():
+end
+program.action(noOp)
-try {
+handle:
program.parse()
const options = program.opts()
- const selected = [
- ['run', bsrCommand], ['compile', bstCommand],
- ['print', bspCommand], ['delete', bsdCommand]
- ].filter(([name]) => options[name] !== undefined)
+ const selected = []
+ for const name of ['run', 'compile', 'print', 'delete']:
+ if options[name] !== undefined:
+ selected.push(name)
+ end
+ end
- if (selected.length > 1) {
- throw new Error('Specify only one command at a time.')
- }
- if (selected.length === 1) {
- const [name, command] = selected[0]
- command(options[name])
- } else if (process.argv.length === 2) {
+ if selected.length > 1:
+ throw new Err('Specify only one command at a time.')
+ elif selected.length === 1:
+ const name = selected[0]
+ if name === 'run':
+ bsrCommand(options[name])
+ elif name === 'compile':
+ bstCommand(options[name])
+ elif name === 'print':
+ bspCommand(options[name])
+ else:
+ bsdCommand(options[name])
+ end
+ elif process.argv.length === 2:
program.error('A command is required.')
- }
-} catch (error) {
- console.error(`Error: ${error.message}`)
+ end
+recovery (error):
+ process.stderr.write(`Error: ${error.message}\n`)
process.exitCode = 1
-}
+end
diff --git a/src/translate.bs b/src/translate.bs
index 9451d04..c2b7f65 100644
--- a/src/translate.bs
+++ b/src/translate.bs
@@ -2,45 +2,40 @@ const fs = require('node:fs')
const path = require('node:path')
const { extensions } = require('../js/bsc.js')
const { parsing, parsingMsg, parseCode, addSemicolons } = require('../js/parser.js')
-const replacements = require('./parser/parsingDecl.js')
+const replacements = require('bynixscript/dist/parser/parsingDecl.js')
-function resolveFile(file) {
- if (typeof file !== 'string' || file.trim() === '') {
- throw new Error('A file path is required.')
- }
- if (![extensions.primary, extensions.secondary, extensions.module].some(extension => file.endsWith(extension))) {
- throw new Error(`Unsupported file extension: ${file}. Use ${extensions.primary}, ${extensions.secondary}, or ${extensions.module}.`)
- }
+func resolveFile(file):
+ if typeof file !== 'string' || file.trim() === '':
+ throw new Err('A file path is required.')
+ end
+ if !file.is_end(extensions.primary) && !file.is_end(extensions.secondary) && !file.is_end(extensions.module):
+ throw new Err(`Unsupported file extension: ${file}. Use ${extensions.primary}, ${extensions.secondary}, or ${extensions.module}.`)
+ end
const filePath = path.resolve(file)
- if (!fs.existsSync(filePath)) {
- throw new Error(`File not found: ${file}`)
- }
- if (!fs.statSync(filePath).isFile()) {
- throw new Error(`Not a file: ${file}`)
- }
+ if !fs.existsSync(filePath):
+ throw new Err(`File not found: ${file}`)
+ end
+ if !fs.statSync(filePath).isFile():
+ throw new Err(`Not a file: ${file}`)
+ end
return filePath
-}
+end
-function readSource(file) {
+func readSource(file):
const filePath = resolveFile(file)
return { filePath, source: fs.readFileSync(filePath, 'utf8') }
-}
+end
-function translate(source) {
- let code = addSemicolons(parseCode(source))
- if (parsing(code) === false) {
- throw new Error(parsingMsg.message)
- }
- for (const name of [
- 'flowReplace', 'utilityReplace', 'funcReplace', 'condReplace',
- 'forEachReplace', 'reassignReplace', 'assignReplace', 'logReplace',
- 'interactReplace', 'mathReplace', 'commentReplace', 'asyncReplace',
- 'forReplace', 'convReplace', 'checkReplace', 'oopReplace',
- 'excepReplace', 'domReplace'
- ]) {
- code = replacements[name](code)
- }
+func translate(source):
+ var code = addSemicolons(parseCode(source))
+ if parsing(code) === false:
+ throw new Err(parsingMsg.message)
+ end
+ const passes = 'flowReplace utilityReplace funcReplace condReplace forEachReplace reassignReplace assignReplace logReplace interactReplace mathReplace commentReplace asyncReplace forReplace convReplace checkReplace oopReplace excepReplace domReplace'.split(' ')
+ for const pass of passes:
+ code = replacements[pass](code)
+ end
return code
-}
+end
module.exports = { resolveFile, readSource, translate, extensions }
diff --git a/tests/cli.test.cjs b/tests/cli.test.cjs
index 020f7db..8d85392 100644
--- a/tests/cli.test.cjs
+++ b/tests/cli.test.cjs
@@ -4,6 +4,8 @@ const fs = require('node:fs')
const os = require('node:os')
const path = require('node:path')
const { spawnSync } = require('node:child_process')
+const { Script } = require('node:vm')
+const { compileSource } = require('../scripts/compile.cjs')
const root = path.resolve(__dirname, '..')
const binary = path.join(root, 'dist', 'index.min.cjs')
@@ -28,6 +30,19 @@ test('build produces the packaged executable', () => {
assert.equal(invoke(['--version']).status, 0)
})
+test('every CLI source compiles through the build compiler', () => {
+ for (const name of ['index', 'bsr', 'bst', 'bsp', 'bsd', 'translate']) {
+ const source = fs.readFileSync(path.join(root, 'src', `${name}.bs`), 'utf8')
+ const code = compileSource(source)
+ assert.match(source, /\bfunc \w+\(|\bhandle:/)
+ assert.doesNotMatch(source, /\bfunction\s+\w+\s*\(|\btry\s*\{/)
+ assert.match(code, /function \w+\(|try \{/)
+ assert.doesNotThrow(() => new Script(code, { filename: `${name}.js` }))
+ }
+ assert.match(compileSource('func valid(value):\n print(value)\nend'), /function valid\(value\) \{/)
+ assert.throws(() => new Script(compileSource('func invalid():\n print("x")\n')), SyntaxError)
+})
+
test('global and command help succeeds', () => {
assert.match(invoke(['--help']).stdout, /run /)
for (const command of ['run', 'compile', 'print', 'delete']) {
@@ -124,3 +139,12 @@ test('legacy file options still work', context => {
assert.equal(invoke(['-d', 'legacy.bys'], directory).status, 0)
assert.equal(fs.existsSync(path.join(directory, 'legacy.bys')), false)
})
+
+test('conflicting legacy commands fail without changing files', context => {
+ const directory = fixture(context)
+ fs.writeFileSync(path.join(directory, 'safe.bys'), 'print("safe")\n')
+ const result = invoke(['-p', 'safe.bys', '-d', 'safe.bys'], directory)
+ assert.notEqual(result.status, 0)
+ assert.match(result.stderr, /Specify only one command/)
+ assert.ok(fs.existsSync(path.join(directory, 'safe.bys')))
+})