UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

105 lines 5.41 kB
"use strict"; 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