UNPKG

bat2js

Version:
263 lines (251 loc) 9.91 kB
#!/usr/bin/env node "use strict"; // -- START HEADER -- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.tokenType = void 0; exports.tokenize = tokenize; exports.tokens_to_javascript = tokens_to_javascript; exports.isPercentVariable = isPercentVariable; // Copyright (c) benja2998. You may redistribute under the terms of the GPL-3.0-or-later. // bat2js is a Batch to JavaScript compiler. // -- END HEADER -- // -- START IMPORTS -- const fs = __importStar(require("fs")); const path = __importStar(require("path")); const { randomUUID } = require('crypto'); class tokenType { } exports.tokenType = tokenType; tokenType.COMMENT = 'COMMENT'; tokenType.COMMAND = 'COMMAND'; tokenType.STRING = 'STRING'; tokenType.VARIABLE = 'VARIABLE'; tokenType.NUMBER = 'NUMBER'; tokenType.SETVAR = 'SETVAR'; // Define a function to tokenize the file contents function tokenize(contents) { // Parse the contents into an array of lines let lines = contents.split('\n'); let tokens = []; // Iterate over each word for (let i = 0; i < lines.length; i++) { let line = lines[i]; let words = line.split(' '); if (line.startsWith('@')) { continue; } if (line === '') { continue; } // Check if \r comes at the end of a word or is the only word if (words[words.length - 1].endsWith('\r')) { // Remove the \r words[words.length - 1] = words[words.length - 1].slice(0, -1); } // Do the same but for \n if (words[words.length - 1].endsWith('\n')) { // Remove the \n words[words.length - 1] = words[words.length - 1].slice(0, -1); } // Remove empty words words = words.filter(word => word !== ''); // Now that we've cleaned up, let's tokenize for (let j = 0; j < words.length; j++) { let word = words[j]; let restOfLine = line.slice(line.indexOf(word) + word.length).trim(); // Check if the word is a comment by checking if it's rem or :: if (word.startsWith('rem') || word.startsWith('::')) { // Get the comment by looking at the rest of the line let comment = line.slice(line.indexOf(word) + word.length).trim(); // Add the comment to the tokens tokens.push({ type: tokenType.COMMENT, value: comment, argument: '' // No argument for a comment }); continue; } else { // Check if the word is a command if (word === 'echo') { tokens.push({ type: tokenType.COMMAND, value: word, argument: restOfLine }); } else if (word === 'set') { // Get the rest of the line for the variable name and value let variableNameValue = restOfLine.trim(); // Remove quotes from the variable name and value variableNameValue = variableNameValue.replace(/['"]/g, ''); // Split the variable name and value by the first = let variableName = variableNameValue.split('=')[0]; let variableValue = variableNameValue.split('=')[1]; // Check if the variableValue is a string if (isNaN(Number(variableValue))) { // It's a string, so add quotes variableValue = `"${variableValue}"`; } // Add the variable to the tokens tokens.push({ type: tokenType.SETVAR, value: variableName, argument: variableValue }); } else if (word === 'pause') { let redirectTo = ''; // Get the next word let nextWord = words[j + 1]; // Check if the next word contains > if (nextWord !== undefined && nextWord.includes('>')) { // Redirection // Split nextWord by > and get the second part redirectTo = nextWord.split('>')[1]; // Check if redirectTo is empty if (redirectTo === '') { // Set redirectTo to the rest of the line redirectTo = line.slice(line.indexOf(nextWord) + nextWord.length).trim(); } } // Push the pause command to the tokens tokens.push({ type: tokenType.COMMAND, value: 'pause', argument: redirectTo }); } continue; } } } return tokens; } function tokens_to_javascript(tokens, filePath) { // This is the complex part let javascript = `#!/usr/bin/env node // -- START HEADER -- // DO NOT MODIFY. // This file is automatically generated by bat2js. // To modify the contents, edit the original .bat file and run bat2js again. // Compiled from: ${filePath} // -- END HEADER -- const fs = require('fs'); const path = require('path'); const { spawn } = require('child_process'); const readline = require('readline'); // -- START FUNCTIONS -- // Make a waitForKeypress function function waitForKeypress(message) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); return new Promise((resolve) => { rl.question(message, () => { rl.close(); resolve(); }); }); } // -- END FUNCTIONS -- // -- START COMPILED CODE -- `; for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; if (token.type === tokenType.COMMENT) { javascript += `// ${token.value}\n`; } if (token.type === tokenType.COMMAND) { if (token.value === 'echo') { javascript += `console.log(\`${token.argument}\`);\n`; } if (token.value === 'pause') { // Check argument if (token.argument !== 'nul') { // Check if argument is a file if (token.argument !== undefined) { // Append "Press Enter to continue..." to the end of the file javascript += `waitForKeypress('');\n`; javascript += `fs.appendFileSync('${token.argument}', 'Press Enter to continue...');\n`; } else { javascript += `waitForKeypress('Press Enter to continue...');\n`; } } else { javascript += `waitForKeypress('');\n`; } } } if (token.type === tokenType.SETVAR) { // Check if javascript already contains "let ${token.value} = ${token.argument};" if (javascript.includes(`let ${token.value} = ${token.argument};`)) { javascript += `${token.value} = ${token.argument};\n`; } else { javascript += `let ${token.value} = ${token.argument};\n`; } } } javascript = javascript.replace(/%([^%]+)%/g, (_match, p1) => `\${${p1}}`); return javascript; } function isPercentVariable(str) { return /^%.*%$/.test(str); } if (require.main === module) { let filePath = process.argv[2]; if (!filePath) { console.error('Usagee: bat2js <file>'); ; process.exit(1); } if (!fs.existsSync(filePath)) { console.error(`File '${filePath}' does not exist.`); process.exit(1); } let fileContents = fs.readFileSync(filePath, 'utf8'); console.time('Compilation Time'); let tokens = tokenize(fileContents); let javascript = tokens_to_javascript(tokens, filePath); console.timeEnd('Compilation Time'); // Write the compiled JavaScript to a file const fileName = path.parse(filePath).name; const compiledFilePath = path.join(path.dirname(filePath), `${fileName}.js`); fs.writeFileSync(compiledFilePath, javascript); }