@eagleoutice/flowr
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
193 lines • 8.74 kB
JavaScript
;
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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.replCompleter = replCompleter;
exports.makeDefaultReplReadline = makeDefaultReplReadline;
exports.replProcessAnswer = replProcessAnswer;
exports.repl = repl;
exports.loadReplHistory = loadReplHistory;
/**
* Basically a helper file to allow the main 'flowr' script (located in the source root) to provide its repl
*
* @module
*/
const prompt_1 = require("./prompt");
const readline = __importStar(require("readline"));
const repl_execute_1 = require("./commands/repl-execute");
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const args_1 = require("../../util/text/args");
const repl_commands_1 = require("./commands/repl-commands");
const scripts_info_1 = require("../common/scripts-info");
const retriever_1 = require("../../r-bridge/retriever");
const repl_main_1 = require("./commands/repl-main");
const shell_1 = require("../../r-bridge/shell");
const log_1 = require("../../util/log");
let _replCompleterKeywords = undefined;
function replCompleterKeywords() {
if (_replCompleterKeywords === undefined) {
_replCompleterKeywords = Array.from((0, repl_commands_1.getCommandNames)(), s => `:${s}`);
}
return _replCompleterKeywords;
}
const defaultHistoryFile = path_1.default.join(os_1.default.tmpdir(), '.flowrhistory');
/**
* Used by the repl to provide automatic completions for a given (partial) input line
*/
function replCompleter(line) {
const splitLine = (0, args_1.splitAtEscapeSensitive)(line);
// did we just type a space (and are starting a new arg right now)?
const startingNewArg = line.endsWith(' ');
// if we typed a command fully already, autocomplete the arguments
if (splitLine.length > 1 || startingNewArg) {
const commandNameColon = replCompleterKeywords().find(k => splitLine[0] === k);
if (commandNameColon) {
const completions = [];
const commandName = commandNameColon.slice(1);
if ((0, repl_commands_1.getCommand)(commandName)?.script === true) {
// autocomplete script arguments
const options = scripts_info_1.scripts[commandName].options;
completions.push(...(0, scripts_info_1.getValidOptionsForCompletion)(options, splitLine).map(o => `${o} `));
}
else {
// autocomplete command arguments (specifically, autocomplete the file:// protocol)
completions.push(retriever_1.fileProtocol);
}
// add an empty option so that it doesn't autocomplete the only defined option immediately
completions.push(' ');
const currentArg = startingNewArg ? '' : splitLine[splitLine.length - 1];
return [completions.filter(a => a.startsWith(currentArg)), currentArg];
}
}
// if no command is already typed, just return all commands that match
return [replCompleterKeywords().filter(k => k.startsWith(line)).map(k => `${k} `), line];
}
function makeDefaultReplReadline() {
return {
input: process.stdin,
output: process.stdout,
tabSize: 4,
terminal: true,
history: loadReplHistory(defaultHistoryFile),
removeHistoryDuplicates: true,
completer: replCompleter
};
}
;
async function replProcessStatement(output, statement, parser, allowRSessionAccess) {
if (statement.startsWith(':')) {
const command = statement.slice(1).split(' ')[0].toLowerCase();
const processor = (0, repl_commands_1.getCommand)(command);
const bold = (s) => output.formatter.format(s, { style: 1 /* FontStyles.Bold */ });
if (processor) {
try {
await processor.fn(output, parser, statement.slice(command.length + 2).trim(), allowRSessionAccess);
}
catch (e) {
output.stdout(`${bold(`Failed to execute command ${command}`)}: ${e?.message}. Using the ${bold('--verbose')} flag on startup may provide additional information.\n`);
if (log_1.log.settings.minLevel < 6 /* LogLevel.Fatal */) {
console.error(e);
}
}
}
else {
output.stdout(`the command '${command}' is unknown, try ${bold(':help')} for more information\n`);
}
}
else {
await (0, repl_execute_1.tryExecuteRShellCommand)(output, parser, statement, allowRSessionAccess);
}
}
/**
* This function interprets the given `expr` as a REPL command (see {@link repl} for more on the semantics).
*
* @param output - Defines two methods that every function in the repl uses to output its data.
* @param expr - The expression to process.
* @param parser - The {@link RShell} or {@link TreeSitterExecutor} to use (see {@link repl}).
* @param allowRSessionAccess - If true, allows the execution of arbitrary R code.
*/
async function replProcessAnswer(output, expr, parser, allowRSessionAccess) {
const statements = (0, args_1.splitAtEscapeSensitive)(expr, false, ';');
for (const statement of statements) {
await replProcessStatement(output, statement, parser, allowRSessionAccess);
}
}
/**
* Provides a never-ending repl (read-evaluate-print loop) processor that can be used to interact with a {@link RShell} as well as all flowR scripts.
*
* The repl allows for two kinds of inputs:
* - Starting with a colon `:`, indicating a command (probe `:help`, and refer to {@link commands}) </li>
* - Starting with anything else, indicating default R code to be directly executed. If you kill the underlying shell, that is on you! </li>
*
* @param options - The options for the repl. See {@link FlowrReplOptions} for more information.
*
* For the execution, this function makes use of {@link replProcessAnswer}.
*
*/
async function repl({ parser = new shell_1.RShell({ revive: 2 /* RShellReviveOptions.Always */ }), rl = readline.createInterface(makeDefaultReplReadline()), output = repl_main_1.standardReplOutput, historyFile = defaultHistoryFile, allowRSessionAccess = false }) {
if (historyFile) {
rl.on('history', h => fs_1.default.writeFileSync(historyFile, h.join('\n'), { encoding: 'utf-8' }));
}
// the incredible repl :D, we kill it with ':quit'
while (true) {
await new Promise((resolve, reject) => {
rl.question((0, prompt_1.prompt)(), answer => {
rl.pause();
replProcessAnswer(output, answer, parser, allowRSessionAccess).then(() => {
rl.resume();
resolve();
}).catch(reject);
});
});
}
}
function loadReplHistory(historyFile) {
try {
if (!fs_1.default.existsSync(historyFile)) {
return undefined;
}
return fs_1.default.readFileSync(historyFile, { encoding: 'utf-8' }).split('\n');
}
catch (e) {
log_1.log.error(`Failed to load repl history from ${historyFile}: ${e?.message}`);
log_1.log.error(e?.stack);
return undefined;
}
}
//# sourceMappingURL=core.js.map