@eagleoutice/flowr-dev
Version:
Static Dataflow Analyzer and Program Slicer for the R Programming Language
105 lines • 5.41 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.interpolationsOf = interpolationsOf;
exports.processStringTemplate = processStringTemplate;
const known_call_handling_1 = require("../known-call-handling");
const retriever_1 = require("../../../../../../r-bridge/retriever");
const decorate_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate");
const r_function_call_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call");
const identifier_1 = require("../../../../../environments/identifier");
const type_1 = require("../../../../../../r-bridge/lang-4.x/ast/model/type");
const unknown_side_effect_1 = require("../../../../../graph/unknown-side-effect");
const built_in_proc_name_1 = require("../../../../../environments/built-in-proc-name");
const built_in_source_1 = require("./built-in-source");
const linker_1 = require("../../../../linker");
/** Arguments naming the scope the template runs against, which we cannot follow. */
const RedirectingParameters = ['.envir', '.con', '.x', '.data'];
/** cli markup: `{.cls text}` styles its content, so the class is no expression but the content may hold some. */
const Markup = /^\.[A-Za-z][\w.-]*[\s]/;
/**
* The R expressions a template interpolates. A doubled delimiter escapes it, delimiters nest, and one inside a
* string is literal, so this walks the template. With `markup`, `{.cls ...}` contributes its content instead.
*/
function interpolationsOf(template, open, close, markup = false) {
const found = [];
let at = 0;
while (at < template.length) {
if (template.startsWith(open + open, at)) {
at += 2 * open.length;
continue;
}
else if (!template.startsWith(open, at)) {
at += 1;
continue;
}
const from = at + open.length;
let depth = 1, quote = undefined, i = from;
for (; i < template.length && depth > 0; i++) {
const c = template[i];
if (quote !== undefined) {
quote = c === '\\' ? (i++, quote) : c === quote ? undefined : quote;
}
else if (c === '"' || c === '\'' || c === '`') {
quote = c;
}
else if (template.startsWith(open, i)) {
depth++;
}
else if (template.startsWith(close, i)) {
depth--;
}
}
if (depth === 0) {
const content = template.slice(from, i - close.length);
if (markup && Markup.test(content)) {
found.push(...interpolationsOf(content, open, close, markup));
}
else {
found.push(content);
}
}
at = i;
}
return found;
}
/** The argument given under `name`, `undefined` if the call does not name it. */
function namedArgument(args, name) {
return args.find((a) => a !== r_function_call_1.EmptyArgument && a.name !== undefined
&& identifier_1.Identifier.getName(a.name.content) === name);
}
/** The string a named argument spells out, `undefined` unless it is a literal. */
function literalOf(args, name) {
const value = namedArgument(args, name)?.value;
return value?.type === type_1.RType.String ? value.content.str : undefined;
}
/**
* Processes a call whose string arguments are templates carrying R code, as `glue` and `cli` use them.
* The code runs where the call is, reading and writing like anything written there, so that is how it is
* analyzed. A template pointed at another scope is marked unknown instead.
*/
function processStringTemplate(name, args, rootId, data, config) {
const { information } = (0, known_call_handling_1.processKnownFunctionCall)({ name, args, rootId, data, origin: built_in_proc_name_1.BuiltInProcName.StringTemplate });
/* these are only ever given by name, and matching them positionally would swallow the template itself */
if (RedirectingParameters.some(p => namedArgument(args, p) !== undefined)) {
(0, unknown_side_effect_1.handleUnknownSideEffect)(information.graph, information.environment, rootId);
return information;
}
const open = literalOf(args, '.open') ?? config.open ?? '{';
const close = literalOf(args, '.close') ?? config.close ?? '}';
const results = [];
for (const arg of args) {
if (arg === r_function_call_1.EmptyArgument || arg.name !== undefined || arg.value?.type !== type_1.RType.String) {
continue;
}
const template = arg.value;
for (const code of interpolationsOf(template.content.str, open, close, config.markup)) {
const generator = (0, decorate_1.sourcedDeterministicCountingIdGenerator)(`${name.lexeme}::${template.info.id}`, template.location);
const result = (0, built_in_source_1.sourceRequest)(rootId, (0, retriever_1.requestFromInput)(code), data, information, false, generator, true);
/* the template stands where the call does, so what it leaves open resolves against the bindings here */
(0, linker_1.linkInputs)([...result.in, ...result.unknownReferences], data.environment, [], result.graph, false);
results.push(result);
}
}
return (0, built_in_source_1.mergeSourced)(information, results);
}
//# sourceMappingURL=built-in-string-template.js.map