From cdfbbb09809b2f8aae2578f598e6cbbfd45ad513 Mon Sep 17 00:00:00 2001
From: "coderabbitai[bot]"
<136622811+coderabbitai[bot]@users.noreply.github.com>
Date: Fri, 25 Sep 2026 01:04:26 +0000
Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20CodeRabbit=20Chat:=20Consolidate?=
=?UTF-8?q?=20JavaScript=20utilities=20and=20compiler=20paths=20in=20Bynix?=
=?UTF-8?q?Script?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +
js/browser.js | 407 +++-----------------------------------------
js/bsc.js | 40 +----
js/bsconfig.js | 40 +----
js/config.js | 31 ++++
js/dirProcess.js | 66 +++----
js/download.js | 23 +--
js/error.js | 80 +++------
js/watcher.js | 126 +++++---------
package.json | 2 +-
scripts/build.cjs | 23 +++
scripts/compile.cjs | 4 +-
src/config.bs | 31 ++++
src/dirProcess.bs | 29 ++++
src/download.bs | 15 ++
src/error.bs | 34 ++++
src/translate.bs | 2 +-
src/watcher.bs | 43 +++++
tests/cli.test.cjs | 171 ++++++++++++++++++-
19 files changed, 509 insertions(+), 660 deletions(-)
create mode 100644 js/config.js
create mode 100644 src/config.bs
create mode 100644 src/dirProcess.bs
create mode 100644 src/download.bs
create mode 100644 src/error.bs
create mode 100644 src/watcher.bs
diff --git a/README.md b/README.md
index 82dc81a..b5ddcd0 100644
--- a/README.md
+++ b/README.md
@@ -52,6 +52,8 @@ The build bootstraps the `.bs` CLI sources with the BynixScript compiler from th
`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.
+The build also bundles the shared compiler with the browser integration entry
+into `dist/browser.js`; browser configuration uses the default extensions.
### Using CDN
```html
diff --git a/js/browser.js b/js/browser.js
index 88af974..dfb68f1 100644
--- a/js/browser.js
+++ b/js/browser.js
@@ -1,382 +1,29 @@
-let parsingResults;
-let allowJs;
-const style = document.createElement("style");
-style.textContent = `
- bynix {
- display: none;
- }
- `;
-style.classList.add("bynix-style");
-document.head.appendChild(style);
-const milown = document.createElement("script");
-const ajax = document.createElement("script");
-const fontawesome = document.createElement("link");
-const brython = document.createElement("script");
-milown.src = '';
-ajax.src = 'https://cdnjs.cloudflare.com/ajax/libs/jquery/4.0.0-beta.2/jquery.min.js"';
-fontawesome.rel = "stylesheet";
-fontawesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css';
-brython.type = "type/javascript";
-brython.src = 'https://cdn.jsdelivr.net/npm/brython@3.9.6/brython.min.js';
-document.head.appendChild(ajax);
-document.head.appendChild(fontawesome);
-document.head.appendChild(milown);
-document.head.appendChild(brython);
-document.addEventListener("DOMContentLoaded", function() {
- const bynixTags = document.querySelectorAll("bynix");
- bynixTags.forEach(tag => {
- var code = '';
- const src = tag.getAttribute('src');
- const allowJsTag = tag.getAttribute('allowJs');
-
- if (allowJsTag === null) {
- allowJs = "false";
- } else {
- allowJs = allowJsTag;
- }
-
- // Jika src ada, lakukan fetch
- if (src) {
- const end = src.substring(src.lastIndexOf('.') + 1);
-
- if (src.endsWith(".bs") || src.endsWith(".bynixscript") || src.endsWith(".mbs")) {
- fetch(src)
- .then(response => response.text())
- .then(srcCode => {
- code = parseCode(srcCode);
- parsingResults = parsing(code);
- code = addSemicolons(code);
- executeCode(code);
- tag.remove();
- })
- .catch(error => console.error('Error fetching BynixScript code:', error));
- } else {
- console.error("Invalid extension '." + end + "'");
- }
- } else {
- // Jika src tidak ada, ambil kode dari textContent
- code = parseCode(tag.textContent);
- parsingResults = parsing(code);
- code = addSemicolons(code);
- executeCode(code);
- tag.remove();
- }
- });
-});
-
-function executeCode(code) {
- if (parsingResults === "false") {
- console.error(`Please check your code and try again.`);
- } else {
- // function
- code = code.replace(/func\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([^)]*)\):/g, (match, p1, p2) => `function ${p1}(${p2}) {`);
-
- // if, elif, else
- code = code.replace(/elif\s+(.+?):/g, (match, p1) => `} else if (${p1}) {`);
- code = code.replace(/if\s+(.+?):/g, (match, p1) => `if (${p1}) {`);
- code = code.replace(/else:/g, "} else {");
-
- // forEach
- code = code.replace(/forEach\((.+?)\)\:/g, (match, p1) => `forEach(${p1} => {`);
- code = code.replace(/forEach\((.+?)\s=>\s(.+?)\)/g, (match, p1, p2) => `forEach(${p1} => ${p2})`);
-
- // var, const
- code = code.replace(/var\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+?);/g, (match, p1, p2) => `var ${p1} = ${p2};`);
- code = code.replace(/const\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+?);/g, (match, p1, p2) => `const ${p1} = ${p2};`);
-
- // print, print.err, print.info, print.debug, print.trace, print.warn, print.assert
- code = code.replace(/print(\.[a-zA-Z]+)?\((.+?)\)/g, (match, p1, p2) => {
- const type = p1 || '';
-
- if (type.includes(".info") || type.includes(".debug") || type.includes(".trace") || type.includes(".warn") || type.includes(".assert")) {
- return `console${p1}(${p2})`
- } else if (type.includes(".err")) {
- return `console.error(${p2})`
- } else if (match.includes("/")) {
- return match;
- } else {
- return `console.log(${p2})`
+const { compileSource } = require('../scripts/compile.cjs')
+
+const style = document.createElement('style')
+style.textContent = 'bynix { display: none; }'
+style.classList.add('bynix-style')
+document.head.appendChild(style)
+
+document.addEventListener('DOMContentLoaded', () => {
+ document.querySelectorAll('bynix').forEach(async tag => {
+ const sourcePath = tag.getAttribute('src')
+ try {
+ if (sourcePath && !['.bs', '.bys', '.bynixscript', '.mbs'].some(extension => sourcePath.endsWith(extension))) {
+ throw new Error(`Unsupported BynixScript file: ${sourcePath}`)
}
- });
-
- // touch, ask, confirm
- code = code.replace(/touch\((.+?)\)/g, (match, p1) => `alert(${p1});`);
- code = code.replace(/ask\((.+?)\)/g, (match, p1) => `prompt(${p1});`);
- code = code.replace(/confirm\((.+?)\)/g, (match, p1) => `confirm(${p1});`);
-
- // rand, roundDown, roundUp, maxOf, minOf
- code = code.replace(/rand\((.+?)\)/g, (match, p1) => `Math.random(${p1})`);
- code = code.replace(/roundDown\((.+?)\)/g, (match, p1) => `Math.floor(${p1})`);
- code = code.replace(/roundUp\((.+?)\)/g, (match, p1) => `Math.ceil(${p1})`);
- code = code.replace(/maxOf\((.+?)\)/g, (match, p1) => `Math.min(${p1})`);
- code = code.replace(/minOf\((.+?)\)/g, (match, p1) => `Math.max(${p1})`);
-
- // getElement, getAllElement, createElement, addClass, addElement, listener
- code = code.replace(/getElement\((.+?)\)/g, (match, p1) => `document.querySelector(${p1});`);
- code = code.replace(/getAllElement\((.+?)\)/g, (match, p1) => `document.querySelectorAll(${p1});`);
- code = code.replace(/createElement\((.+?)\)/g, (match, p1) => `document.createElement(${p1});`);
- code = code.replace(/\.addClass\((.+?)\)/g, (match, p1) => `.classList.add(${p1})`);
- code = code.replace(/([^\s].+)\.addElement\((.+?)\)/g, (match, p1, p2) => `document.${p1}.appendChild(${p2});`);
- code = code.replace(/(.+?)\.listener\((.+?)\)\:/g, (match, p1, p2) => `${p1}.addEventListener(${p2}, () => {`)
- code = code.replace(/listener\((.+?)\)\:/g, (match, p1) => `document.addEventListener(${p1}, function() {`)
-
- // change
- code = code.replace(/(.+?)\.change\((.+?), \((.+?)\)\)\:/g, (match, p1, p2, p3) => `${p1}.replace(${p2}, (${p3}) => {`)
- code = code.replace(/(.+?)\.change\((.+)\)/g, (match, p1, p2) => `${p1}.replace(${p2})`);
-
- // text
- code = code.replace(/(.+?)\.text/g, (match, p1) => `${p1}.textContent`)
- code = code.replace(/(.+?)\.text = (.+?)/g, (match, p1, p2) => `${p1}.textContent = ${p2}`);
- code = code.replace(/(? `//${p1}`);
- code = code.replace(/\*\*(.+?)/g, (match, p1) => `/*${p1}`);
- code = code.replace(/(.+?)\*\*/g, (match, p1) => `${p1}*/`);
-
- // condition property
- code = code.replace(/is_includes\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1) => `includes(${p1})`);
- code = code.replace(/is_start\s===\s([a-zA-Z0-9"'`!?*#@$_&-+|.,:;=×÷%/]+)/g, (match, p1) => `startsWith(${p1})`);
- code = code.replace(/is_end\s===\s([a-zA-Z0-9"'`!?*#@$_&-+|.,:;=×÷%/]+)/g, (match, p1) => `endsWith(${p1})`);
- code = code.replace(/is_matched\s===\s"(.+?)"/g, (match, p1) => `match(${p1})`);
- code = code.replace(/is_matched\s===\s'(.+?)'/g, (match, p1) => `match(${p1})`);
- code = code.replace(/is_value\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1) => `value === ${p1}`);
- code = code.replace(/is_design.(.+?)\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1, p2) => `style.${p1} === ${p2}`);
- code = code.replace(/(.+?)\.design\.(.+?)\s=\s(.+?)/g, (match, p1, p2, p3) => `${p1}.style.${p2} = ${p3}`);
- code = code.replace(/(.+?)\.design\s=\s(.+?)/g, (match, p1, p2) => `${p1}.style = ${p2}`);
- code = code.replace(/is_action\s([=!><]+)\s(["'])checked(["'])/g, (match) => `checked`);
- code = code.replace(/is_action\s([=!><]+)\s(["'])selected(["'])/g, (match) => `selected`);
- code = code.replace(/is_action\s([=!><]+)\s(["'])open(["'])/g, (match) => `open`);
- code = code.replace(/is_have\s([=!><]+)\s(["'])required(["'])/g, (match) => `required`);
- code = code.replace(/is_have\s([=!><]+)\s(["'])readOnly(["'])/g, (match) => `readOnly`);
- code = code.replace(/is_have\s([=!><]+)\s(["'])autofocus(["'])/g, (match) => `autofocus`);
- code = code.replace(/is_have\s([=!><]+)\s(["'])disabled(["'])/g, (match) => `disabled`);
- code = code.replace(/is_have\s([=!><]+)\s(["'])multiple(["'])/g, (match) => `multiple`);
- code = code.replace(/is_have\s([=!><]+)\s(["'])hidden(["'])/g, (match) => `hidden`);
- code = code.replace(/delay\:\n\s+(.+?)/g, (match, p1) => `setTimeout(function() {\n ${p1}`);
-
- // loop
- code = code.replace(/repeat\:\n\s+(.+?)/g, (match, p1) => `setInterval(function() {\n ${p1}`);
- code = code.replace(/for (.+?)=(.+?) to (.+?)\:/g, (match, p1, p2, p3) => `for (let ${p1} = ${p2}; ${p1} < ${p3}; ${p1}++) {`);
- code = code.replace(/for (.+?) in (.+?)\:/g, (match, p1, p2) => `for (let ${p1} in ${p2}) {`);
- code = code.replace(/for (.+?) of (.+?)\:/g, (match, p1, p2) => `for (let ${p1} of ${p2}) {`);
- code = code.replace(/(? `switch ((${p1})) {`)
- code = code.replace("switch ((", "switch (")
- code = code.replace(")) {", ") {")
- code = code.replace(/case (.+?):\n(.+?)/g, (match, p1, p2) => `case ${p1}: \n${p2}`)
- code = code.replace(/([^\s].+?)\.image\s*=\s*(['"`][^'"`]*['"``])/g, (match, p1, p2) => `image(${p1}, ${p2})`);
- code = code.replace(/process\.argv\[(\d+)\]/g, (match, p1) => {
- var a = Number(p1) + 0;
- return `process.argv[${a}]`;
- });
- code = code.replace(/int\((.+?)\)/g, (match, p1) => `parseInt(${p1})`)
- code = code.replace(/num\((.+?)\)/g, (match, p1) => `Number(${p1})`)
- code = code.replace(/str\((.+?)\)/g, (match, p1) => `String(${p1})`)
- code = code.replace(/float\((.+?)\)/g, (match, p1) => `parseFloat(${p1})`)
- code = code.replace(/json\((.+?)\)/g, (match, p1) => `JSON.parse(${p1})`);
- code = code.replace(/(.+?)\.map\((.+?)\)\:/g, (match, p1, p2) => `${p1}.map(function(${p2}) {`)
- code = code.replace(/\.map\((.+?)\)/g, (match, p1) => `.map(${p1})`);
- code = code.replace(/(.+?)\.filter\((.+?)\)\:/g, (match, p1, p2) => `${p1}.filter(function(${p2}) {`);
- code = code.replace(/\.filter\((.+?)\)/g, (match, p1) => `.filter(${p1})`);
- code = code.replace(/(.+?)\.reduce\((.+?)\)\:/g, (match, p1, p2) => `${p1}.reduce(function(${p2}) {`)
- code = code.replace(/\.reduce\((.+?)\)/g, (match, p1) => `.reduce(${p1})`);
- code = code.replace(/(? `typeof ${p1}`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_type\s===\s([a-zA-Z0-9"'`]+)/g, (match, p1, p2) => `typeof ${p1} === ${p2}`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])type(["'])/g, (match, p1) => `${p1} instanceof TypeError`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])reference(["'])/g, (match, p1) => `${p1} instanceof ReferenceError`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])syntax(["'])/g, (match, p1) => `${p1} instanceof SyntaxError`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])range(["'])/g, (match, p1) => `${p1} instanceof RangeError`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])uri(["'])/g, (match, p1) => `${p1} instanceof URIError`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])eval(["'])/g, (match, p1) => `${p1} instanceof EvalError`)
- code = code.replace(/([a-zA-Z0-9_$]+)\.is_error\s===\s(["'])(.+?)(["'])/g, (match, p1, p2, p3) => `${p1} instanceof ${p3}`)
- code = code.replace(/constructor\((.+?)\)\:/g, (match, p1) => `constructor(${p1}) {`);
- code = code.replace(/(.+?)\(\)\:/g, (match, p1) => `${p1}() {`);
- code = code.replace(/class\s(.+?)\:/g, (match, p1) => `class ${p1} {`);
- code = code.replace(/class\s(.+?)\sextends\s(.+?)\:/g, (match, p1, p2) => `class ${p1} extends ${p2} {`);
- code = code.replace(/static\((.+?)\)\ {/g, (match, p1) => `static(${p1}) {`);
- code = code.replace(/super\((.+?)\)\:/g, (match, p1) => `super(${p1})`)
- code = code.replace(/throw (.+?)/g, (match, p1) => `throw ${p1}`)
- code = code.replace(/new (.+?)/g, (match, p1) => `new ${p1}`)
- code = code.replace(/end\:(.+?)/g, (match, p1) => `}, ${p1});`);
- code = code.replace(/handle\:/g, 'try {')
- code = code.replace(/recovery\s\((.+?)\)\:/g, (match, p1) => `} catch (${p1}) {`)
- code = code.replace(/final\:/g, "} finally {")
- code = code.replace(/handle\:/g, 'try {')
- code = code.replace(/recovery\s*\((.+?)\)\:/g, (match, p1) => `} catch (${p1}) {`)
- code = code.replace(/final\:/g, "} finally {")
- code = code.replace(/Err\((.+?)\)/g, (match, p1) => `Error(${p1})`)
- code = code.replace(/URIErr\((.+?)\)/g, (match, p1) => `URIError(${p1})`)
- code = code.replace(/EvalErr\((.+?)\)/g, (match, p1) => `EvalError(${p1})`)
- code = code.replace(/TypeErr\((.+?)\)/g, (match, p1) => `TypeError(${p1})`)
- code = code.replace(/RangeErr\((.+?)\)/g, (match, p1) => `RangeError(${p1})`)
- code = code.replace(/SyntaxErr\((.+?)\)/g, (match, p1) => `Error(${p1})`)
- code = code.replace(/InternalErr\((.+?)\)/g, (match, p1) => `InternalError(${p1})`)
- code = code.replace(/ReferenceErr\((.+?)\)/g, (match, p1) => `ReferenceError(${p1})`)
-
- const script = document.createElement("script");
- script.textContent = code;
- document.body.appendChild(script);
- }
-}
-
-function image(element, imageClass) {
- if (!element) {
- console.error(`Element tidak ditemukan.`);
- return;
- }
-
- const icon = document.createElement("i");
-
- // Pisahkan class yang diberikan berdasarkan spasi
- const classes = imageClass.split(' ');
- classes.forEach(cls => icon.classList.add(cls));
-
- element.appendChild(icon);
-}
-
-function parsing(variable) {
- const patterns = [
- /let\s*(.+?)=(.+?)/g,
- /function\s*(.+?)\((.+?)\)/g,
- /if\s*\((.+?)\)\s*{/g,
- /else if\s*\((.+?)\)\s*{/g,
- /else\s*{\s*/g,
- /console(.+?)\((.+?)\)/g,
- /alert\((.+?)\)/g,
- /prompt\((.+?)\)/g,
- /Math\.random\((.+?)\)/g,
- /Math\.floor\((.+?)\)/g,
- /Math\.ceil\((.+?)\)/g,
- /Math\.min\((.+?)\)/g,
- /Math\.max\((.+?)\)/g,
- /document\.createElement\((.+?)\)/g,
- /document\.(.+?)\.appendChild\((.+?)\)/g,
- /(.+?).replace\((.+?)\)/g,
- /\/\//g,
- /(.+?)\.textContent\s*=\s*(.+?)/g,
- /\/\*(.+?)\*\//g,
- /document\.addEventListener\((.+?), function\(\) \{/g,
- /forEach\((.+?) \=\> \{/g,
- /(.+?)\.includes\((.+?)\)/g,
- /(.+?)\.match\((.+?)\)/g,
- /(.+?)\.value ===/g,
- /(.+?)\.value ==/g,
- /(.+?)\.value =/g,
- /(.+?)\.value !===/g,
- /(.+?)\.value !==/g,
- /(.+?)\.value !=/g,
- /(.+?)\.value >/g,
- /(.+?)\.value /g,
- /(.+?)\.value <==/g,
- /(.+?)\.style ===/g,
- /(.+?)\.style ==/g,
- /(.+?)\.style =/g,
- /(.+?)\.style !===/g,
- /(.+?)\.style !==/g,
- /(.+?)\.style !=/g,
- /(.+?)\.style >/g,
- /(.+?)\.style /g,
- /(.+?)\.style <==/g,
- /(.+?)\.style\.(.+?) =/g,
- /(.+?)\.style =/g,
- /(.+?)\.checked/g,
- /(.+?)\.required/g,
- /setTimeout\(function\(\) \{/g,
- /setInterval\(function\(\) \{/g,
- /for \((.+?)\) \{/g,
- /classList\.add\((.+?)\)/g,
- /switch\s*\((.+?)\)/g,
- /image\((.+?), (.+?)\)/g,
- /class\s*(.+?)\s*\{/g,
- /class\s*(.+?)\s*extends\s*(.+?)\s*\{/g,
- /constructor(.+?)\s*\{/g,
- /(.+?)\(\)\s*\{/g,
- /static(.+?)\s*\{/g,
- /typeof\s*(.+?)\s*(.+?)\s*(.+?)/g,
- /\.map\(function\((.+?)\)\s*\{/g,
- /\.filter\(function\((.+?)\)\s*\{/g,
- /\.reduce\(function\((.+?)\)\s*\{/g,
- /try\s*{/g,
- /}\s*catch\s*(.+?)\s*{/g,
- /}\s*finally\s*{/g,
- ];
-
- if (allowJs === "false") {
- const matches = patterns.map(pattern => {
- const match = variable.match(pattern);
- return match ? match[0] : null;
- }).filter(Boolean);
-
- if (matches.length > 0) {
- parsingMsg = "Invalid function '" + matches.join("', '") + "'";
- const error = new Error(parsingMsg);
- console.error(error);
- return "false";
- } else {
- parsingMsg = "";
- return "true";
- }
- }
-}
-
-function parseCode(code) {
- const lines = code.split('\n');
- const output = [];
- const stack = [];
-
- const regex = {
- listener: /(.+?)\.listener/,
- classFunc: /(.+?)\(\)\:/,
- change: /(.+?)\.change\((.+?), \((.+?)\)\)\:/,
- map: /(.+?)\.map\((.+?)\)\:/,
- filter: /(.+?)\.filter\((.+?)\)\:/,
- forEach: /forEach\((.+?)\)\:/,
- func: /func\s*(.+?)\((.+?)\)/,
- blockStart: /^(if|elif|else|class|constructor|static|final|recovery|func)/,
- };
-
- for (let line of lines) {
- const trimmedLine = line.trim();
-
- if (regex.blockStart.test(trimmedLine)) {
- stack.push('block');
- output.push(line);
- } else if (regex.listener.test(trimmedLine) || regex.change.test(trimmedLine) ||
- regex.map.test(trimmedLine) || regex.filter.test(trimmedLine) || regex.forEach.test(trimmedLine)) {
- stack.push('listener');
- output.push(line);
- } else if (regex.func.test(trimmedLine)) {
- stack.push('func');
- output.push(line);
- } else if (trimmedLine === 'end') {
- const indent = line.match(/^\s*/)[0];
- const lastBlock = stack.pop();
- output.push(lastBlock === 'listener' ? `${indent}});` : `${indent}}`);
- } else {
- output.push(line);
- }
- }
-
- return output.join('\n');
-}
-
-function addSemicolons(code) {
- const lines = code.split('\n');
- const output = [];
-
- const regex = {
- needsSemicolon: /^(var|const|return|throw|break|continue|.*\=.*|[^;{}]+\))$/,
- alreadyTerminated: /[:;{}]\s*$/
- };
-
- for (let line of lines) {
- const trimmedLine = line.trim();
-
- if (regex.needsSemicolon.test(trimmedLine) && !regex.alreadyTerminated.test(trimmedLine)) {
- output.push(line + ';');
- } else {
- output.push(line);
- }
+ const source = sourcePath
+ ? await fetch(sourcePath).then(response => {
+ if (!response.ok) throw new Error(`Failed to fetch ${sourcePath}: ${response.status}`)
+ return response.text()
+ })
+ : tag.textContent
+ const script = document.createElement('script')
+ script.textContent = compileSource(source, tag.getAttribute('allowJs') === 'true')
+ document.body.appendChild(script)
+ tag.remove()
+ } catch (error) {
+ console.error('Error executing BynixScript code:', error)
}
-
- return output.join('\n');
-}
+ })
+})
diff --git a/js/bsc.js b/js/bsc.js
index 87ab6a0..46c72e1 100644
--- a/js/bsc.js
+++ b/js/bsc.js
@@ -1,39 +1 @@
-const fs = require('fs');
-
-const defaultConfig = {
- extension: {
- primary: '.bys',
- secondary: '.bynixscript',
- module: '.mbs'
- },
- readFolder: './',
- toFolder: './',
- watch: false,
- allowJs: false,
- strict: false,
- translate: 'javascript'
-};
-
-let config;
-
-try {
- const data = fs.readFileSync('./bsc.json', 'utf8');
- config = JSON.parse(data);
-} catch (err) {
- config = defaultConfig;
-}
-
-const extensions = {
- primary: config.extension?.primary || defaultConfig.extension.primary,
- secondary: config.extension?.secondary || defaultConfig.extension.secondary,
- module: config.extension?.module || defaultConfig.extension.module
-};
-
-const readFolder = config.readFolder || defaultConfig.readFolder;
-const toFolder = config.toFolder || defaultConfig.toFolder;
-const watch = config.watch || defaultConfig.watch;
-const allowJs = config.allowJs || defaultConfig.allowJs;
-const strict = config.strict || defaultConfig.strict;
-const translate = config.translate || defaultConfig.translate;
-
-module.exports = { defaultConfig, config, extensions, toFolder, readFolder, watch, allowJs, strict, translate };
+module.exports = require('./config.js').loadConfig('./bsc.json', '.bys')
diff --git a/js/bsconfig.js b/js/bsconfig.js
index ad6211c..765c9af 100644
--- a/js/bsconfig.js
+++ b/js/bsconfig.js
@@ -1,39 +1 @@
-const fs = require('fs');
-
-const defaultConfig = {
- extension: {
- primary: '.bs',
- secondary: '.bynixscript',
- module: '.mbs'
- },
- readFolder: './',
- toFolder: './',
- watch: false,
- allowJs: false,
- strict: false,
- translate: 'javascript'
-};
-
-let config;
-
-try {
- const data = fs.readFileSync('./bsconfig.json', 'utf8');
- config = JSON.parse(data);
-} catch (err) {
- config = defaultConfig;
-}
-
-const extensions = {
- primary: config.extension?.primary || defaultConfig.extension.primary,
- secondary: config.extension?.secondary || defaultConfig.extension.secondary,
- module: config.extension?.module || defaultConfig.extension.module
-};
-
-const readFolder = config.readFolder || defaultConfig.readFolder;
-const toFolder = config.toFolder || defaultConfig.toFolder;
-const watch = config.watch || defaultConfig.watch;
-const allowJs = config.allowJs || defaultConfig.allowJs;
-const strict = config.strict || defaultConfig.strict;
-const translate = config.translate || defaultConfig.translate;
-
-module.exports = { defaultConfig, config, extensions, toFolder, readFolder, watch, allowJs, strict, translate };
+module.exports = require('./config.js').loadConfig('./bsconfig.json', '.bs')
diff --git a/js/config.js b/js/config.js
new file mode 100644
index 0000000..b5017e5
--- /dev/null
+++ b/js/config.js
@@ -0,0 +1,31 @@
+const fs = require('node:fs');
+
+function loadConfig(filename, primary) {
+ const defaultConfig = {
+ extension: { primary, secondary: '.bynixscript', module: '.mbs' },
+ readFolder: './', toFolder: './', watch: false,
+ allowJs: false, strict: false, translate: 'javascript'
+ }
+ var config
+ try {
+ config = JSON.parse(fs.readFileSync(filename, 'utf8'));
+ } catch (error) {
+ config = defaultConfig;
+ }
+ const extensions = {
+ primary: config.extension?.primary || defaultConfig.extension.primary,
+ secondary: config.extension?.secondary || defaultConfig.extension.secondary,
+ module: config.extension?.module || defaultConfig.extension.module
+ }
+ return {
+ defaultConfig, config, extensions,
+ readFolder: config.readFolder || defaultConfig.readFolder,
+ toFolder: config.toFolder || defaultConfig.toFolder,
+ watch: config.watch || defaultConfig.watch,
+ allowJs: config.allowJs || defaultConfig.allowJs,
+ strict: config.strict || defaultConfig.strict,
+ translate: config.translate || defaultConfig.translate
+ }
+}
+
+module.exports = { loadConfig }
diff --git a/js/dirProcess.js b/js/dirProcess.js
index a05a4bf..46ae5e8 100644
--- a/js/dirProcess.js
+++ b/js/dirProcess.js
@@ -1,47 +1,29 @@
-const fs = require('fs');
-const path = require('path');
-const vm = require('vm');
-const { translate } = require('./parser.js');
+const fs = require('node:fs');
+const path = require('node:path');
+const vm = require('node:vm');
+const { compileSource } = require('../scripts/compile.cjs');
function readAndProcessFilesInDirectory(directory) {
- // Baca semua file dalam direktori
- fs.readdir(directory, (err, files) => {
- if (err) {
- console.error("Error reading directory:", err);
- return;
+ fs.readdir(directory, (error, files) => {
+ if (error) {
+ console.error('Error reading directory:', error);
+ return;
+ }
+ files.filter(file => path.extname(file) === '.bs').forEach(file => {
+ const filePath = path.join(directory, file);
+ fs.readFile(filePath, 'utf8', (readError, data) => {
+ if (readError) {
+ console.error(`Error reading file ${file}:`, readError)
+ return;
}
-
- // Filter hanya file .bs
- const bsFiles = files.filter(file => path.extname(file) === '.bs');
-
- // Proses setiap file .bs
- bsFiles.forEach(file => {
- const filePath = path.join(directory, file);
-
- // Baca isi file
- fs.readFile(filePath, 'utf8', (err, data) => {
- if (err) {
- console.error(`Error reading file ${file}:`, err);
- return;
- }
-
- // Konversi "print" menjadi "console.log"
- let processedData = translate(code);
-
- // Eksekusi kode yang telah diproses menggunakan vm
- try {
- const script = new vm.Script(processedData);
- script.runInThisContext();
- } catch (err) {
- console.error(`Error executing file ${file}:`, err);
- }
- });
- });
- });
+ try {
+ new vm.Script(compileSource(data), { filename: filePath }).runInThisContext()
+ } catch (executionError) {
+ console.error(`Error executing file ${file}:`, executionError)
+ }
+ })
+ })
+ })
}
-// Panggil fungsi ini dengan direktori yang berisi file .bs
-readAndProcessFilesInDirectory('./src');
-
-// Ekspor fungsi jika dibutuhkan untuk digunakan di file lain
-module.exports = { readAndProcessFilesInDirectory };
\ No newline at end of file
+module.exports = { readAndProcessFilesInDirectory }
diff --git a/js/download.js b/js/download.js
index 36749df..2a008e9 100644
--- a/js/download.js
+++ b/js/download.js
@@ -1,22 +1,15 @@
-const https = require('https');
+const https = require('node:https');
function getDownloads(packageName, time) {
const url = `https://api.npmjs.org/downloads/point/last-${time}/${packageName}`;
-
- https.get(url, (res) => {
+ https.get(url, response => {
let data = '';
-
- res.on('data', (chunk) => {
- data += chunk;
- });
-
- res.on('end', () => {
+ response.on('data', chunk => { data += chunk });
+ response.on('end', () => {
const jsonData = JSON.parse(data);
- console.log(`Package ${packageName} downloaded ${jsonData.downloads} times.`);
- });
- }).on('error', (e) => {
- console.error(`Error: ${e.message}`);
- });
+ console.log(`Package ${packageName} downloaded ${jsonData.downloads} times.`)
+ })
+ }).on('error', error => { console.error(`Error: ${error.message}`) });
}
-module.exports = { https, getDownloads };
+module.exports = { https, getDownloads }
diff --git a/js/error.js b/js/error.js
index 018b5ee..a0df077 100644
--- a/js/error.js
+++ b/js/error.js
@@ -1,58 +1,34 @@
function stackParsing(filename, errorMsg, errorStack, code, type, modulLocation) {
- if (code !== undefined && code !== null) {
- const stackLines = errorStack.split('\n');
- let codeLines = code.split('\n');
-
- const errorLine = stackLines[0];
- const parts = errorLine.split(':');
- let line;
- let column
- let tabs;
- let arrow;
- let errorCode;
- if (type === "SyntaxError") {
- const errorLine = stackLines[0];
- const parts = errorLine.split(':');
- line = parseInt(parts[1].trim());
- let l = line - 2;
- errorCode = codeLines[l].trim();
-
- return `${type}: ${errorMsg}\n at file ${filename}\n\nSnippet:\n${errorCode}\n`;
- } else if (type === "TypeError" || type === "ReferenceError" || type === "RangeError" || type === "URIError" || type === "EvalError") {
- const errorLine = stackLines[5];
- const parts = errorLine.split(':');
- line = parseInt(parts[1].trim());
- column = parseInt(parts[2].trim());
-
- if (column > 6) {
- column -= 6;
- }
-
- let l = line - 1;
- const codeNoTrim = codeLines[l]
- errorCode = codeLines[l].trim();
- const spaces = codeNoTrim.match(/^ +/);
- const spacesCount = spaces ? spaces[0].length : 0;
- let x = column - spacesCount - 1;
- if (x < 0) {
- x = Math.abs(x);
- x -= 2;
- }
- tabs = ' '.repeat(x);
- arrow = tabs + '^';
-
- return `${type}: ${errorMsg}\n at file ${filename}\n at line ${line}, column ${column}\n\nSnippet:\n${errorCode}\n${arrow}`;
- } else {
- let commonErr = errorMsg
- commonErr = commonErr.replace(/\sstack/g, '');
- commonErr = commonErr.replace(/bst\.js/g, filename);
- commonErr = commonErr.replace(/bsr\.js/g, filename);
- commonErr = commonErr.replace(/bsp\.js/g, filename);
- return `${type}: ${commonErr}`;
+ if (code === undefined || code === null) {
+ return errorMsg
+ }
+ const stackLines = errorStack.split('\n');
+ const codeLines = code.split('\n');
+ if (type === 'SyntaxError') {
+ const line = parseInt(stackLines[0].split(':')[1].trim());
+ const errorCode = codeLines[line - 2].trim();
+ return `${type}: ${errorMsg}\n at file ${filename}\n\nSnippet:\n${errorCode}\n`
+ } else if (type === 'TypeError' || type === 'ReferenceError' || type === 'RangeError' || type === 'URIError' || type === 'EvalError') {
+ const parts = stackLines[5].split(':');
+ const line = parseInt(parts[1].trim());
+ let column = parseInt(parts[2].trim());
+ if (column > 6) {
+ column -= 6;
+ }
+ const codeNoTrim = codeLines[line - 1];
+ const errorCode = codeNoTrim.trim();
+ const spaces = codeNoTrim.match(/^ +/);
+ const spacesCount = spaces ? spaces[0].length : 0;
+ let offset = column - spacesCount - 1;
+ if (offset < 0) {
+ offset = Math.abs(offset) - 2;
}
+ const arrow = ' '.repeat(offset) + '^';
+ return `${type}: ${errorMsg}\n at file ${filename}\n at line ${line}, column ${column}\n\nSnippet:\n${errorCode}\n${arrow}`
} else {
- return errorMsg;
+ const commonErr = errorMsg.replace(/\sstack/g, '').replace(/bst\.js|bsr\.js|bsp\.js/g, filename);
+ return `${type}: ${commonErr}`
}
}
-module.exports = { stackParsing };
\ No newline at end of file
+module.exports = { stackParsing }
diff --git a/js/watcher.js b/js/watcher.js
index 4f46d89..98f338e 100644
--- a/js/watcher.js
+++ b/js/watcher.js
@@ -1,93 +1,43 @@
const chokidar = require('chokidar');
-const fs = require('fs');
-const path = require('path');
-const { parsing, parsingMsg, parseCode } = require('./parser.js')
-const { funcReplace, condReplace, forEachReplace, reassignReplace, assignReplace, logReplace, interactReplace, mathReplace, domReplace, commentReplace, utilityReplace, asyncReplace, flowReplace, forReplace, convReplace, checkReplace, oopReplace, excepReplace, otherReplace } = require('../raw/parser/parsingDecl.js')
+const fs = require('node:fs');
+const path = require('node:path');
+const { compileSource } = require('../scripts/compile.cjs');
function watching(directory, directory2) {
- let code;
-
- const watcher = chokidar.watch(directory, {
- ignored: /(^|[\/\\])\../,
- persistent: true
- });
-
- watcher
- .on('add', filePath => {
- code = fs.readFileSync(filePath, 'utf8');
- code = parseCode(code);
- const parsingResults = parsing(code);
- if (parsingResults === false) {
- console.log("Please check your code and try again.")
- } else {
- code = flowReplace(code)
- code = utilityReplace(code)
- code = funcReplace(code)
- code = condReplace(code)
- code = forEachReplace(code)
- code = reassignReplace(code)
- code = assignReplace(code)
- code = logReplace(code)
- code = interactReplace(code)
- code = mathReplace(code)
- code = commentReplace(code)
- code = asyncReplace(code)
- code = forReplace(code)
- code = convReplace(code)
- code = checkReplace(code)
- code = oopReplace(code)
- code = excepReplace(code)
- code = domReplace(code)
- var newFilePath = path.join(directory2, path.basename(filePath));
- newFilePath = newFilePath.replace(".bs", ".js");
- fs.writeFileSync(newFilePath, code);
-
- console.log(`File ${newFilePath} has been created.`);
- }
- })
- .on('change', filePath => {
- code = fs.readFileSync(filePath, 'utf8');
- code = parseCode(code);
- const parsingResults = parsing(code);
- if (parsingResults === false) {
- console.log("Please check your code and try again.")
- } else {
- code = flowReplace(code)
- code = utilityReplace(code)
- code = funcReplace(code)
- code = condReplace(code)
- code = forEachReplace(code)
- code = reassignReplace(code)
- code = assignReplace(code)
- code = logReplace(code)
- code = interactReplace(code)
- code = mathReplace(code)
- code = commentReplace(code)
- code = asyncReplace(code)
- code = forReplace(code)
- code = convReplace(code)
- code = checkReplace(code)
- code = oopReplace(code)
- code = excepReplace(code)
- code = domReplace(code)
- var newFilePath = path.join(directory2, path.basename(filePath));
- newFilePath = newFilePath.replace(".bs", ".js");
- fs.writeFileSync(newFilePath, code);
-
- console.log(`File ${newFilePath} has been changed.`);
- }
- })
- .on('unlink', filePath => {
- var newFilePath = path.join(directory2, path.basename(filePath));
- newFilePath = newFilePath.replace(".bs", ".js");
- fs.unlinkSync(newFilePath);
-
- console.log(`File ${newFilePath} has been deleted.`);
- });
-
- watcher
- .on('error', error => console.log(`Watcher error: ${error}`))
- .on('ready', () => console.log('Initial scan complete. Ready for changes'));
+ const watcher = chokidar.watch(directory, { ignored: /(^|[\/\\])\../, persistent: true });
+ const extensions = ['.bs', '.bys', '.bynixscript', '.mbs'];
+ function outputPath(filePath) {
+ const extension = path.extname(filePath);
+ if (!extensions.includes(extension)) {
+ return null
+ }
+ return path.join(directory2, path.basename(filePath, extension) + (extension === '.mbs' ? '.mjs' : '.js'));
+ }
+ function compileFile(filePath) {
+ const output = outputPath(filePath);
+ if (!output) {
+ return;
+ }
+ try {
+ const source = fs.readFileSync(filePath, 'utf8');
+ const code = compileSource(source);
+ fs.writeFileSync(output, code);
+ console.log(`File ${output} has been created or changed.`)
+ } catch (error) {
+ console.error(`Error compiling ${filePath}:`, error)
+ }
+ }
+ function removeFile(filePath) {
+ const output = outputPath(filePath);
+ if (output && fs.existsSync(output)) {
+ fs.unlinkSync(output);
+ console.log(`File ${output} has been deleted.`)
+ }
+ }
+ watcher.on('add', compileFile).on('change', compileFile).on('unlink', removeFile);
+ watcher.on('error', error => console.error('Watcher error:', error));
+ watcher.on('ready', () => console.log('Initial scan complete. Ready for changes'));
+ return watcher
}
-module.exports = { watching };
\ No newline at end of file
+module.exports = { watching }
diff --git a/package.json b/package.json
index 6ca3865..e06ab7e 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.2.2-next",
"description": "This is new Programming Language.",
"type": "commonjs",
- "browser": "js/browser.js",
+ "browser": "dist/browser.js",
"main": "./index.js",
"scripts": {
"build": "node scripts/build.cjs",
diff --git a/scripts/build.cjs b/scripts/build.cjs
index a411f77..203dbba 100644
--- a/scripts/build.cjs
+++ b/scripts/build.cjs
@@ -7,9 +7,15 @@ 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')
+const modules = ['config', 'download', 'error', 'dirProcess', 'watcher']
async function main() {
try {
+ for (const name of modules) {
+ const code = compileSource(fs.readFileSync(path.join(source, `${name}.bs`), 'utf8'))
+ fs.writeFileSync(path.join(root, 'js', `${name}.js`),
+ code.endsWith('\n') ? code : code + '\n')
+ }
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')),
@@ -27,6 +33,23 @@ async function main() {
target: 'node20'
})
fs.chmodSync(output, 0o755)
+ await build({
+ entryPoints: [path.join(root, 'js', 'browser.js')],
+ outfile: path.join(root, 'dist', 'browser.js'),
+ platform: 'browser',
+ format: 'iife',
+ bundle: true,
+ plugins: [{
+ name: 'browser-config',
+ setup(bundle) {
+ bundle.onResolve({ filter: /^fs$/ }, () => ({ path: 'fs', namespace: 'browser-config' }))
+ bundle.onLoad({ filter: /.*/, namespace: 'browser-config' }, () => ({
+ contents: 'module.exports = { readFileSync() { throw new Error("No config file in browser") } }',
+ loader: 'js'
+ }))
+ }
+ }]
+ })
} finally {
fs.rmSync(temporary, { recursive: true, force: true })
}
diff --git a/scripts/compile.cjs b/scripts/compile.cjs
index 6587b6b..73810ed 100644
--- a/scripts/compile.cjs
+++ b/scripts/compile.cjs
@@ -92,10 +92,10 @@ function protectSource(source) {
return { masked, restore: text => text.replace(token, (match, index) => protectedText[Number(index)]) }
}
-function compileSource(source) {
+function compileSource(source, allowJs = false) {
const { masked, restore } = protectSource(source)
let code = addSemicolons(parseCode(masked))
- if (parsing(code) === false) {
+ if (!allowJs && parsing(code) === false) {
throw new Error(parsingMsg.message)
}
for (const pass of passes) {
diff --git a/src/config.bs b/src/config.bs
new file mode 100644
index 0000000..cc869f4
--- /dev/null
+++ b/src/config.bs
@@ -0,0 +1,31 @@
+const fs = require('node:fs')
+
+func loadConfig(filename, primary):
+ const defaultConfig = {
+ extension: { primary, secondary: '.bynixscript', module: '.mbs' },
+ readFolder: './', toFolder: './', watch: false,
+ allowJs: false, strict: false, translate: 'javascript'
+ }
+ var config
+ handle:
+ config = JSON.parse(fs.readFileSync(filename, 'utf8'))
+ recovery (error):
+ config = defaultConfig
+ end
+ const extensions = {
+ primary: config.extension?.primary || defaultConfig.extension.primary,
+ secondary: config.extension?.secondary || defaultConfig.extension.secondary,
+ module: config.extension?.module || defaultConfig.extension.module
+ }
+ return {
+ defaultConfig, config, extensions,
+ readFolder: config.readFolder || defaultConfig.readFolder,
+ toFolder: config.toFolder || defaultConfig.toFolder,
+ watch: config.watch || defaultConfig.watch,
+ allowJs: config.allowJs || defaultConfig.allowJs,
+ strict: config.strict || defaultConfig.strict,
+ translate: config.translate || defaultConfig.translate
+ }
+end
+
+module.exports = { loadConfig }
diff --git a/src/dirProcess.bs b/src/dirProcess.bs
new file mode 100644
index 0000000..74ed5cf
--- /dev/null
+++ b/src/dirProcess.bs
@@ -0,0 +1,29 @@
+const fs = require('node:fs')
+const path = require('node:path')
+const vm = require('node:vm')
+const { compileSource } = require('../scripts/compile.cjs')
+
+func readAndProcessFilesInDirectory(directory):
+ fs.readdir(directory, (error, files) => {
+ if (error) {
+ console.error('Error reading directory:', error)
+ return
+ }
+ files.filter(file => path.extname(file) === '.bs').forEach(file => {
+ const filePath = path.join(directory, file)
+ fs.readFile(filePath, 'utf8', (readError, data) => {
+ if (readError) {
+ console.error(`Error reading file ${file}:`, readError)
+ return
+ }
+ handle:
+ new vm.Script(compileSource(data), { filename: filePath }).runInThisContext()
+ recovery (executionError):
+ console.error(`Error executing file ${file}:`, executionError)
+ end
+ })
+ })
+ })
+end
+
+module.exports = { readAndProcessFilesInDirectory }
diff --git a/src/download.bs b/src/download.bs
new file mode 100644
index 0000000..bd22c06
--- /dev/null
+++ b/src/download.bs
@@ -0,0 +1,15 @@
+const https = require('node:https')
+
+func getDownloads(packageName, time):
+ const url = `https://api.npmjs.org/downloads/point/last-${time}/${packageName}`
+ https.get(url, response => {
+ let data = ''
+ response.on('data', chunk => { data += chunk })
+ response.on('end', () => {
+ const jsonData = JSON.parse(data)
+ console.log(`Package ${packageName} downloaded ${jsonData.downloads} times.`)
+ })
+ }).on('error', error => { console.error(`Error: ${error.message}`) })
+end
+
+module.exports = { https, getDownloads }
diff --git a/src/error.bs b/src/error.bs
new file mode 100644
index 0000000..8f9498f
--- /dev/null
+++ b/src/error.bs
@@ -0,0 +1,34 @@
+func stackParsing(filename, errorMsg, errorStack, code, type, modulLocation):
+ if code === undefined || code === null:
+ return errorMsg
+ end
+ const stackLines = errorStack.split('\n')
+ const codeLines = code.split('\n')
+ if type === 'SyntaxError':
+ const line = parseInt(stackLines[0].split(':')[1].trim())
+ const errorCode = codeLines[line - 2].trim()
+ return `${type}: ${errorMsg}\n at file ${filename}\n\nSnippet:\n${errorCode}\n`
+ elif type === 'TypeError' || type === 'ReferenceError' || type === 'RangeError' || type === 'URIError' || type === 'EvalError':
+ const parts = stackLines[5].split(':')
+ const line = parseInt(parts[1].trim())
+ let column = parseInt(parts[2].trim())
+ if column > 6:
+ column -= 6
+ end
+ const codeNoTrim = codeLines[line - 1]
+ const errorCode = codeNoTrim.trim()
+ const spaces = codeNoTrim.match(/^ +/)
+ const spacesCount = spaces ? spaces[0].length : 0
+ let offset = column - spacesCount - 1
+ if offset < 0:
+ offset = Math.abs(offset) - 2
+ end
+ const arrow = ' '.repeat(offset) + '^'
+ return `${type}: ${errorMsg}\n at file ${filename}\n at line ${line}, column ${column}\n\nSnippet:\n${errorCode}\n${arrow}`
+ else:
+ const commonErr = errorMsg.replace(/\sstack/g, '').replace(/bst\.js|bsr\.js|bsp\.js/g, filename)
+ return `${type}: ${commonErr}`
+ end
+end
+
+module.exports = { stackParsing }
diff --git a/src/translate.bs b/src/translate.bs
index 1dfc864..20e6090 100644
--- a/src/translate.bs
+++ b/src/translate.bs
@@ -1,6 +1,6 @@
const fs = require('node:fs')
const path = require('node:path')
-const { extensions } = require('../js/bsc.js')
+const { extensions } = require('../js/config.js').loadConfig('./bsc.json', '.bys')
const { compileSource } = require('../scripts/compile.cjs')
func resolveFile(file):
diff --git a/src/watcher.bs b/src/watcher.bs
new file mode 100644
index 0000000..724f580
--- /dev/null
+++ b/src/watcher.bs
@@ -0,0 +1,43 @@
+const chokidar = require('chokidar')
+const fs = require('node:fs')
+const path = require('node:path')
+const { compileSource } = require('../scripts/compile.cjs')
+
+func watching(directory, directory2):
+ const watcher = chokidar.watch(directory, { ignored: /(^|[\/\\])\../, persistent: true })
+ const extensions = ['.bs', '.bys', '.bynixscript', '.mbs']
+ func outputPath(filePath):
+ const extension = path.extname(filePath)
+ if !extensions.includes(extension):
+ return null
+ end
+ return path.join(directory2, path.basename(filePath, extension) + (extension === '.mbs' ? '.mjs' : '.js'))
+ end
+ func compileFile(filePath):
+ const output = outputPath(filePath)
+ if !output:
+ return
+ end
+ handle:
+ const source = fs.readFileSync(filePath, 'utf8')
+ const code = compileSource(source)
+ fs.writeFileSync(output, code)
+ console.log(`File ${output} has been created or changed.`)
+ recovery (error):
+ console.error(`Error compiling ${filePath}:`, error)
+ end
+ end
+ func removeFile(filePath):
+ const output = outputPath(filePath)
+ if output && fs.existsSync(output):
+ fs.unlinkSync(output)
+ console.log(`File ${output} has been deleted.`)
+ end
+ end
+ watcher.on('add', compileFile).on('change', compileFile).on('unlink', removeFile)
+ watcher.on('error', error => console.error('Watcher error:', error))
+ watcher.on('ready', () => console.log('Initial scan complete. Ready for changes'))
+ return watcher
+end
+
+module.exports = { watching }
diff --git a/tests/cli.test.cjs b/tests/cli.test.cjs
index bf46a91..30d6fcd 100644
--- a/tests/cli.test.cjs
+++ b/tests/cli.test.cjs
@@ -31,7 +31,8 @@ test('build produces the packaged executable', () => {
})
test('every CLI source compiles through the build compiler', () => {
- for (const name of ['index', 'bsr', 'bst', 'bsp', 'bsd', 'translate']) {
+ for (const name of ['index', 'bsr', 'bst', 'bsp', 'bsd', 'translate',
+ 'config', 'download', 'error', 'dirProcess', 'watcher']) {
const source = fs.readFileSync(path.join(root, 'src', `${name}.bs`), 'utf8')
const code = compileSource(source)
assert.match(source, /\bfunc \w+\(|\bhandle:/)
@@ -229,3 +230,171 @@ test('conflicting legacy commands fail without changing files', context => {
assert.match(result.stderr, /Specify only one command/)
assert.ok(fs.existsSync(path.join(directory, 'safe.bys')))
})
+
+test('generated modules match their BynixScript sources', () => {
+ for (const name of ['config', 'download', 'error', 'dirProcess', 'watcher']) {
+ const source = fs.readFileSync(path.join(root, 'src', `${name}.bs`), 'utf8')
+ const code = compileSource(source)
+ assert.equal(fs.readFileSync(path.join(root, 'js', `${name}.js`), 'utf8'),
+ code.endsWith('\n') ? code : code + '\n')
+ }
+})
+
+test('configuration wrappers preserve separate defaults and public exports', context => {
+ const directory = fixture(context)
+ const { loadConfig } = require('../js/config.js')
+ const legacy = require('../js/bsconfig.js')
+ const current = require('../js/bsc.js')
+ assert.equal(legacy.extensions.primary, '.bs')
+ assert.equal(current.extensions.primary, '.bys')
+ assert.equal(legacy.defaultConfig.extension.primary, '.bs')
+ assert.equal(current.defaultConfig.extension.primary, '.bys')
+ for (const name of ['defaultConfig', 'config', 'extensions', 'readFolder', 'toFolder',
+ 'watch', 'allowJs', 'strict', 'translate']) {
+ assert.ok(Object.hasOwn(current, name))
+ assert.ok(Object.hasOwn(legacy, name))
+ }
+ const file = path.join(directory, 'custom.json')
+ fs.writeFileSync(file, JSON.stringify({ extension: { secondary: '.custom' }, readFolder: 'input', watch: true }))
+ const loaded = loadConfig(file, '.bys')
+ assert.equal(loaded.extensions.primary, '.bys')
+ assert.equal(loaded.extensions.secondary, '.custom')
+ assert.equal(loaded.readFolder, 'input')
+ assert.equal(loaded.watch, true)
+ assert.equal(loadConfig(path.join(directory, 'missing.json'), '.bs').extensions.primary, '.bs')
+})
+
+test('generated error formatter preserves its public behavior', () => {
+ const { stackParsing } = require('../js/error.js')
+ assert.equal(stackParsing('test.bys', 'missing', '', null, 'Error'), 'missing')
+ assert.equal(stackParsing('test.bys', 'failed in bst.js', '', 'code', 'Error'),
+ 'Error: failed in test.bys')
+ assert.equal(stackParsing('test.bys', 'invalid', 'test.bys:3:1', 'first\nsecond\nthird', 'SyntaxError'),
+ 'SyntaxError: invalid\n at file test.bys\n\nSnippet:\nsecond\n')
+})
+
+test('generated download utility keeps its https export and reports counts', () => {
+ const { EventEmitter } = require('node:events')
+ const { https, getDownloads } = require('../js/download.js')
+ const originalGet = https.get
+ const originalLog = console.log
+ const messages = []
+ try {
+ console.log = message => messages.push(message)
+ https.get = (url, callback) => {
+ assert.equal(url, 'https://api.npmjs.org/downloads/point/last-week/bynixscript')
+ const response = new EventEmitter()
+ callback(response)
+ response.emit('data', '{"downloads":42}')
+ response.emit('end')
+ return new EventEmitter()
+ }
+ getDownloads('bynixscript', 'week')
+ assert.deepEqual(messages, ['Package bynixscript downloaded 42 times.'])
+ } finally {
+ https.get = originalGet
+ console.log = originalLog
+ }
+})
+
+test('directory processor does not run on import and compiles file contents', async context => {
+ const directory = fixture(context)
+ fs.writeFileSync(path.join(directory, 'example.bs'), 'globalThis.bynixDirectoryTest = "print(1)"\n# print("unsafe")\n')
+ const modulePath = path.join(root, 'js', 'dirProcess.js')
+ const importOnly = spawnSync(process.execPath, ['-e', `require(${JSON.stringify(modulePath)})`], {
+ cwd: directory, encoding: 'utf8'
+ })
+ assert.equal(importOnly.status, 0, importOnly.stderr)
+ assert.equal(importOnly.stdout, '')
+ assert.equal(importOnly.stderr, '')
+ const { readAndProcessFilesInDirectory } = require(modulePath)
+ context.after(() => { delete globalThis.bynixDirectoryTest })
+ readAndProcessFilesInDirectory(directory)
+ for (let attempt = 0; attempt < 100 && globalThis.bynixDirectoryTest === undefined; attempt++) {
+ await new Promise(resolve => setTimeout(resolve, 10))
+ }
+ assert.equal(globalThis.bynixDirectoryTest, 'print(1)')
+})
+
+test('watcher compiles with the shared compiler and removes generated output', async context => {
+ const source = fixture(context)
+ const output = fixture(context)
+ const { watching } = require('../js/watcher.js')
+ const watcher = watching(source, output)
+ context.after(() => watcher.close())
+ await new Promise((resolve, reject) => {
+ watcher.once('ready', resolve)
+ watcher.once('error', reject)
+ })
+ const file = path.join(source, 'watched.bys')
+ const generated = path.join(output, 'watched.js')
+ fs.writeFileSync(file, 'print("watch")\n# print("not executed")\n')
+ async function waitUntil(predicate) {
+ for (let attempt = 0; attempt < 100; attempt++) {
+ if (predicate()) return
+ await new Promise(resolve => setTimeout(resolve, 20))
+ }
+ assert.fail('watcher did not update the output')
+ }
+ await waitUntil(() => fs.existsSync(generated))
+ assert.equal(fs.readFileSync(generated, 'utf8'), compileSource(fs.readFileSync(file, 'utf8')))
+ fs.unlinkSync(file)
+ await waitUntil(() => !fs.existsSync(generated))
+})
+
+test('browser bundle runs the shared compiler without changing strings or comments', async () => {
+ const browser = fs.readFileSync(path.join(root, 'dist', 'browser.js'), 'utf8')
+ const scripts = []
+ let onReady
+ let removed = false
+ const tag = {
+ textContent: 'const text = "print(123)"\n# print("unsafe")\nprint(text)',
+ getAttribute: () => null,
+ remove: () => { removed = true }
+ }
+ const document = {
+ createElement: () => ({ classList: { add() {} } }),
+ head: { appendChild() {} },
+ body: { appendChild: script => scripts.push(script.textContent) },
+ addEventListener: (name, callback) => { onReady = callback },
+ querySelectorAll: () => [tag]
+ }
+ runInNewContext(browser, { document, console })
+ onReady()
+ await new Promise(resolve => setImmediate(resolve))
+ assert.equal(removed, true)
+ assert.equal(scripts.length, 1)
+ assert.ok(scripts[0].includes('"print(123)"'))
+ assert.ok(scripts[0].includes('// print("unsafe")'))
+ const printed = []
+ runInNewContext(scripts[0], { console: { log: value => printed.push(value) } })
+ assert.deepEqual(printed, ['print(123)'])
+})
+
+test('browser bundle fetches sources and honors allowJs', async () => {
+ const browser = fs.readFileSync(path.join(root, 'dist', 'browser.js'), 'utf8')
+ const scripts = []
+ let onReady
+ const tag = {
+ getAttribute: name => ({ src: 'remote.bys', allowJs: 'true' })[name] ?? null,
+ remove() {}
+ }
+ const document = {
+ createElement: () => ({ classList: { add() {} } }),
+ head: { appendChild() {} },
+ body: { appendChild: script => scripts.push(script.textContent) },
+ addEventListener: (name, callback) => { onReady = callback },
+ querySelectorAll: () => [tag]
+ }
+ runInNewContext(browser, {
+ document, console,
+ fetch: async file => {
+ assert.equal(file, 'remote.bys')
+ return { ok: true, text: async () => 'const message = "print(1)"\nprint(message)\n' }
+ }
+ })
+ onReady()
+ await new Promise(resolve => setImmediate(resolve))
+ assert.equal(scripts.length, 1)
+ assert.ok(scripts[0].includes('"print(1)"'))
+})