bat2js
Version:
Batch to JavaScript compiler
263 lines (251 loc) • 9.91 kB
JavaScript
;
// -- 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);
}