UNPKG

@eagleoutice/flowr-dev

Version:

Static Dataflow Analyzer and Program Slicer for the R Programming Language

123 lines 6.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PasteLikeCalls = exports.StringFns = void 0; exports.foldStringCall = foldStringCall; exports.resolveAsStringFn = resolveAsStringFn; const type_1 = require("../../../r-bridge/lang-4.x/ast/model/type"); const identifier_1 = require("../../environments/identifier"); const r_value_1 = require("../values/r-value"); const string_constants_1 = require("../values/string/string-constants"); const interval_constants_1 = require("../values/intervals/interval-constants"); const match_arguments_1 = require("./match-arguments"); const resolve_helper_1 = require("../../environments/resolve-helper"); /** everything after the last separator, with trailing separators dropped first (`a/b/` is `b`, `/` is the empty string) */ function basename(path) { const trimmed = path.replace(/\/+$/, ''); return trimmed.slice(trimmed.lastIndexOf('/') + 1); } /** everything before the last separator, `.` if there is none and `/` if only the root remains */ function dirname(path) { if (path === '') { return ''; } const trimmed = path.length > 1 ? path.replace(/(?<=.)\/+$/, '') : path; const cut = trimmed.lastIndexOf('/'); if (cut < 0) { return '.'; } const head = trimmed.slice(0, cut).replace(/\/+$/, ''); return head === '' ? '/' : head; } /** * Every string built-in the value solver folds, the joining ones included: `paste` is an entry like `toupper` * is, and both are reached through {@link resolveAsStringFn}. Teaching flowR one more is a line here plus the * matching `evalHandler` in the built-in configuration -- a test checks that the two agree. * * An entry with a `...` parameter joins what it collected, which is why its separator is just another * parameter with a default. The rest take a fixed number of arguments under the names R documents. */ exports.StringFns = { /* the joining calls, which differ only in their separator and what the argument overriding it is called */ paste: { pkg: "base" /* PkgName.Base */, params: ['...', 'sep'], defaults: { sep: ' ' }, ignored: ['collapse'], fold: (parts, sep) => parts.join(sep) }, paste0: { pkg: "base" /* PkgName.Base */, params: ['...', 'sep'], defaults: { sep: '' }, ignored: ['collapse'], fold: (parts, sep) => parts.join(sep) }, 'file.path': { pkg: "base" /* PkgName.Base */, params: ['...', 'fsep'], defaults: { fsep: '/' }, fold: (parts, fsep) => parts.join(fsep) }, /* the project root stays implicit, so the fold yields the path below it */ here: { pkg: "here" /* PkgName.Here */, params: ['...'], fold: (parts) => parts.length > 0 ? parts.join('/') : '.' }, /* the path splits, which only ever treat `/` as a separator, so a Windows path keeps its non-`\` parts */ basename: { pkg: "base" /* PkgName.Base */, params: ['path'], fold: basename }, dirname: { pkg: "base" /* PkgName.Base */, params: ['path'], fold: dirname }, /* whole-string transformations */ toupper: { pkg: "base" /* PkgName.Base */, params: ['x'], fold: (s) => s.toUpperCase() }, tolower: { pkg: "base" /* PkgName.Base */, params: ['x'], fold: (s) => s.toLowerCase() }, trimws: { pkg: "base" /* PkgName.Base */, params: ['x'], fold: (s) => s.trim() }, /** R counts characters, so we count code points rather than UTF-16 units */ nchar: { pkg: "base" /* PkgName.Base */, params: ['x'], fold: (s) => [...s].length } }; /** the entries that join what they are handed, the ones a name at construction time may be built from */ exports.PasteLikeCalls = new Set(Object.entries(exports.StringFns).filter(([, fn]) => fn.params.includes('...')).map(([name]) => name)); /** * Folds a named {@link StringFns} call to its result, resolving each argument with `resolveArg`. `undefined` * when the name is not one of them, an argument does not resolve, or the call does not match what the entry * declares. Shared by the value solver ({@link resolveAsStringFn}) and construction-time name resolution. */ function foldStringCall(node, resolveArg) { const known = exports.StringFns[identifier_1.Identifier.getName(node.functionName.content)]; if (known === undefined) { return undefined; } /* `dplyr::paste` is not `base::paste`; a bare call has already been resolved */ const ns = identifier_1.Identifier.getNamespace(node.functionName.content); if (ns !== undefined && ns !== known.pkg) { return undefined; } const matched = (0, match_arguments_1.matchCallArguments)(node, known.params, known.ignored); if (matched === undefined) { return undefined; } /* a fold that reads the characters cannot run on source text with an escape still in it (`\t` is two chars * there); joining does not read them, so the entries collecting a `...` are exempt */ const literal = !known.params.includes('...'); const args = []; for (const [at, slot] of matched.entries()) { if (Array.isArray(slot)) { const parts = slot.map(resolveArg); if (parts.includes(undefined)) { return undefined; } args.push(parts); continue; } if (slot === undefined) { const fallback = known.defaults?.[known.params[at]]; if (fallback === undefined) { break; // the parameters R gives a default are the trailing ones, so the fold sees a shorter prefix } args.push(fallback); continue; } // the argument *was* given, so failing to resolve it means we do not know the result, defaults do not apply const value = resolveArg(slot); if (value === undefined || (literal && value.includes('\\'))) { return undefined; } args.push(value); } return args.length > 0 ? known.fold(...args) : undefined; } /** * Resolves any call of a {@link StringFns} entry to a {@link Value}, with its arguments in any order R accepts: * a join like `paste0("cfg_", k)` when every part resolves to a single string constant, and a transformation * like `basename(p)` when its argument does. Anything that does not resolve stays `Top`. */ function resolveAsStringFn(args) { const node = args.node; if (node.type !== type_1.RType.FunctionCall || !node.named) { return r_value_1.Top; } const folded = foldStringCall(node, arg => resolve_helper_1.Resolve.toSingleString(arg.info.id, args)); if (folded === undefined) { return r_value_1.Top; } return typeof folded === 'number' ? (0, interval_constants_1.intervalFrom)(folded, folded) : (0, string_constants_1.stringFrom)(folded); } //# sourceMappingURL=resolve-strings.js.map