diff --git a/license-clarifications.json b/license-clarifications.json new file mode 100644 index 00000000000..866213dc8b1 --- /dev/null +++ b/license-clarifications.json @@ -0,0 +1,5 @@ +{ + "bzip2@0.1.1": { + "licenses": "LGPL-2.0" + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index fc1750b206d..ab0fda8bb5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@davisvaughan/tree-sitter-r": "^1.2.0", "@jupyterlab/nbformat": "^4.5.4", "@xmldom/xmldom": "^0.9.7", + "bzip2": "^0.1.1", "clipboardy": "^4.0.0", "command-line-args": "^6.0.1", "command-line-usage": "^7.0.3", @@ -20,6 +21,7 @@ "gray-matter": "^4.0.3", "joi": "^18.0.1", "lz-string": "^1.5.0", + "lzma1": "^0.3.0", "n-readlines": "^3.4.1", "n3": "^1.26.0", "object-hash": "^3.0.0", @@ -47,6 +49,8 @@ "@types/command-line-usage": "^5.0.4", "@types/commonmark": "^0.27.10", "@types/dagre": "^0.7.53", + "@types/lzma-native": "^4.0.4", + "@types/n-readlines": "^1.0.6", "@types/n3": "^1.26.0", "@types/object-hash": "^3.0.6", "@types/object-path": "^0.11.4", @@ -2707,6 +2711,26 @@ "license": "MIT", "peer": true }, + "node_modules/@types/lzma-native": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/lzma-native/-/lzma-native-4.0.4.tgz", + "integrity": "sha512-9nwec86WAT3wUhjx9iV0AQ06xyDyiN/D9CAk3ZzNLb8zFjjo4EDBliN2uo7CFcBDJ64oXfX4sa+p6fpGpzy/4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/n-readlines": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/n-readlines/-/n-readlines-1.0.6.tgz", + "integrity": "sha512-0kCJ/bVdHl0yxNYbiGTInj8BPqVLMiUp4gn4zfyYJgKn0iQKiJ+mNGCJj2rFLupOrZ5y/W5l13YhOZ8jYG9DIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/n3": { "version": "1.26.0", "resolved": "https://registry.npmjs.org/@types/n3/-/n3-1.26.0.tgz", @@ -4150,6 +4174,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bzip2": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/bzip2/-/bzip2-0.1.1.tgz", + "integrity": "sha512-wMvOIQ5jX3ikcCxWO1HjYVOAB+sjKzMTYLQmFPi4d6GBF01cYpnIwQ4RaDX4F3QSJeiB6gFqt5hh9fbebCSspw==" + }, "node_modules/c12": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/c12/-/c12-3.3.3.tgz", @@ -8556,6 +8585,19 @@ "lz-string": "bin/bin.js" } }, + "node_modules/lzma1": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/lzma1/-/lzma1-0.3.0.tgz", + "integrity": "sha512-ETg5CT6Ojybzjx2YG48NuPcN7kGzhLO6FfKbkvUwt0bxIEOq+wXykCZBoNBrkGq66zwJT1ZdUI71F0wC6pP9TQ==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/xseman" + } + }, "node_modules/macos-release": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-3.4.0.tgz", diff --git a/package.json b/package.json index 0fb7302c8fe..29909715bc8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "build:copy-wasm": "mkdir -p dist/node_modules/@davisvaughan/tree-sitter-r/ && mkdir -p dist/node_modules/web-tree-sitter && cp node_modules/@davisvaughan/tree-sitter-r/tree-sitter-r.wasm dist/node_modules/@davisvaughan/tree-sitter-r/ && cp node_modules/web-tree-sitter/tree-sitter.wasm dist/node_modules/web-tree-sitter/", "lint-local": "npx eslint --version && npx eslint src/ test/ --rule \"no-warning-comments: off\"", "lint": "npm run license-compat -- --summary && npx eslint --version && npx eslint src/ test/", - "license-compat": "license-checker-rseidelsohn --onlyAllow 'MIT;MPL-2.0;MIT OR X11;GPLv2;LGPL;GNUGPL;ISC;Apache-2.0;FreeBSD;BSD-2-Clause;clearbsd;ModifiedBSD;BSD-3-Clause;Python-2.0;Unlicense;WTFPL;BlueOak-1.0.0;CC-BY-4.0;CC-BY-3.0;CC0-1.0;0BSD'", + "license-compat": "license-checker-rseidelsohn --onlyAllow 'MIT;MPL-2.0;MIT OR X11;GPLv2;LGPL;GNUGPL;ISC;Apache-2.0;FreeBSD;BSD-2-Clause;clearbsd;ModifiedBSD;BSD-3-Clause;Python-2.0;Unlicense;WTFPL;BlueOak-1.0.0;CC-BY-4.0;CC-BY-3.0;CC0-1.0;0BSD' --clarificationsFile license-clarifications.json", "doc": "typedoc", "test": "vitest --exclude \"test/system-tests/**\" --config test/vitest.config.mts", "test:verbose": "FLOWR_VERBOSE=true vitest --exclude \"test/system-tests/**\" --config test/vitest.config.mts", @@ -177,6 +177,8 @@ "@types/command-line-usage": "^5.0.4", "@types/commonmark": "^0.27.10", "@types/dagre": "^0.7.53", + "@types/lzma-native": "^4.0.4", + "@types/n-readlines": "^1.0.6", "@types/n3": "^1.26.0", "@types/object-hash": "^3.0.6", "@types/object-path": "^0.11.4", @@ -203,6 +205,7 @@ "@davisvaughan/tree-sitter-r": "^1.2.0", "@jupyterlab/nbformat": "^4.5.4", "@xmldom/xmldom": "^0.9.7", + "bzip2": "^0.1.1", "clipboardy": "^4.0.0", "command-line-args": "^6.0.1", "command-line-usage": "^7.0.3", @@ -211,6 +214,7 @@ "gray-matter": "^4.0.3", "joi": "^18.0.1", "lz-string": "^1.5.0", + "lzma1": "^0.3.0", "n-readlines": "^3.4.1", "n3": "^1.26.0", "object-hash": "^3.0.0", diff --git a/src/config.ts b/src/config.ts index beee328b239..1ec9a120106 100644 --- a/src/config.ts +++ b/src/config.ts @@ -111,6 +111,10 @@ export interface FlowrConfig extends MergeableRecord { * Whether source calls should be ignored, causing {@link processSourceCall}'s behavior to be skipped */ readonly ignoreSourceCalls: boolean + /** + * Whether load calls should be ignored, causing {@link processLoadCall}'s behavior to be skipped + */ + readonly ignoreLoadCalls: boolean /** Configure language semantics and how flowR handles them */ readonly semantics: { /** Semantics regarding the handling of the environment */ @@ -296,6 +300,7 @@ export const FlowrConfig = { default(this: void): FlowrConfig { return { ignoreSourceCalls: false, + ignoreLoadCalls: false, semantics: { environment: { overwriteBuiltIns: { @@ -358,6 +363,7 @@ export const FlowrConfig = { */ Schema: Joi.object({ ignoreSourceCalls: Joi.boolean().optional().description('Whether source calls should be ignored, causing {@link processSourceCall}\'s behavior to be skipped.'), + ignoreLoadCalls: Joi.boolean().optional().description('Whether load calls should be ignored, causing {@link processLoadCall}\'s behavior to be skipped.'), semantics: Joi.object({ environment: Joi.object({ overwriteBuiltIns: Joi.object({ diff --git a/src/control-flow/semantic-cfg-guided-visitor.ts b/src/control-flow/semantic-cfg-guided-visitor.ts index 4207bd8f09b..e59acddaafd 100644 --- a/src/control-flow/semantic-cfg-guided-visitor.ts +++ b/src/control-flow/semantic-cfg-guided-visitor.ts @@ -302,6 +302,8 @@ export class SemanticCfgGuidedVisitor< case BuiltInProcName.Function: case BuiltInProcName.FunctionDefinition: return this.onDefaultFunctionCall({ call }); + case BuiltInProcName.Load: + return this.onLoadCall({ call }); default: assertUnreachable(origin); } @@ -727,4 +729,6 @@ export class SemanticCfgGuidedVisitor< } return { target: undefined, source: undefined }; } + + private onLoadCall(_param: { call: DataflowGraphVertexFunctionCall }) {} } diff --git a/src/dataflow/environments/built-in-proc-name.ts b/src/dataflow/environments/built-in-proc-name.ts index 48f5fe82cd9..36b7a98d843 100644 --- a/src/dataflow/environments/built-in-proc-name.ts +++ b/src/dataflow/environments/built-in-proc-name.ts @@ -36,6 +36,8 @@ export enum BuiltInProcName { Library = 'builtin:lib', /** for `list` calls, see {@link processList} */ List = 'builtin:list', + /** for 'load' calls see {@link processLoadCall} */ + Load = 'builtin:load', /** for `local` calls, see {@link processLocal} */ Local = 'builtin:local', /** for `::` and `:::` called as a function, see {@link processNamespaceAccess} */ diff --git a/src/dataflow/environments/built-in.ts b/src/dataflow/environments/built-in.ts index 4fb726e383a..fc5fce2e673 100644 --- a/src/dataflow/environments/built-in.ts +++ b/src/dataflow/environments/built-in.ts @@ -68,6 +68,7 @@ import { processNewEnv } from '../internal/process/functions/call/built-in/built import { processAttach } from '../internal/process/functions/call/built-in/built-in-attach'; import { processWithEnv } from '../internal/process/functions/call/built-in/built-in-with'; import { processNamespaceAccess } from '../internal/process/functions/call/built-in/built-in-namespace-access'; +import { processLoadCall } from '../internal/process/functions/call/built-in/built-in-load'; export type BuiltInIdentifierProcessor = ( name: RSymbol, @@ -219,6 +220,7 @@ export const BuiltInProcessorMapper = { [BuiltInProcName.IfThenElse]: processIfThenElse, [BuiltInProcName.Library]: processLibrary, [BuiltInProcName.List]: processList, + [BuiltInProcName.Load]: processLoadCall, [BuiltInProcName.Local]: processLocal, [BuiltInProcName.NamespaceAccess]: processNamespaceAccess, [BuiltInProcName.Pipe]: processPipe, diff --git a/src/dataflow/environments/default-builtin-config.ts b/src/dataflow/environments/default-builtin-config.ts index da74ccdfb37..e74496df070 100644 --- a/src/dataflow/environments/default-builtin-config.ts +++ b/src/dataflow/environments/default-builtin-config.ts @@ -573,7 +573,7 @@ export const DefaultBuiltinConfig = [ Identifier.from(['topenv', PkgName.Base]), ], processor: BuiltInProcName.Default, config: {}, assumePrimitive: true }, { type: 'function', names: [Identifier.from(['load', PkgName.Base]), 'load_image'], - processor: BuiltInProcName.Default, config: { hasUnknownSideEffects: true }, assumePrimitive: false }, + processor: BuiltInProcName.Load, config: {}, assumePrimitive: false }, /* attach injects an environment's contents into the search path; detach reverses it (treated as unknown side effect) */ { type: 'function', names: [Identifier.from(['attach', PkgName.Base])], processor: BuiltInProcName.Attach, config: {}, assumePrimitive: false }, diff --git a/src/dataflow/internal/process/functions/call/built-in/built-in-load.ts b/src/dataflow/internal/process/functions/call/built-in/built-in-load.ts new file mode 100644 index 00000000000..88dbc15fdb4 --- /dev/null +++ b/src/dataflow/internal/process/functions/call/built-in/built-in-load.ts @@ -0,0 +1,288 @@ +import type { RSymbol } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-symbol'; +import type { ParentInformation } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/decorate'; +import type { NodeId } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/node-id'; +import type { DataflowProcessorInformation } from '../../../../../processor'; +import type { ControlDependency, DataflowInformation } from '../../../../../info'; +import { processKnownFunctionCall } from '../known-call-handling'; +import type { InGraphReferenceType } from '../../../../../environments/identifier'; +import { ReferenceType } from '../../../../../environments/identifier'; +import type { RObjectData } from '../../../../../../project/plugins/file-plugins/files/flowr-rda-file'; +import { RDAParser, SexpType } from '../../../../../../project/plugins/file-plugins/files/flowr-rda-file'; +import { RType } from '../../../../../../r-bridge/lang-4.x/ast/model/type'; +import { removeRQuotes } from '../../../../../../r-bridge/retriever'; +import { findSource } from './built-in-source'; +import { FlowrTextFile } from '../../../../../../project/context/flowr-file'; +import { VertexType } from '../../../../../graph/vertex'; +import { RoleInParent } from '../../../../../../r-bridge/lang-4.x/ast/model/processing/role'; +import { expensiveTrace } from '../../../../../../util/log'; +import { dataflowLogger } from '../../../../../logger'; +import { handleUnknownSideEffect } from '../../../../../graph/unknown-side-effect'; +import { EdgeType } from '../../../../../graph/edge'; +import { unpackArg } from '../argument/unpack-argument'; +import { valueSetGuard } from '../../../../../eval/values/general'; +import { resolveIdToValue } from '../../../../../eval/resolve/alias-tracking'; +import { isValue } from '../../../../../eval/values/r-value'; +import { isNotUndefined } from '../../../../../../util/assert'; +import type { REnvironmentInformation } from '../../../../../environments/environment'; +import { SourceRange } from '../../../../../../util/range'; +import type { RExpressionList } from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-expression-list'; +import type { DataflowFunctionFlowInformation } from '../../../../../graph/graph'; +import { BuiltInProcName } from '../../../../../environments/built-in-proc-name'; +import type { + PotentiallyEmptyRArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { + EmptyArgument +} from '../../../../../../r-bridge/lang-4.x/ast/model/nodes/r-function-call'; +import { bindArgs, resolveArgToEnvir } from './built-in-envir-utils'; + +/** + * Processes a built-in 'load' function call by retrieving the names of the variables loaded by the given file. + * Example: `load(test.rda)` with two variables 'x' and 'y'. processLoadCall adds 'x' and 'y' to the dataflow graph and + * adds control dependencies between the variables and the loaded file. + */ +export function processLoadCall( + name: RSymbol, + args: readonly PotentiallyEmptyRArgument[], + rootId: NodeId, + data: DataflowProcessorInformation, +): DataflowInformation { + const { fileArg, envirArg } = getArguments(args); + + if(!fileArg) { + const fn = processKnownFunctionCall({ name, args, rootId, data, origin: 'default' }); + handleUnknownSideEffect(fn.information.graph, fn.information.environment, rootId); + return fn.information; + } + + const fn = processKnownFunctionCall({ name, args, rootId, data, origin: BuiltInProcName.Load }); + + if(data.ctx.config.ignoreLoadCalls) { + expensiveTrace(dataflowLogger, () => `Skipping load call ${JSON.stringify(fileArg)} (disabled in config file)`); + handleUnknownSideEffect(fn.information.graph, fn.information.environment, rootId); + return fn.information; + } + + const envirResolution = envirArg ? resolveArgToEnvir(envirArg, data) : undefined; + if(envirResolution) { + fn.information.graph.addEdge(rootId, envirResolution.envirNodeId, EdgeType.Reads); + } + + + let sourceFile: string[] | undefined; + + if(fileArg.type === RType.String) { + sourceFile = [removeRQuotes(fileArg.lexeme)]; + } else { + const resolved = valueSetGuard(resolveIdToValue(fileArg.info.id, { environment: envirResolution ? envirResolution.envirData.environment : data.environment, idMap: data.completeAst.idMap, resolve: data.ctx.config.solver.variables, ctx: data.ctx })); + sourceFile = resolved?.elements.map(r => r.type === 'string' && isValue(r.value) ? r.value.str : undefined).filter(isNotUndefined); + } + + if(sourceFile) { + for(const candidate of sourceFile) { + const path = removeRQuotes(candidate); + let filepath = path ? findSource(data.ctx.config.solver.resolveSource, path, data) : path; + + if(Array.isArray(filepath)) { + if(filepath.length > 1) { + dataflowLogger.warn(`Found multiple candidate files for load(${JSON.stringify(path)}): ${JSON.stringify(filepath)}, using first match`); + } + filepath = filepath.find(isNotUndefined); + } + + if(filepath === undefined) { + continue; + } + + const variables = new RDAParser(new FlowrTextFile(filepath), true).parseRDA(); + if(!variables) { + continue; + } + + let envir = envirResolution ? envirResolution.envirData.environment : fn.information.environment; + + const loadLocation = name.location ?? name.fullRange ?? SourceRange.invalid(); + const loadCds = [...(data.cds ?? []), { id: rootId, when: true, file: filepath }]; + + for(const variable of variables) { + if(variable.name) { + envir = defineLoadedVariable({ ...variable, name: variable.name }, rootId, fn, envir, loadLocation, loadCds, data); + } + } + + return { ...fn.information, environment: envir }; + } + } + + handleUnknownSideEffect(fn.information.graph, fn.information.environment, rootId); + return fn.information; +} + +function defineLoadedVariable( + variable: RObjectData & { name: string }, + rootId: NodeId, + fn: { information: DataflowInformation }, + envir: REnvironmentInformation, + loadLocation: SourceRange, + loadCds: ControlDependency[], + data: DataflowProcessorInformation, +): REnvironmentInformation { + const syntheticId = `${rootId}:loaded:${variable.name}`; + const isClosure = variable.type === SexpType.CloSxp; + const rootInfo = data.completeAst.idMap.get(rootId)?.info; + + data.completeAst.idMap.set(syntheticId, { + type: RType.Symbol, + content: variable.name, + lexeme: variable.name, + location: loadLocation, + namespace: undefined, + info: { + ...rootInfo, + id: syntheticId, + parent: rootId, + role: RoleInParent.ExpressionListChild, + } as OtherInfo & ParentInformation + }); + + if(isClosure) { + defineLoadedClosure(syntheticId, variable, fn, envir, loadLocation, loadCds, data, rootInfo); + } else { + fn.information.graph.addVertex({ + tag: VertexType.VariableDefinition, + id: syntheticId, + cds: loadCds, + }, envir); + } + + const nodeToDefine = { + nodeId: syntheticId, + name: variable.name, + type: sexpTypeToReferenceType(variable.type) as InGraphReferenceType, + definedAt: rootId, + cds: loadCds, + }; + const newCurrent = envir.current.define(nodeToDefine); + return { ...envir, current: newCurrent }; +} + +function defineLoadedClosure( + syntheticId: string, + variable: RObjectData & { name: string }, + fn: { information: DataflowInformation }, + envir: REnvironmentInformation, + loadLocation: SourceRange, + loadCds: ControlDependency[], + data: DataflowProcessorInformation, + rootInfo: (OtherInfo & ParentInformation) | undefined, +): void { + const fdefId = `${syntheticId}:fdef`; + const cleanEnv = data.ctx.env.makeCleanEnv(); + const bodyId = `${fdefId}:body`; + + const body: RExpressionList = { + type: RType.ExpressionList, + lexeme: undefined, + grouping: undefined, + children: [], + location: loadLocation, + info: { + ...rootInfo, + id: bodyId, + parent: fdefId, + role: RoleInParent.FunctionDefinitionBody, + } as OtherInfo & ParentInformation + }; + + data.completeAst.idMap.set(bodyId, body); + + data.completeAst.idMap.set(fdefId, { + type: RType.FunctionDefinition, + parameters: [], + lexeme: variable.name, + location: loadLocation, + body, + info: { + ...rootInfo, + id: fdefId, + parent: syntheticId, + role: RoleInParent.ExpressionListChild, + } as OtherInfo & ParentInformation + }); + + const flow: DataflowFunctionFlowInformation = { + entryPoint: fdefId, + graph: new Set(), + out: [], + in: [], + unknownReferences: [], + hooks: [], + environment: cleanEnv + }; + + fn.information.graph.addVertex({ + tag: VertexType.FunctionDefinition, + id: fdefId, + cds: loadCds, + environment: cleanEnv, + subflow: flow, + exitPoints: [], + params: {}, + }, cleanEnv); + + fn.information.graph.addVertex({ + tag: VertexType.VariableDefinition, + id: syntheticId, + cds: loadCds, + }, envir); + + fn.information.graph.addEdge(syntheticId, fdefId, EdgeType.DefinedBy); +} + +function sexpTypeToReferenceType(type?: SexpType): ReferenceType{ + if(type === undefined){ + return ReferenceType.Unknown; + } + switch(type) { + case SexpType.NilSxp: + return ReferenceType.Unknown; + case SexpType.SymSxp: + case SexpType.CharSxp: + case SexpType.LglSxp: + case SexpType.IntSxp: + case SexpType.RealSxp: + case SexpType.CplxSxp: + case SexpType.StrSxp: + case SexpType.RawSxp: + case SexpType.ListSxp: + case SexpType.EnvSxp: + case SexpType.PromSxp: + case SexpType.LangSxp: + case SexpType.DotSxp: + case SexpType.VecSxp: + case SexpType.ExprSxp: + case SexpType.ObjSxp: + return ReferenceType.Variable; + case SexpType.CloSxp: + return ReferenceType.Function; + case SexpType.SpecialSxp: + case SexpType.BuiltInSxp: + return ReferenceType.BuiltInFunction; + default: + return ReferenceType.Unknown; + } +} + +function getArguments(args: readonly PotentiallyEmptyRArgument[]) { + const loadParams = ['file', 'envir', 'verbose'] as const; + const bound = bindArgs(args, loadParams); + + const fileArgBound = bound.get('file'); + const envirArg = bound.get('envir'); + const verboseArgBound = bound.get('verbose'); + + const fileArg = fileArgBound && fileArgBound !== EmptyArgument ? unpackArg(fileArgBound) : undefined; + const verboseArg = verboseArgBound && verboseArgBound !== EmptyArgument ? unpackArg(verboseArgBound) : undefined; + + return { fileArg, envirArg, verboseArg }; +} \ No newline at end of file diff --git a/src/documentation/wiki-interface.ts b/src/documentation/wiki-interface.ts index 1fe8445b903..c8f55e7f00c 100644 --- a/src/documentation/wiki-interface.ts +++ b/src/documentation/wiki-interface.ts @@ -233,8 +233,9 @@ ${ctx.linkO(FlowrConfig, 'amend')}. The following summarizes the configuration options: - \`ignoreSourceCalls\`: If set to \`true\`, _flowR_ will ignore source calls when analyzing the code, i.e., ignoring the inclusion of other files. -- \`semantics\`: allows to configure the way _flowR_ handles R, although we currently only support \`semantics/environment/overwriteBuiltIns\`. - You may use this to overwrite _flowR_'s handling of built-in function and even completely clear the preset definitions shipped with flowR. +- \`ignoreLoadCalls\`: If set to \`true\`, _flowR_ will ignore load calls when analyzing the code, i.e., ignoring the loading of r-data files. +- \`semantics\`: allows to configure the way _flowR_ handles R, although we currently only support \`semantics/environment/overwriteBuiltIns\`. + You may use this to overwrite _flowR_'s handling of built-in function and even completely clear the preset definitions shipped with flowR. See [Configure BuiltIn Semantics](#configure-builtin-semantics) for more information. - \`solver\`: allows to configure how _flowR_ resolves variables and their values (currently we support: ${Object.values(VariableResolve).map(v => `\`${v}\``).join(', ')}), as well as if pointer analysis should be active. - \`engines\`: allows to configure the engines used by _flowR_ to interact with R code. See the ${ctx.linkPage('wiki/Engines', 'Engines wiki page')} for more information. @@ -252,6 +253,7 @@ So you can configure _flowR_ by adding a file like the following: ${codeBlock('json', JSON.stringify( { ignoreSourceCalls: true, + ignoreLoadCalls: true, semantics: { environment: { overwriteBuiltIns: { diff --git a/src/project/plugins/file-plugins/files/flowr-rda-file.ts b/src/project/plugins/file-plugins/files/flowr-rda-file.ts new file mode 100644 index 00000000000..0a317cf77be --- /dev/null +++ b/src/project/plugins/file-plugins/files/flowr-rda-file.ts @@ -0,0 +1,2521 @@ +import type { FileRole, FlowrFileProvider } from '../../../context/flowr-file'; +import { FlowrFile } from '../../../context/flowr-file'; +import fs from 'node:fs'; +// @ts-expect-error no type information provided for package bzip2 +import * as bzip2 from 'bzip2'; +import * as zlib from 'node:zlib'; +import { RFunTabOffsets } from './r-fun-tab'; +import { RShellExecutor } from '../../../../r-bridge/shell-executor'; +import { decompress } from 'lzma1'; + +/** + * This decorates a text file and provides access to its content in the format of an {@link RObject}. + */ +export class FlowrRDAFile extends FlowrFile { + private readonly wrapped: FlowrFileProvider; + private readonly shortcut: boolean; + + /** + * Prefer the static {@link FlowrRDAFile.from} method to create instances of this class as it will not re-create if already a description file + * and handle role assignments. + * @param file - The underlying file provider whose path points to an RDA file. + * @param shortcut - When `true`, only top-level object names and types are + * collected during parsing. Payload data is skipped. + */ + constructor(file: FlowrFileProvider, shortcut: boolean = true) { + super(file.path(), file.roles); + this.wrapped = file; + this.shortcut = shortcut ?? false; + } + + /** + * Loads and parses the content of the wrapped file as an RDA structure. + * @see {@link parseRDA} for details on the parsing logic. + * @returns An array of top-level {@link RObject}s or `[{}]` when the + * file contains no R objects. + */ + protected loadContent(): RObject[] { + return new RDAParser(this.wrapped, this.shortcut).parseRDA() ?? [{}]; + } + + /** + * RDA file lifter, this does not re-create if already an RDA file. + * @param file - A raw {@link FlowrFileProvider} or an existing {@link FlowrRDAFile}. + * @param role - Optional {@link FileRole} to assign before returning. + * @returns The (possibly newly created) {@link FlowrRDAFile}. + */ + public static from(file: FlowrFileProvider | FlowrRDAFile, role?: FileRole): FlowrRDAFile { + if(role) { + file.assignRole(role); + } + return file instanceof FlowrRDAFile ? file : new FlowrRDAFile(file); + } +} + +/** + * Compression algorithm wrapping an RDA file. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/connections.c#L2673 | R source: comp_type} + */ +export enum CompressionType { + CompGz = 'COMP_GZ', + CompBz = 'COMP_BZ', + CompXz = 'COMP_XZ', + CompLzma = 'COMP_LZMA', + CompZstd = 'COMP_ZSTD', + CompUnknownOrNo = 'COMP_UNKNOWN_OR_NO', +} + +/** + * RDA file serialization format and version. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/saveload.c#L61-L72 | R source: saveload.c} + */ +export enum SerializationTypeTag { + MagicEmpty = 'R_MAGIC_EMPTY', + MagicCorrupt = 'R_MAGIC_CORRUPT', + MagicAsciiV1 = 'R_MAGIC_ASCII_V1', + MagicBinaryV1 = 'R_MAGIC_BINARY_V1', + MagicXdrV1 = 'R_MAGIC_XDR_V1', + MagicAsciiV2 = 'R_MAGIC_ASCII_V2', + MagicBinaryV2 = 'R_MAGIC_BINARY_V2', + MagicXdrV2 = 'R_MAGIC_XDR_V2', + MagicAsciiV3 = 'R_MAGIC_ASCII_V3', + MagicBinaryV3 = 'R_MAGIC_BINARY_V3', + MagicXdrV3 = 'R_MAGIC_XDR_V3', + MagicMaybeTooNew = 'R_MAGIC_MAYBE_TOONEW', +} + +type SerializationTypes = SerializationTypeTag | number; + +type RObject = RValues.NilValue | RObjectData; + +type Real = number | RValues.NilValue | RValues.NaReal | RValues.NaN | RValues.PosInf | RValues.NegInf; +type Complex = { r: Real, i: Real }; + +/** + * Structured representation of a deserialized R SEXP node. + * + * Not all fields are populated on every instance. If a field is populated depends on the {@link SexpType}. + */ +export interface RObjectData { + name?: string; + type?: SexpType, + levels?: number, + object?: boolean, + hasAttribute?: boolean, + attributes?: RObjectData[], + hasTag?: boolean, + tag?: RObject, + value?: RObject | RObject[] | RValues | number | (number | RValues | Complex | Real)[] | string | (string | RValues)[] | null[], + frame?: RObject, + _isObject?: boolean, + _isLocked?: boolean, + car?: RObject, + cdr?: RObject, + enClos?: RObject, + address?: object | null, + protected?: RObject, + cloEnv?: RValues; + prEnv?: RValues; + key?: unknown; + finalizer?: unknown; + next?: unknown; + gp?: number; + hashTab?: unknown; + offset?: number; + altRep?: boolean; +} + +/** + * Numeric identifiers for R SEXP types. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/include/Rinternals.h#L111-L144 | R source: SEXP} + * @see {@link http://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L681-L711 | R source: SEXP} + */ +export enum SexpType { + NilSxp = 0, + SymSxp = 1, + ListSxp = 2, + CloSxp = 3, + EnvSxp = 4, + PromSxp = 5, + LangSxp = 6, + SpecialSxp = 7, + BuiltInSxp = 8, + CharSxp = 9, + LglSxp = 10, + IntSxp = 13, + RealSxp = 14, + CplxSxp = 15, + StrSxp = 16, + DotSxp = 17, + AnySxp = 18, + VecSxp = 19, + ExprSxp = 20, + BcodesSxp = 21, + ExtPtrSxp = 22, + WeakRefSxp = 23, + RawSxp = 24, + ObjSxp = 25, + NewSxp = 30, + FreeSxp = 31, + FunSxp = 99, + RefSxp = 255, + NilValueSxp = 254, + GlobalEnvSxp = 253, + UnboundValueSxp = 252, + MissingArgSxp = 251, + BaseNamespaceSxp = 250, + NamespaceSxp = 249, + PackageSxp = 248, + PersistSxp = 247, + ClassRefSxp = 246, + GenericRefSxp = 245, + BcRepDef = 244, + BcRepRef = 243, + EmptyEnvSxp = 242, + BaseEnvSxp = 241, + AltLangSxp = 240, + AttrListSxp = 239, + AltRepSxp = 238, +} + +/** + * Special values used internally by R serialization. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/include/Rinternals.h#L401-L471 | R source: RValues} + */ +enum RValues { + NilValue = 'NIL', + EmptyEnv = 'EMPTY_ENV', + BaseEnv = 'BASE_ENV', + GlobalEnv = 'GLOBAL_ENV', + UnboundValue = 'UNBOUND_VALUE', + MissingArg = 'MISSING_ARG', + BaseNamespace = 'BASE_NAMESPACE', + ClassSymbol = 'CLASS', + NaString = 'NA_character_', + NaInteger = 'NA_integer_', + NaReal = 'NA_real_', + NaComplex = 'NA_complex_', + NaN = 'NaN', + PosInf = 'Pos_Inf', + NegInf = 'Neg_inf', +} + +export enum SerializationFormat { + Xdr = 'XDR', + Ascii = 'ASCII', + Binary = 'BINARY', +} + +/** + * Parser for RDA files. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c | R source: serialize.c} + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/saveload.c | R source: saveload.c} + */ +export class RDAParser{ + private readonly file: FlowrFileProvider; + private readonly shortcut: boolean; + private buffer!: Buffer; + private currentDepth: number = 0; + private static readonly INITIAL_DEPTH: number = 1; + private lastName: string | undefined = undefined; + private setLastName = false; + private offset = 0; + private static readonly R_CODE_SET_MAX = 2 ** 6 - 1; + private RWeakRefs: null | RObjectData = null; + private static readonly CHUNK_SIZE = 8906; + private static readonly SIZE_OF_DOUBLE = 2 ** 3; + private static readonly WORD_SIZE = 2 ** 7; + private static readonly MAX_VECTOR_LENGTH = 2 ** 16; + private format!: SerializationFormat; + private readonly refTable: RObject[] = []; + private Registry: RObjectData | null = null; + + + private opinfo = { + addr: null, + argc: null, + instName: null + }; + + constructor(file: FlowrFileProvider, shortcut: boolean = true) { + this.file = file; + this.shortcut = shortcut ?? true; + } + + /** + * Parses an RDA file. + * + * The file is decompressed, deserialized and converted into a flattened + * object representation. + * @param file - RDA file provider. + * @param shortcut - When `true`, only names and types are collected. + * @returns List of found {@link RObjectData} or `null` if the file is empty. + */ + parseRDA(): RObjectData[] | null { + const fileContent = fs.readFileSync(this.file.path()); + const compressionType = this.detectCompression(fileContent); + this.buffer = this.decompress(fileContent, compressionType); + const result = this.deserialize2(); + if(result === RValues.NilValue) { + return null; + } else { + return this.flattenRObject(result, this.shortcut); + } + } + + /** + * Detects the compression algorithm used for an RDA file. + * @param buf - Raw file buffer. + * @param with_zlib - Whether zlib headers should also be considered. + * @returns Detected {@link CompressionType}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/connections.c#L2675-L2710 | R source: comp_type_from_memory} + */ + detectCompression(buf: Buffer, with_zlib: boolean = false): CompressionType { + if(buf.length >= 2 && buf[0] == 0x1f && buf[1] == 0x8b) { + return CompressionType.CompGz; + } + if(with_zlib && buf.length>=2 && buf[0] == 0x78 && buf[1] == 0x9c){ + return CompressionType.CompGz; + } + if(buf.length >= 10 && buf[0] === 0x42 && buf[1] === 0x5a && buf[2] === 0x68) { + if(buf[3] >= 0x31 && buf[3] <= 0x39) { + const magic1 = [0x31, 0x41, 0x59, 0x26, 0x53, 0x59]; + const magic2 = [0x17, 0x72, 0x45, 0x38, 0x50, 0x90]; + const isMagic1 = magic1.every((v, i) => buf[4 + i] === v); + const isMagic2 = magic2.every((v, i) => buf[4 + i] === v); + + if(isMagic1 || isMagic2) { + return CompressionType.CompBz; + } + } + } + + if(buf.length >=4){ + if(buf.length >= 4 && buf[0] == 0x89 && buf[1] === 0x4c && buf[2] === 0x5a && buf[3] === 0x4f) { + throw new Error('this is a lzop-compressed file which this build of R does not support'); + } else if(buf.length >= 4 && buf[0] === 0x28 && buf[1] === 0xB5 && buf[2] === 0x2F && buf[3] === 0xFD) { + return CompressionType.CompZstd; + } + } + + if(buf.length >= 5) { + if(buf[0] === 0xFD && buf[1] === 0x37 && buf[2] === 0x7a && buf[3] === 0x58 && buf[4] === 0x5a) { + return CompressionType.CompXz; + } else if(buf[0] === 0xFF && buf[1] === 0x4C && buf[2] === 0x5A && buf[3] === 0x4D && buf[4] === 0x41) { + return CompressionType.CompLzma; + } else if(buf[0] === 0x5D && buf[1] === 0x00 && buf[2] === 0x00 && buf[3] === 0x80 && buf[4] === 0x00) { + return CompressionType.CompLzma; + } + } + + return CompressionType.CompUnknownOrNo; + } + + /** + * Decompresses the given RDA file buffer. + * @param fileContent - Raw compressed file buffer. + * @param compressionType - {@link CompressionType} as returned by {@link detectCompression}. + * @returns Decompressed buffer. + * @throws Error for unsupported compression types. + */ + decompress(fileContent: Buffer, compressionType: CompressionType): Buffer { + let buffer: Buffer; + + switch(compressionType) { + case CompressionType.CompGz: { + try { + buffer = zlib.gunzipSync(fileContent); + } catch{ + buffer = zlib.inflateSync(fileContent); + } + break; + } + + case CompressionType.CompBz: { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-member-access + const decompressed = bzip2.simple(bzip2.array(fileContent)); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + buffer = Buffer.from(decompressed); + break; + } + + case CompressionType.CompXz: + case CompressionType.CompLzma: { + buffer = Buffer.from(decompress(fileContent)); + break; + } + + case CompressionType.CompZstd: { + throw new Error(compressionType + 'not supported yet.'); + } + + case CompressionType.CompUnknownOrNo: + buffer = fileContent; + break; + default: + throw new Error('Unknown or unsupported compression type.'); + } + + return buffer; + } + + /** + * Detects the serialization type used for the RDA-file. + * @param buf - Buffer with decompressed RDA-file content + * @returns The identified {@link SerializationTypes} of decompressed RDA-file + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/saveload.c#L1808-L1858 | R source: R_ReadMagic} + */ + determineSerializationType(buf: Buffer): SerializationTypes { + if(buf.length < 5) { + if(buf.length === 0) { + return SerializationTypeTag.MagicEmpty; + } else { + return SerializationTypeTag.MagicCorrupt; + } + } + + const magic = buf.toString('ascii', 0, 5); + switch(magic) { + case 'RDA1\n': + return SerializationTypeTag.MagicAsciiV1; + case 'RDB1\n': + return SerializationTypeTag.MagicBinaryV1; + case 'RDX1\n': + return SerializationTypeTag.MagicXdrV1; + case 'RDA2\n': + return SerializationTypeTag.MagicAsciiV2; + case 'RDB2\n': + return SerializationTypeTag.MagicBinaryV2; + case 'RDX2\n': + return SerializationTypeTag.MagicXdrV2; + case 'RDA3\n': + return SerializationTypeTag.MagicAsciiV3; + case 'RDB3\n': + return SerializationTypeTag.MagicBinaryV3; + case 'RDX3\n': + return SerializationTypeTag.MagicXdrV3; + } + + if(magic.startsWith('RD')) { + return SerializationTypeTag.MagicMaybeTooNew; + } + + return Number(buf.toString('ascii', 0, 4)); + } + + /** + * Deserializes a decompressed RDA-file. + * @returns Deserialized RDA-file as {@link RObject} or {@link RValues.NilValue}, if the deserialization fails + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/saveload.c#L1923-L1972 | R source: R_LoadFromFile} + */ + deserialize2(): RObject{ + this.offset = 0; + const serializationType = this.determineSerializationType(this.buffer); + this.offset += 5; + + if( + serializationType === undefined || + serializationType === 'R_MAGIC_CORRUPT' || + serializationType === 'R_MAGIC_EMPTY' || + serializationType === 'R_MAGIC_MAYBE_TOONEW' + ) { + throw new Error('Could not determine serialization type'); + } + + if( + serializationType === 'R_MAGIC_ASCII_V2' || + serializationType === 'R_MAGIC_ASCII_V3' || + serializationType === 'R_MAGIC_XDR_V2' || + serializationType === 'R_MAGIC_XDR_V3' || + serializationType === 'R_MAGIC_BINARY_V2' || + serializationType === 'R_MAGIC_BINARY_V3' + ) { + const result = this.deserialize(); + this.currentDepth--; + return result; + } + if( + serializationType === 'R_MAGIC_ASCII_V1' || + serializationType === 'R_MAGIC_XDR_V1' || + serializationType === 'R_MAGIC_BINARY_V1' + ){ + // https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/saveload.c#L2157-L2196 + console.warn('Version one rda files are not supported yet'); + } + return RValues.NilValue; + } + + /** + * Deserializes a decompressed RDA-file. + * @returns Deserialized RDA-file as {@link RObject} + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2237-L2292 | R source: R_Unserialize} + */ + deserialize(): RObject { + + switch(String.fromCodePoint(this.buffer[this.offset])) { + case 'A': this.format = SerializationFormat.Ascii; break; + case 'B': this.format = SerializationFormat.Binary; break; + case 'X': this.format = SerializationFormat.Xdr; break; + case '\n': + if(String.fromCodePoint(this.buffer[this.offset + 1]) === 'A') { + this.format = SerializationFormat.Ascii; + this.offset += 1; + } + break; + default: + throw new Error('unknown input format'); + } + + this.offset += 2; + + const version = this.assertInteger(this.inInteger()); + const writerVersion = this.assertInteger(this.inInteger()); + const minReaderVersion = this.assertInteger(this.inInteger()); + + switch(version) { + case 2: break; + case 3: + { + const neLen = this.assertInteger(this.inInteger()); + if(neLen > RDAParser.R_CODE_SET_MAX || neLen < 0) { + throw new Error('invalid length of encoding name'); + } + const _nativeEncoding = this.inString(neLen); + break; + } + default: + { + const [vw, pw, sw] = this.decodeVersion(writerVersion); + if(minReaderVersion < 0) { + throw new Error(`cannot read unreleased workspace version ${version} written by experimental R ${vw}.${pw}.${sw}`); + } else { + const [vm, pm, sm] = this.decodeVersion(minReaderVersion); + throw new Error(`cannot read unreleased workspace version ${version} written by experimental R ${vw}.${pw}.${sw}; need R ${vm}.${pm}.${sm} or newer`); + } + } + } + + return this.readItem(); + } + + /** + * Reads a serialized integer value from the current buffer position. + * @returns Parsed integer value or {@link RValues.NaInteger}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L396-L420 | R source: inInteger} + */ + inInteger(): number | RValues.NaInteger { + switch(this.format) { + case SerializationFormat.Ascii: { + const word = this.inWord(128); + if(word === 'NA') { + return RValues.NaInteger; + } + const i = Number.parseInt(word, 10); + if(Number.isNaN(i)) { + throw new TypeError(`${word} (or ${i}) is not a number`); + } + return i; + } + case SerializationFormat.Binary: { + const i = this.buffer.readInt32LE(this.offset); + this.offset += 4; + return i; + } + case SerializationFormat.Xdr:{ + const i = this.buffer.readInt32BE(this.offset); + this.offset += 4; + return i; + } + default: + return RValues.NaInteger; + } + } + + /** + * Advances the buffer offset past a serialized integer value. + * + * Mirrors {@link inInteger}. + */ + skipInteger(): void { + if(this.format === SerializationFormat.Ascii) { + this.skipWord(); + } else if(this.format === SerializationFormat.Binary || this.format === SerializationFormat.Xdr) { + this.offset += 4; + } else { + return; + } + } + + /** + * Ensures that the given value is not {@link RValues.NaInteger}. + * @param value - Integer value to be tested. + * @returns The validated integer value. + * @throws Error if the value equals `RValues.NaInteger`. + */ + assertInteger(value: number | RValues.NaInteger): number { + if(value === RValues.NaInteger) { + throw new Error('Unexpected NA integer'); + } + return value; + } + + /** + * Ensures that the given object is not {@link RValues.NilValue}. + * @param obj - R object to validate. + * @returns The validated {@link RObjectData}. + * @throws Error if the object equals {@link RValues.NilValue}. + */ + assertRObjectData(obj: RObject): RObjectData { + if(obj === RValues.NilValue) { + throw new Error('Unexpected NilValue'); + } + return obj; + } + + /** + * Reads an ASCII word from the input buffer. + * @param size - Maximum allowed word size. + * @returns The parsed word. + * @throws Error if EOF is reached or the word exceeds the allowed size. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L378-L394 | R source: inWord} + */ + inWord(size: number): string { + let i = 0; + let c; + const word = new Array(size); + + do{ + c = this.inChar(); + if(c === -1){ + throw new Error('Read character is -1.'); + } + } while(this.isSpace(c)); + + while(!this.isSpace(c) && i < size) { + word[i++] = String.fromCodePoint(c); + c = this.inChar(); + } + if(i >= size) { + throw new Error(`$\{i} >= ${size} when reading word.`); + } + + return word.join(''); + } + + /** + * Skips an ASCII word in the input buffer. + * + * Mirrors {@link inWord} + * @returns An empty string. + * @throws Error if EOF is reached or the word exceeds {@link RDAParser.WORD_SIZE}. + */ + skipWord(): string { + let c; + let i = 0; + + do{ + c = this.inChar(); + if(c === -1){ + throw new Error('Read character is -1.'); + } + } while(this.isSpace(c)); + + while(!this.isSpace(c) && i < RDAParser.WORD_SIZE) { + i++; + c = this.inChar(); + } + if(i >= RDAParser.WORD_SIZE) { + throw new Error(`$\{i} >= ${RDAParser.WORD_SIZE} when reading word.`); + } + + return ''; + } + + /** + * Reads the next character from the input buffer. + * @returns The next character or `-1` on EOF. + */ + inChar(): number { + if(this.offset >= this.buffer.length) { + return -1; + } + + const char = this.buffer[this.offset]; + this.offset++; + return char; + } + + /** + * Checks whether the given byte represents a whitespace character. + * @param c - Character code to check. + * @returns `true` if the character is whitespace. + */ + isSpace(c: number): boolean { + return c >= 9 && c <= 13 || c === 32; + } + + /** + * Reads a serialized string from the buffer. + * @param len - Length of the serialized string. + * @returns Decoded string. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L505-L550 | R source: inString} + */ + inString(len: number): string { + if(this.format === SerializationFormat.Ascii) { + if(len > 0){ + const result = []; + + while(this.offset < this.buffer.length) { + const c = this.buffer[this.offset++]; + if(!this.isSpace(c)) { + break; + } + } + + this.offset--; + + for(let i = 0; i < len; i++) { + let c = String.fromCodePoint(this.buffer[this.offset++]); + if(c === '\\'){ + c = String.fromCodePoint(this.buffer[this.offset++]); + switch(c){ + case 'n': result.push('\n'); break; + case 't': result.push('\t'); break; + case 'v': result.push('\v'); break; + case 'b': result.push('\b'); break; + case 'r': result.push('\r'); break; + case 'f': result.push('\f'); break; + case 'a': result.push('\x07'); break; // \a + case '\\': result.push('\\'); break; + case '?': result.push('?'); break; + case '\'': result.push('\''); break; + case '"': result.push('"'); break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': { + let d = 0; + let j = 0; + while('0' <= c && c < '8' && j < 3) { + d = d * 8 + (Number.parseInt(c)); + c = String.fromCodePoint(this.buffer[this.offset++]); + j++; + } + result.push(String.fromCodePoint(d)); + this.offset--; + break; + } + default: + result.push(c); + } + } else { + result.push(c); + } + } + return result.join(''); + } + return ''; + } else { + const bytes = this.buffer.subarray(this.offset, this.offset + len); + this.offset += len; + return bytes.toString('latin1'); + } + } + + /** + * Skips a serialized string depending on the current serialization format. + * + * Mirrors {@link inString} + * @param len - Length of the serialized string. + */ + skipString(len: number): void { + if(this.format === SerializationFormat.Ascii) { + if(len > 0){ + while(this.offset < this.buffer.length) { + const c = this.buffer[this.offset++]; + if(!this.isSpace(c)) { + break; + } + } + + this.offset--; + + for(let i = 0; i < len; i++) { + let c = String.fromCodePoint(this.buffer[this.offset++]); + if(c === '\\'){ + c = String.fromCodePoint(this.buffer[this.offset++]); + switch(c){ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': { + let j = 0; + while('0' <= c && c < '8' && j < 3) { + c = String.fromCodePoint(this.buffer[this.offset++]); + j++; + } + this.offset--; + break; + } + } + } + } + } + } else { + this.offset += len; + } + } + + /** + * Decodes an R writer version integer into v, p, and s. + * @param writerVersion - Encoded writer version. + * @returns Tuple containing version components `[v,p,s]`. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2230-L2235 | R source: decodeVersion} + */ + decodeVersion(writerVersion: number): number[] { + const v = writerVersion / RDAParser.MAX_VECTOR_LENGTH; + writerVersion = writerVersion % RDAParser.MAX_VECTOR_LENGTH; + const p = writerVersion / 2 ** 8; + writerVersion = writerVersion % 2 ** 8; + const s = writerVersion; + + return [v, p, s]; + } + + /** + * Reads the next flags and dispatches to {@link readItemRecursive}. + * This is the main recursive entry point called for every R object encountered + * during deserialization. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2117-L2121 | R source: ReadItem} + */ + readItem(): RObject { + const flags = this.assertInteger(this.inInteger()); + return this.readItemRecursive(flags); + } + + /** + * Resolves a namespace reference by name using `getNamespace()` in R. + * Simpler variant of {@link R_FindNamespace1}. + * @param info - The {@link RObjectData} holding the namespace/package info. + * @returns An {@link RObjectData} of type {@link SexpType.EnvSxp} whose + * `value` is set to the resolved environment. Currently only + * `R_GlobalEnv` is handled; other results are logged as warnings. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/envir.c#L3795-L3804 | R source: R_FindNamespace} + */ + R_FindNamespace(info: RObjectData): RObjectData { + const namespaceName = (info.value as RObjectData).name as string; + + const code = `getNamespace("${namespaceName}")`; + const shell = new RShellExecutor(); + const result = shell.run(code); + shell.close(); + + const val: RObjectData = {}; + val.type = SexpType.EnvSxp; + + if(result === '') { + val.value = RValues.GlobalEnv; + } + + return val; + } + + /** + * Resolves a serialized namespace reference by executing R at runtime. + * @param info - The {@link RObjectData} holding the namespace/package info. + * @returns An {@link RObjectData} of type {@link SexpType.EnvSxp} whose + * `value` is set to the resolved environment. Currently only + * `R_GlobalEnv` is handled; other results are logged as warnings. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1785-L1796 | R source: R_FindNamespace1} + */ + R_FindNamespace1(info: RObjectData): RObjectData { + const where: RObjectData = {}; + where.type = SexpType.CharSxp; + where.value = this.lastName; + const code = `..getNamespace("${(info.value as RObjectData[])[0].name as string}", "${where.value as string}")`; + const shell = new RShellExecutor(); + const result = shell.run(code); + shell.close(); + const val: RObjectData = {}; + val.type = SexpType.EnvSxp; + if(result == '') { + val.value = RValues.GlobalEnv; + } + return val; + } + + /** + * Deserializes a single SEXP node recursively. + * + * The method dispatches to the corresponding deserialization logic + * depending on the encoded {@link SexpType}. + * @param flags - Serialized SEXP flags word. + * @returns Deserialized {@link RObjectData}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1871-L2115 | R source: ReadItem_Recursive} + */ + readItemRecursive(flags: number): RObjectData { + const [type, levels, object, hasAttribute, _hasTag] = this.unpackFlags(flags); + + let s: RObjectData = {}; + + switch(type) { + case SexpType.NilValueSxp: + case SexpType.EmptyEnvSxp: + case SexpType.BaseEnvSxp: + case SexpType.GlobalEnvSxp: + case SexpType.UnboundValueSxp: + case SexpType.MissingArgSxp: + case SexpType.BaseNamespaceSxp: + s.value = RValues.BaseNamespace; + s.type = SexpType.EnvSxp; + return s; + case SexpType.RefSxp: return this.getReadRef(this.inRefIndex(flags)); + case SexpType.PersistSxp: { + s = this.inStringVec(); + this.addReadRef(s); + return s; + } + case SexpType.AltRepSxp: + { + this.currentDepth++; + console.warn('AltReps are not supported yet!'); + const info = this.readItem() as RObjectData; + const state = this.readItem() as RObjectData; + const attr = this.readItem() as RObjectData; + s.type = (((info.cdr as RObjectData).cdr as RObjectData).car as RObjectData).type as SexpType; + s = this.AltRepUnserializeEx(info, state, attr, object, levels); + this.currentDepth--; + return s; + } + case SexpType.SymSxp: { + this.currentDepth++; + s = this.assertRObjectData(this.readItem()); + this.currentDepth--; + s.type = SexpType.SymSxp; + this.addReadRef(s); + return s; + } + case SexpType.PackageSxp: + { + s = this.inStringVec(); + s = this.rFindPackageEnv(s); + this.addReadRef(s); + return s; + } + case SexpType.NamespaceSxp: + s = this.inStringVec(); + s = this.R_FindNamespace1(s); + this.addReadRef(s); + return s; + case SexpType.EnvSxp: + { + const locked = this.inInteger(); + s.type = SexpType.EnvSxp; + this.addReadRef(s); + + this.currentDepth++; + this.SetEnClos(s, this.assertRObjectData(this.readItem())); + s.frame = this.readItem(); + s.hashTab = this.readItem(); + s.attributes = this.assertRObjectData(this.readItem()).attributes; + + this.currentDepth--; + + if(s.attributes?.some(e => e.name === RValues.ClassSymbol)){ + s._isObject = true; + } + // R_RestoreHashCount(s); + if(locked) { + s._isLocked = false; + } + if(!s.enClos || s.enClos === RValues.NilValue) { + this.SetEnClos(s, { + value: RValues.BaseEnv, + type: SexpType.EnvSxp, + enClos: RValues.NilValue + } as RObjectData); + } + return s; + } + case SexpType.ListSxp: + case SexpType.LangSxp: + case SexpType.CloSxp: + case SexpType.PromSxp: + case SexpType.DotSxp: + return this.readItemIterative(flags); + default: + switch(type) { + case SexpType.ExtPtrSxp: { + s.type = type; + this.addReadRef(s); + s.address = null; + this.currentDepth++; + s.protected = this.readItem(); + s.tag = this.readItem(); + this.currentDepth--; + break; + } + case SexpType.WeakRefSxp: + s.value = this.R_MakeWeakRef(RValues.NilValue, RValues.NilValue, RValues.NilValue, false); + this.addReadRef(s); + break; + case SexpType.SpecialSxp: + case SexpType.BuiltInSxp: + { + const len = this.assertInteger(this.inInteger()); + if(len < 0) { + throw new Error('invalid length'); + } + const name = this.inString(len); + const index = (RFunTabOffsets as Record)[name] as number; + if(name in RFunTabOffsets) { + s = this.mkPrimSxp(index, SexpType.BuiltInSxp); + } else { + s.value = RValues.NilValue; + throw new Error(`unrecognized internal function name "${name}"`); + } + } + break; + case SexpType.CharSxp: { + const len = this.assertInteger(this.inInteger()); + if(len < -1) { + throw new Error(`Invalid length ${len} of string.`); + } else if(len == -1) { + s.name = RValues.NaString; + } else if(len < 1000) { + s.name = this.readChar(len, levels); + } else { + s.name = this.readChar(len, levels); + } + break; + } + case SexpType.LglSxp: + case SexpType.IntSxp: + { + const len = this.readLength(); + s.type = type; + s.value = this.inIntegerVec(len); + break; + } + case SexpType.RealSxp: + { + const len = this.readLength(); + s.type = type; + s.value = this.inRealVec(len); + break; + } + case SexpType.CplxSxp: { + const len = this.readLength(); + s.type = type; + s.value = this.inComplexVec(len); + break; + } + case SexpType.StrSxp: { + const len = this.readLength(); + s.type = type; + s.value = new Array(len); + this.currentDepth++; + for(let count = 0; count < len; ++count) { + this.SET_STRING_ELT(s, count, this.assertRObjectData(this.readItem())); + } + this.currentDepth--; + + break; + } + case SexpType.VecSxp: + case SexpType.ExprSxp: { + const len = this.readLength(); + s.type = type; + s.value = new Array(len); + this.currentDepth++; + for(let count = 0; count < len; ++count) { + this.SET_VECTOR_ELT(s, count, this.readItem()); + } + this.currentDepth--; + break; + } + case SexpType.BcodesSxp: + s = this.readBC() as RObjectData; + break; + case SexpType.ClassRefSxp: + throw new Error('this version of R cannot read class references'); + case SexpType.GenericRefSxp: + throw new Error('this version of R cannot read generic function references'); + case SexpType.RawSxp: { + const len = this.readLength(); + s.type = type; + s.value = this.inRaw(len); + break; + } + case SexpType.ObjSxp: + s.type = SexpType.ObjSxp; + break; + default: + throw new Error(`ReadItem: unknown type ${type}, perhaps written by later version of R`); + } + if(type !== SexpType.CharSxp) { + s.levels = levels; + } + s.object = object; + if(s.type === SexpType.CharSxp) { + this.currentDepth++; + if(hasAttribute) { + this.readItem(); + } + this.currentDepth--; + } else { + this.currentDepth++; + s.attributes = hasAttribute ? [this.readItem()] as RObjectData[] : undefined; + this.currentDepth--; + } + if(s.type === SexpType.BcodesSxp && !this.R_BCVersionOK(s)) { + return this.R_BytecodeExpr(s) as RObjectData; + } + return s; + } + } + + /** + * Skips a serialized SEXP node, only filling in its type and calling other skip methods to + * advance the buffer position. + * + * Mirrors the structure of {@link readItemRecursive} but skips payload data. + * @returns A minimal {@link RObjectData} with little to no payload. + */ + skipItem(): RObjectData { + const flags = this.assertInteger(this.inInteger()); + const [type, levels, _object, hasAttribute, _hasTag] = this.unpackFlags(flags); + + let s: RObjectData = {}; + + switch(type) { + case SexpType.NilValueSxp: + case SexpType.EmptyEnvSxp: + case SexpType.BaseEnvSxp: + case SexpType.GlobalEnvSxp: + case SexpType.UnboundValueSxp: + case SexpType.MissingArgSxp: + case SexpType.BaseNamespaceSxp: + s.type = SexpType.EnvSxp; + return s; + case SexpType.RefSxp: + return this.getReadRef(this.inRefIndex(flags)); + case SexpType.NamespaceSxp: + this.skipStringVec(); + s.type = SexpType.EnvSxp; + this.addReadRef(s); + return s; + case SexpType.PackageSxp: + case SexpType.PersistSxp: { + this.skipStringVec(); + s.type = SexpType.CharSxp; + return s; + } + case SexpType.AltRepSxp: + { + this.currentDepth++; + const info = this.skipItem(); + const _state = this.skipItem(); + const _attr = this.skipItem(); + + s.type = (((info.cdr as RObjectData).cdr as RObjectData).car as RObjectData).type as SexpType; + this.currentDepth--; + return s; + } + case SexpType.SymSxp: { + this.currentDepth++; + s = this.skipItem(); + this.currentDepth--; + s.type = SexpType.SymSxp; + this.addReadRef(s); + return s; + } + case SexpType.EnvSxp: + { + this.skipInteger(); + s.type = SexpType.EnvSxp; + this.addReadRef(s); + + this.currentDepth++; + this.SetEnClos(s, this.assertRObjectData(this.skipItem())); + s.frame = this.skipItem(); + s.hashTab = this.skipItem(); + s.attributes = this.assertRObjectData(this.skipItem()).attributes; + + this.currentDepth--; + + if(!s.enClos || s.enClos === RValues.NilValue) { + this.SetEnClos(s, { + value: RValues.BaseEnv, + type: SexpType.EnvSxp, + enClos: RValues.NilValue + } as RObjectData); + } + return s; + } + case SexpType.ListSxp: + case SexpType.LangSxp: + case SexpType.CloSxp: + case SexpType.PromSxp: + case SexpType.DotSxp: + return this.readItemIterative(flags); + default: + switch(type) { + case SexpType.ExtPtrSxp: { + s.type = type; + this.addReadRef(s); + this.currentDepth++; + s.protected = this.skipItem(); + s.tag = this.skipItem(); + this.currentDepth--; + break; + } + case SexpType.WeakRefSxp: + s.value = this.R_MakeWeakRef(RValues.NilValue, RValues.NilValue, RValues.NilValue, false); + this.addReadRef(s); + break; + case SexpType.SpecialSxp: + case SexpType.BuiltInSxp: + { + s.type = type; + const len = this.assertInteger(this.inInteger()); + if(len < 0) { + throw new Error('invalid length'); + } + this.skipString(len); + } + break; + case SexpType.CharSxp: { + const len = this.assertInteger(this.inInteger()); + if(len < -1) { + throw new Error(`Invalid length ${len} of string.`); + } else if(len == -1) { + s.name = RValues.NaString; + } else if(len < 1000) { + s.name = this.readChar(len, levels); + } else { + s.name = this.readChar(len, levels); + } + break; + } + case SexpType.LglSxp: + case SexpType.IntSxp: + { + const len = this.readLength(); + s.type = type; + s.value = this.skipIntegerVec(len); + break; + } + case SexpType.RealSxp: + { + const len = this.readLength(); + s.type = type; + s.value = this.skipRealVec(len); + break; + } + case SexpType.CplxSxp: { + const len = this.readLength(); + s.type = type; + this.skipComplexVec(len); + break; + } + case SexpType.StrSxp: + case SexpType.VecSxp: + case SexpType.ExprSxp: { + const len = this.readLength(); + s.type = type; + this.currentDepth++; + for(let count = 0; count < len; ++count) { + this.skipItem(); + } + this.currentDepth--; + break; + } + case SexpType.BcodesSxp: + this.skipBC(); + s.type = SexpType.VecSxp; + break; + case SexpType.ClassRefSxp: + throw new Error('this version of R cannot read class references'); + case SexpType.GenericRefSxp: + throw new Error('this version of R cannot read generic function references'); + case SexpType.RawSxp: { + const len = this.readLength(); + s.type = type; + this.skipRaw(len); + break; + } + case SexpType.ObjSxp: + s.type = SexpType.ObjSxp; + break; + default: + throw new Error(`ReadItem: unknown type ${type}, perhaps written by later version of R`); + } + if(s.type === SexpType.CharSxp) { + this.currentDepth++; + if(hasAttribute) { + this.skipItem(); + } + this.currentDepth--; + } else { + this.currentDepth++; + s.attributes = hasAttribute ? [this.skipItem()] : undefined; + this.currentDepth--; + } + return s; + } + } + + /** + * Reads `len` raw bytes from the buffer into a number array. + * @param len - Number of raw bytes to read. + * @returns Array of unsigned byte values. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2062-L2084 | R source: ReadItem_Recursive} + */ + inRaw(len: number): number[]{ + const result = []; + switch(this.format) { + case SerializationFormat.Ascii: + for(let ix = 0; ix < len; ix++) { + const word = this.inWord(128); + result[ix] = Number.parseInt(word, 16); + } + break; + default: { + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + for(let i = 0; i < t; i++) { + result[done + i] = this.buffer[this.offset]; + this.offset += 1; + } + } + } + } + return result; + } + + /** + * Advances the buffer offset past `len` raw bytes without reading values. + * + * Mirrors {@link inRaw}. + * @param len - Number of raw bytes to skip. + */ + skipRaw(len: number): void{ + if(this.format === SerializationFormat.Ascii) { + for(let ix = 0; ix < len; ix++) { + this.skipWord(); + } + } else { + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + for(let i = 0; i < t; i++) { + this.offset += 1; + } + } + } + } + + /** + * Resolves package environments. + * @param s - String-vec {@link RObject} identifying the package. + * + * Not implemented yet! + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/envir.c#L3732-L3741 | R source: R_FindPackageEnv} + */ + rFindPackageEnv(s: RObjectData): RObjectData { + console.warn('Resolving package environments was triggered, but is not implemented yet!'); + return s; + } + + /** + * Decodes the SEXP flags. + * @param flags - Raw 32-bit flags word. + * @returns `[type, levels, isObject, hasAttribute, hasTag]`. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L748-L756 | R source: UnpackFlags} + */ + unpackFlags(flags: number): [number, number, boolean, boolean, boolean] { + const pType = flags & 255; + const pLevels = flags >> 12; + const pIsObj = (flags & (1 << 8)) !== 0; + const pHasAttr = (flags & (1 << 9)) !== 0; + const pHasTag = (flags & (1 << 10)) !== 0; + + return [pType, pLevels, pIsObj, pHasAttr, pHasTag]; + } + + /** + * Retrieves an object from the reference table. + * @param index - 1-based reference index. + * @returns A registered {@link RObjectData}. + * @throws Error if the index is out of range. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1461-L1469 | R source: GetReadRef} + */ + getReadRef(index: number): RObjectData { + const i = index - 1; + + if(i < 0 || i >= this.refTable.length) { + throw new Error('reference index out of range'); + } + return this.refTable[i] as RObjectData; + } + + /** + * Extracts the reference index from the given flags. + * + * When bits 8–31 are non-zero they encode the index directly; + * otherwise the index is read as the next integer from the stream. + * @param flags - Raw 32-bit flags word. + * @returns A 1-based reference index. + * @see {@link http://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L782-L789 | R source: InRefIndex} + */ + inRefIndex(flags: number): number { + const i = flags >> 8; + if(i === 0) { + return this.assertInteger(this.inInteger()); + } else { + return i; + } + } + + /** + * Appends an object to the reference table so it can be resolved later by {@link getReadRef}. + * @param value - The {@link RObject} to register. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1471-L1490 | R source: AddReadRef} + */ + addReadRef(value: RObject): void { + this.refTable.push(value); + } + + /** + * Reads a persistent string vector from the stream. + * @returns An {@link RObjectData} of type `CharSxp` whose `value` array holds the deserialized string items. + * @throws Error if the names flag is non-zero. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1492-L1504 | R source: InStringVec} + */ + inStringVec(): RObjectData { + if(this.inInteger() !== 0) { + throw new Error('names in persistent strings are not supported yet'); + } + const len = this.assertInteger(this.inInteger()); + const s: RObjectData = {}; + s.type = SexpType.CharSxp; + s.value = new Array(len); + this.currentDepth++; + for(let i = 0; i < len; i++) { + (s.value)[i] = this.readItem(); + } + this.currentDepth--; + return s; + } + + /** + * Advances the buffer past a persistent string vector without reading values. + * + * Mirrors {@link inStringVec}. + * @throws Error if the names flag is non-zero. + */ + skipStringVec(): void { + if(this.inInteger() !== 0) { + throw new Error('names in persistent strings are not supported yet'); + } + const len = this.assertInteger(this.inInteger()); + this.currentDepth++; + for(let i = 0; i < len; i++) { + this.skipItem(); + } + this.currentDepth--; + } + + /** + * Sets the enclosing environment of an environment object. + * @param x - Environment whose enclosure should be set. + * @param v - Parent environment. + * @throws Error if the parent is invalid or would introduce a cycle. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/memory.c#L4677-L4690 | R source: SET_ENCLOS} + */ + SetEnClos(x: RObjectData, v: RObjectData): void { + if(v.value === undefined || v.value === RValues.NilValue) { + v.value = RValues.EmptyEnv; + } + if(v.type !== SexpType.EnvSxp) { + throw new Error("'parent' is not an environment"); + } + + for(let e: RObject = v; e !== RValues.NilValue; e = e.enClos ?? RValues.NilValue){ + if(e === x) { + throw new Error('cycles in parent chains are not allowed'); + } + } + x.enClos = v; + } + + /** + * Iteratively deserializes linked-list based SEXP structures. + * @param flags - Initial flags. + * @returns Head node of the reconstructed pairlist chain as {@link RObjectData}. + * @throws Error if the initial type is not a valid pairlist type. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1800-L1868 | R source: ReadItem_Iterative} + */ + readItemIterative(flags: number): RObjectData{ + let sFirst: RObjectData | null = null; + let sLast: RObjectData = {}; + + let type = flags & 255; + + const validIterativeTypes = new Set([ + SexpType.ListSxp, // 2 + SexpType.LangSxp, // 6 + SexpType.CloSxp, // 3 + SexpType.PromSxp, // 5 + SexpType.DotSxp // 17 + ]); + + if(!validIterativeTypes.has(type)) { + throw new Error('Wrong type.'); + } + + while(validIterativeTypes.has(type)) { + const unpackedFlags = this.unpackFlags(flags); + type = unpackedFlags[0] as SexpType; + const levels = unpackedFlags[1]; + const isObject = unpackedFlags[2]; + const hasAttr = unpackedFlags[3]; + const hasTag = unpackedFlags[4]; + const s: RObjectData = {}; + + s.type = type; + s.levels = levels; + s.object = isObject; + this.currentDepth++; + + s.attributes = hasAttr ? [this.shortcut ? this.skipItem() : this.assertRObjectData(this.readItem())] : undefined; + s.tag = hasTag ? this.readItem() : RValues.NilValue; + + if(hasTag && this.currentDepth == RDAParser.INITIAL_DEPTH && typeof s.tag === 'object') { + this.lastName = s.tag.name; + this.setLastName = true; + } + + s.car = this.shortcut ? this.skipItem() : this.readItem(); + this.currentDepth--; + + if(sFirst === null) { + sFirst = s; + } else { + sLast.cdr = s; + } + sLast = s; + + if(type === SexpType.CloSxp && (!s.enClos || s.cloEnv === RValues.NilValue)) { + s.cloEnv = RValues.EmptyEnv; + } else if(type === SexpType.PromSxp && (!s.prEnv || s.prEnv === RValues.NilValue)) { + s.prEnv = RValues.BaseEnv; + } + + flags = this.assertInteger(this.inInteger()); + type = flags & 255; + } + + this.currentDepth++; + const s = this.readItemRecursive(flags); + this.currentDepth--; + sLast.cdr = s; + return sFirst as RObjectData; + } + + /** + * Creates a weak reference object. + * @param key - Weak reference key object. + * @param val - Referenced value. + * @param fin - Finalizer function or NULL. + * @param onexit - Whether the finalizer should run on exit. + * @returns The created weak reference object as {@link RObject}. + * @throws Error if the finalizer type is invalid. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/memory.c#L1424-L1435 | R source: R_MakeWeakRef} + */ + R_MakeWeakRef(key: SexpType | RValues.NilValue, val: RObject, fin: SexpType | RValues.NilValue, onexit: boolean): RObject { + switch(fin) { + case SexpType.NilSxp: + case SexpType.CloSxp: + case SexpType.BuiltInSxp: + case SexpType.SpecialSxp: + break; + default: + throw new Error('finalizer must be a function or NULL'); + } + return this.newWeakRef(key, val, fin, onexit); + } + + /** + * Allocates and initializes a weak reference object. + * @param key - Weak reference key object. + * @param val - Referenced value. + * @param fin - Finalizer function. + * @param onexit - Whether the finalizer should run on exit. + * @returns The initialized weak reference object as {@link RObject}. + * @throws Error if the key type is invalid. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/memory.c#L1388-L1422 | R source: NewWeakRef} + */ + newWeakRef(key: SexpType | RValues.NilValue, val: RObject, fin: SexpType, onexit: boolean): RObject{ + switch(key) { + case SexpType.NilSxp: + case SexpType.EnvSxp: + case SexpType.ExtPtrSxp: + case SexpType.BcodesSxp: + break; + default: + throw new Error('can only weakly reference/finalize reference objects'); + } + + // PROTECT(val = MAYBE_REFERENCED(val) ? duplicate(val) : val); + // w = allocVector(VECSXP, WEAKREF_SIZE); + // SET_TYPEOF(w, WEAKREFSXP); + const w: RObjectData = {}; + + w.type = SexpType.WeakRefSxp; + + if(!key){ //|| key !== RValues.NilValue + w.key = key; + w.value = val; + w.finalizer = fin; + w.next = this.RWeakRefs; + // CLEAR_READY_TO_FINALIZE(w); + if(w.gp) { + w.gp &= ~1; + } + + if(onexit){ + if(w.gp) { + w.gp |= 2; + } + } else { + if(w.gp) { + w.gp &= ~2; + } + } + + this.RWeakRefs = w; + } + return w; + } + + /** + * Creates or retrieves a cached primitive function object. + * @param index - Primitive function table index. + * @param evaluation - Non-zero for {@link SexpType.BuiltInSxp}, zero for {@link SexpType.SpecialSxp}. + * @returns of type {@link SexpType.BuiltInSxp} or {@link SexpType.SpecialSxp}. + * @throws Error if the index is out of range or the cached type mismatches. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/dstruct.c#L37-L68 | R source: mkPRIMSXP} + */ + mkPrimSxp(index: number, evaluation: number): RObjectData { + const type = evaluation ? SexpType.BuiltInSxp : SexpType.SpecialSxp; + let primCache: RObject = RValues.NilValue; + let funTabSize = 0; + if(!primCache || primCache === RValues.NilValue){ + funTabSize = Object.keys(RFunTabOffsets).length; + + primCache = {}; + primCache.type = SexpType.VecSxp; + primCache.value = funTabSize; + } + + if(index < 0 || index >= funTabSize) { + throw new Error('offset is out of R_FunTab range'); + } + + let result = this.VECTOR_ELT(primCache, index); + + if(!result || result === RValues.NilValue) { + result = {}; + result.type = type; + result.offset = index; + // SET_VECTOR_ELT(primCache, index, result); + } else if(result.type !== type) { + throw new Error('requested primitive type is not consistent with cached value'); + } + + return result; + } + + /** + * Reads `len` bytes as a character string and applies encoding from the GP bits. + * + * Encoding flags: bit 3 = UTF-8, bit 2 = Latin-1, bit 6 = bytes (returned as-is). + * @param len - Number of bytes to read. + * @param levels - GP levels bits from the `CharSxp` flags word. + * @returns The decoded string, or `''` when the encoding is not yet handled. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1689-L1759 | R source: ReadChar} + */ + readChar(len: number, levels: number): string{ + const cBuf = this.inString(len); + const bytes = Buffer.from(cBuf, 'latin1'); + + if(levels & (1 << 3)) { + return new TextDecoder('utf-8').decode(bytes); + } + if(levels & (1 << 2)) { + return new TextDecoder('iso-8859-1').decode(bytes); + } + if(levels & (1 << 6)) { + return bytes.toString('latin1'); + } + console.warn('Native encoding detected! Native encoding not supported yet! Value will be empty'); + return ''; + } + + /** + * Reads a vector length from the stream. + * @returns The vector length. + * @throws Error for negative lengths or an invalid high-word value. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1761-L1782 | R source: ReadLENGTH} + */ + readLength(): number { + const len = this.assertInteger(this.inInteger()); + if(len < -1) { + throw new Error('negative serialized length for vector'); + } + if(len == -1) { + const len1 = this.assertInteger(this.inInteger()); + const len2 = this.assertInteger(this.inInteger()); + const xLen = len1; + /* sanity check for now */ + if(len1 > RDAParser.MAX_VECTOR_LENGTH) { + throw new Error('invalid upper part of serialized vector length'); + } + return (xLen << 32) + len2; + } else { + return len; + } + } + + /** + * Reads `len` integers from the buffer. + * @param len - Number of integers to read. + * @returns Array of integer values or {@link RValues.NaInteger}. + * @throws Error for BINARY format or XDR buffer overrun. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1507-L1541 | R source: InIntegerVec} + */ + inIntegerVec(len: number): (number | RValues.NaInteger)[]{ + switch(this.format) { + case SerializationFormat.Xdr: + { + let t = 0; + const result: number[] = []; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + for(let cnt = 0; cnt < t; cnt++) { + if(this.offset + 4 > this.buffer.length) { + throw new Error('XDR read failed'); + } + result[done + cnt] = this.buffer.readInt32BE(this.offset); + this.offset += 4; + } + } + return result; + } + case SerializationFormat.Binary: + { + throw new Error('No binary support yet.'); + } + default: { + const result: (number | RValues.NaInteger)[] = []; + for(let cnt = 0; cnt < len; cnt++) { + result[cnt] = this.inInteger(); + } + return result; + } + } + } + + /** + * Advances the buffer past `len` serialized integers without reading values. + * + * Mirrors {@link inIntegerVec}. + * @param len - Number of integers to skip. + * @returns An empty array. + * @throws Error for BINARY format or XDR buffer overrun. + */ + skipIntegerVec(len: number): (number | RValues.NaInteger)[] { + switch(this.format) { + case SerializationFormat.Xdr: + { + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + for(let cnt = 0; cnt < t; cnt++) { + if(this.offset + 4 > this.buffer.length) { + throw new Error('XDR read failed'); + } + this.offset += 4; + } + } + break; + } + case SerializationFormat.Binary: + { + throw new Error('No binary support yet.'); + } + default: { + for(let cnt = 0; cnt < len; cnt++) { + this.skipInteger(); + } + } + } + return []; + } + + /** + * Reads `len` doubles from the buffer. + * @param len - Number of doubles to read. + * @returns Array of numbers or {@link RValues}. + * @throws Error for BINARY format. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1543-L1577 | R source: InRealVec} + */ + inRealVec(len: number): (number | RValues)[] | null[]{ + switch(this.format) { + case SerializationFormat.Xdr: { + const result = []; + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + + const chunkBytes = t * RDAParser.SIZE_OF_DOUBLE; + const chunk = this.buffer.subarray(this.offset, this.offset + chunkBytes); + this.offset += chunkBytes; + + for(let i = 0; i < t; i++) { + const value = chunk.readDoubleBE(i * RDAParser.SIZE_OF_DOUBLE); + result.push(value); + } + } + return result; + } + case SerializationFormat.Binary: + { + throw new Error('No binary support yet.'); + } + default: { + const result: (Real)[] = []; + for(let cnt = 0; cnt < len; cnt++) { + result[cnt] = this.inReal(); + } + return result; + } + } + } + + /** + * Advances the buffer past `len` serialized doubles without reading values. + * + * Mirrors {@link inRealVec}. + * @param len - Number of doubles to skip. + * @returns An empty array. + * @throws Error for BINARY format. + */ + skipRealVec(len: number): (number | RValues)[] | null[] { + switch(this.format) { + case SerializationFormat.Xdr: { + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + const chunkBytes = t * RDAParser.SIZE_OF_DOUBLE; + this.offset += chunkBytes; + } + break; + } + case SerializationFormat.Binary: + { + throw new Error('No binary support yet.'); + } + default: { + for(let cnt = 0; cnt < len; cnt++) { + this.skipReal(); + } + } + } + return []; + } + + /** + * Reads the next double from the buffer. + * @returns A number or an {@link RValues}. + * @throws TypeError if the ASCII token is not a valid float. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L427-L463 | R source: InReal} + */ + inReal(): Real { + switch(this.format){ + case SerializationFormat.Ascii: { + const word = this.inWord(128); + + if(word === 'NA') { + return RValues.NaReal; + } else if(word === 'NaN') { + return RValues.NaN; + } else if(word === 'Inf') { + return RValues.PosInf; + } else if(word === '-Inf') { + return RValues.NegInf; + } else { + const d = Number.parseFloat(word); + if(Number.isNaN(d)) { + throw new TypeError('Read error: Invalid numeric ASCII format'); + } + return d; + } + } + case SerializationFormat.Binary: { + const d = this.buffer.readDoubleLE(this.offset); + this.offset += 8; + return d; + } + case SerializationFormat.Xdr: { + const d = this.buffer.readDoubleBE(this.offset); + this.offset += 8; + return d; + } + default: + return RValues.NilValue; + } + } + + /** + * Advances the buffer past the next serialized double without reading it. + * + * Mirrors {@link inReal}. + */ + skipReal(): void { + if(this.format === SerializationFormat.Ascii) { + this.skipWord(); + return; + + } else if(this.format === SerializationFormat.Binary || this.format === SerializationFormat.Xdr) { + this.offset += 8; + return; + } + } + + /** + * Reads `len` complex numbers from the buffer. + * + * Each value is read as two consecutive doubles via {@link inComplex}. + * @param len - Number of complex values to read. + * @returns Array of {@link Complex} objects. + * @throws Error for BINARY format. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L1579-L1616 | R source: InComplexVec} + */ + inComplexVec(len: number): Complex[] { + switch(this.format) { + case SerializationFormat.Xdr: { + const result: Complex[] = []; + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + for(let cnt = 0; cnt < t; cnt++) { + result[done] = this.inComplex(); + } + } + return result; + } + case SerializationFormat.Binary: { + throw new Error('No binary support yet.'); + } + default: { + const result: Complex[] = []; + for(let cnt = 0; cnt < len; cnt++) { + result[cnt] = this.inComplex(); + } + return result; + } + } + } + + /** + * Advances the buffer past `len` serialized complex numbers. + * + * Mirrors {@link inComplexVec}. + * @param len - Number of complex values to skip. + * @throws Error for BINARY format. + */ + skipComplexVec(len: number): void { + switch(this.format) { + case SerializationFormat.Xdr: { + let t = 0; + for(let done = 0; done < len; done += t) { + t = Math.min(RDAParser.CHUNK_SIZE, len - done); + for(let cnt = 0; cnt < t; cnt++) { + this.skipComplex(); + } + } + break; + } + case SerializationFormat.Binary: { + throw new Error('No binary support yet.'); + } + default: { + for(let cnt = 0; cnt < len; cnt++) { + this.skipComplex(); + } + } + } + } + + /** + * Reads a single complex number as two consecutive real values. + * @returns A {@link Complex} object with `r` (real) and `i` (imaginary) parts. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L465-L471 | R source: InComplex} + */ + inComplex(): Complex { + return { r: this.inReal(), i: this.inReal() }; + } + + /** + * Advances the buffer past a single serialized complex number (two doubles). + * + * Mirrors {@link inComplex}. + */ + skipComplex(): void { + this.skipReal(); + this.skipReal(); + } + + /** + * Sets the `i`-th element of a character vector. + * @param x - The character vector with type {@link SexpType.StrSxp} to modify. + * @param i - index to set. + * @param v - The {@link SexpType.CharSxp} {@link RObjectData} whose `name` is stored. + * @throws Error if `x` is not a {@link SexpType.StrSxp} or `i` is out of bounds. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/memory.c#L4283-L4301 | R source: SET_STRING_ELT} + */ + SET_STRING_ELT(x: RObjectData, i: number, _v: RObjectData): void { + if(x.type !== SexpType.StrSxp) { + throw new Error(`SET_STRING_ELT() can only be applied to a 'character vector', not a '${x.type}'`); + } + // if(v.type !== SexpType.CharSxp) { + // throw new Error(`Value of SET_STRING_ELT() must be a 'CHARSXP' not a '${v.type}'`); + // } + + const arr = x.value as []; + + if(i < 0 || i >= arr.length) { + throw new Error(`attempt to set index ${i}/${arr.length} in SET_STRING_ELT`); + } + + // if(x.altRep){ + // this.ALTSTRING_SET_ELT(x, i, v); + // } + + // arr[i] = v.name; + } + + /** + * Sets the `i`-th element of a generic list or vector. + * + * Mirrors R's `SET_VECTOR_ELT` macro. + * @param x - The list or vector to modify. + * @param i - index to set. + * @param v - The {@link RObject} to store at position `i`. + * @throws Error if `x` is not a list type or `i` is out of bounds. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/memory.c#L4303-L4322 | R source: SET_VECTOR_ELT} + */ + SET_VECTOR_ELT(x: RObjectData, i: number, v: RObject): void { + if(x.type !== SexpType.VecSxp && + x.type !== SexpType.ExprSxp && + x.type !== SexpType.WeakRefSxp) { + throw new Error(`SET_VECTOR_ELT() can only be applied to a 'list', not a '${x.type}'`); + } + if(i < 0 || i >= (x.value as Array).length) { + throw new Error(`attempt to set index ${i}/${(x.value as Array).length} in SET_VECTOR_ELT`); + } + + (x.value as RObject[])[i] = v; + } + + /** + * Deserializes an R bytecode object. + * @returns A {@link SexpType.BcodesSxp} {@link RObject}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2221-L2228 | R source: ReadBC} + */ + readBC(): RObject { + const reps: RObjectData = {}; + reps.type = SexpType.VecSxp; + reps.value = new Array(this.assertInteger(this.inInteger())); + return this.readBC1(reps); + } + + /** + * Advances the buffer past a bytecode object without reading it. + * + * Mirrors {@link readBC}. + */ + skipBC(): void{ + this.skipInteger(); + this.skipBC1(); + } + + /** + * Advances past a single bytecode. + * + * Mirrors {@link readBC1}. + */ + skipBC1(): void { + this.currentDepth++; + this.skipItem(); + this.currentDepth--; + this.skipBCConsts(); + } + + /** + * Advances past all bytecode constants. + * + * Mirrors {@link ReadBCConsts}. + */ + skipBCConsts(): void { + const n = this.assertInteger(this.inInteger()); + for(let i = 0; i < n; i++) { + const type = this.inInteger(); + switch(type) { + case SexpType.BcodesSxp: { + this.skipBC1(); + break; + } + case SexpType.LangSxp: + case SexpType.ListSxp: + case SexpType.BcRepDef: + case SexpType.BcRepRef: + case SexpType.AltLangSxp: + case SexpType.AttrListSxp: { + this.skipBCLang(type); + break; + } + default: + this.currentDepth++; + this.skipItem(); + this.currentDepth--; + } + } + } + + /** + * Registers a bytecode object after encoding. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/eval.c#L8820-L8885 | R source: R_registerBC} + */ + R_registerBC(_bytes: RObject, _s: RObject) { + throw new Error('BC not implemented yet'); + } + + /** + * Deserializes a single bytecode object. + * @param reps - Pre-allocated repetition table shared across all constants. + * @returns A {@link SexpType.BcodesSxp} {@link RObjectData}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2205-L2219 | R source: ReadBC1} + */ + readBC1(reps: RObjectData): RObjectData { + const s: RObjectData = {}; + s.type = SexpType.BcodesSxp; + this.currentDepth++; + s.car = this.readItem(); + this.currentDepth--; + const _bytes = s.car; + // s.car = R_bcEncode(bytes); + s.cdr = this.ReadBCConsts(reps); + s.tag = RValues.NilValue; + // R_registerBC(bytes, s); + return s; + } + + /** + * Encodes bytecode instructions. + * @param _bytes - integer array + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/eval.c#L8723-L8771 | R source: R_bcEncode} + */ + _R_bcEncode(_bytes: Int32Array){ + throw new Error('Not implemented'); + } + + /** + * Reads the constants of a bytecode object. + * @param reps - Shared repetition table for `BcRepDef`/`BcRepRef` resolution. + * @returns A `VecSxp` {@link RObjectData} holding all `n` constants. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2173-L2203 | R source: ReadBCConsts} + */ + ReadBCConsts(reps: RObjectData): RObjectData { + const n = this.assertInteger(this.inInteger()); + const ans: RObjectData = {}; + ans.type = SexpType.VecSxp; + ans.value = new Array(n); + for(let i = 0; i < n; i++) { + const type = this.inInteger(); + switch(type) { + case SexpType.BcodesSxp: { + const c = this.readBC1(reps); + this.SET_VECTOR_ELT(ans, i, c); + break; + } + case SexpType.LangSxp: + case SexpType.ListSxp: + case SexpType.BcRepDef: + case SexpType.BcRepRef: + case SexpType.AltLangSxp: + case SexpType.AttrListSxp: { + const c = this.ReadBCLang(type, reps); + this.SET_VECTOR_ELT(ans, i, c); + break; + } + default: + this.currentDepth++; + this.SET_VECTOR_ELT(ans, i, this.readItem()); + this.currentDepth--; + } + } + return ans; + } + + /** + * Reads a single language object from bytecode constants. + * @param type - {@link SexpType} read from the constants. + * @param reps - Shared repetition table. + * @returns The deserialized language {@link RObjectData}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/serialize.c#L2125-L2171 | R source: ReadBCLang} + */ + ReadBCLang(type: SexpType, reps: RObjectData): RObjectData { + switch(type) { + case SexpType.BcRepRef: + return this.VECTOR_ELT(reps, this.assertInteger(this.inInteger())) as RObjectData; + case SexpType.BcRepDef: + case SexpType.LangSxp: + case SexpType.ListSxp: + case SexpType.AltLangSxp: + case SexpType.AttrListSxp: + { + let pos = -1; + let hasAttr = false; + if(type == SexpType.BcRepDef) { + pos = this.assertInteger(this.inInteger()); + type = this.assertInteger(this.inInteger()); + } + switch(type) { + case SexpType.AltLangSxp: type = SexpType.LangSxp; hasAttr = true; break; + case SexpType.AttrListSxp: type = SexpType.ListSxp; hasAttr = true; break; + } + const ans: RObjectData = {}; + ans.type = type; + if(pos >= 0) { + this.SET_VECTOR_ELT(reps, pos, ans); + } + this.currentDepth++; + if(hasAttr) { + ans.attributes ??= []; + ans.attributes.push(this.assertRObjectData(this.readItem())); + } + ans.tag = this.readItem(); + this.currentDepth--; + ans.car = this.ReadBCLang(this.assertInteger(this.inInteger()), reps); + ans.cdr = this.ReadBCLang(this.assertInteger(this.inInteger()), reps); + return ans; + } + default: + { + this.currentDepth++; + const res = this.readItem() as RObjectData; + this.currentDepth--; + return res; + } + } + } + + /** + * Advances the buffer past a single language object. + * + * Mirrors {@link ReadBCLang}. + * @param type - read {@link SexpType}. + */ + skipBCLang(type: SexpType) { + switch(type) { + case SexpType.BcRepRef: + this.skipInteger(); + break; + case SexpType.BcRepDef: + case SexpType.LangSxp: + case SexpType.ListSxp: + case SexpType.AltLangSxp: + case SexpType.AttrListSxp: + { + let hasAttr = false; + if(type == SexpType.BcRepDef) { + this.skipInteger(); + type = this.assertInteger(this.inInteger()); + } + switch(type) { + case SexpType.AltLangSxp: type = SexpType.LangSxp; hasAttr = true; break; + case SexpType.AttrListSxp: type = SexpType.ListSxp; hasAttr = true; break; + } + + this.currentDepth++; + if(hasAttr) { + this.skipItem(); + } + this.readItem(); + this.skipItem(); + this.currentDepth--; + this.skipBCLang(this.assertInteger(this.inInteger())); + this.skipBCLang(this.assertInteger(this.inInteger())); + break; + } + default: + { + this.currentDepth++; + this.skipItem(); + this.currentDepth--; + } + } + } + + /** + * Retrieves the `i`-th element of a generic list or vector. + * @param x - The list / expression / weak-ref vector. + * @param i - The index to retrieve. + * @returns The {@link RObject} at position `i`. + * @throws Error if `x` is not a list type or `i` is out of bounds. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/memory.c#L4122-L4142 | R source: VECTOR_ELT} + */ + VECTOR_ELT(x: RObjectData, i: number): RObject { + if(x.type !== SexpType.VecSxp && + x.type !== SexpType.ExprSxp && + x.type !== SexpType.WeakRefSxp) { + throw new Error(`VECTOR_ELT() can only be applied to a 'list', not a '${x.type}'`); + } + // "VECTOR_ELT", "list", R_typeToChar(x)); + if(i < 0 || i >= (x.value as RObject[])?.length) { + throw new Error('attempt access index %lld/%lld in VECTOR_ELT'); + } + // (long long)i, (long long)XLENGTH(x)); + if(x.altRep) { + const ans = (x as RObject[])[i]; + /* the element is marked as not mutable since complex + assignment can't see reference counts on any intermediate + containers in an ALTREP */ + // MARK_NOT_MUTABLE(ans); + return ans; + } else { + return (x as RObject[])[i]; + } + } + + /** + * Checks whether a bytecode object's version is within the supported range. + * @param s - The {@link SexpType.BcodesSxp} {@link RObjectData} to check. + * @returns `true` if the version is supported. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/eval.c#L7166-L7175 | R source: R_BCVersionOK} + */ + R_BCVersionOK(s: RObjectData): boolean{ + if(s.type !== SexpType.BcodesSxp) { + return false; + } + + // const pc = s.code; + const pc = 0; + const version = pc; + + return (version >= 9 && version <= 12); + } + + /** + * Returns the source-language expression for an unsupported bytecode object. + * @param s - The {@link SexpType.BcodesSxp} {@link RObjectData}. + * @returns First constant pool entry if available, otherwise {@link RValues.NilValue}. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/eval.c#L5566-L5574 | R source: bytecodeExpr} + */ + R_BytecodeExpr(s: RObjectData): RObject { + if(s.type === SexpType.BcodesSxp) { + if(((s.cdr as RObjectData).value as RObject[])?.length > 0) { + return this.VECTOR_ELT(s.cdr as RObjectData, 0); + } else { + return RValues.NilValue; + } + } else { + return s; + } + } + + /** + * Attempts to unserialize an ALTREP object. + * @param info - The info to be unserialized. + * @param _state - Serialized state. + * @param _attr - Serialized attributes. + * @param _objf - IS_OBJECT flag. + * @param _levs - GP levels bits. + * @returns The {@link RObjectData} or . + * @throws Error if the base type is not a supported vector type. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/altrep.c#L298-L338 | R source: ALTREP_UNSERIALIZE_EX} + */ + AltRepUnserializeEx(info: RObjectData, _state: RObjectData, _attr: RObjectData, _objf: boolean, _levs: number): RObjectData { + const cSym = info.car; + const pSym = (info.cdr as RObjectData).car; + const type = (((info.cdr as RObjectData).cdr as RObjectData).car as RObjectData).type as SexpType; + + const clss = this.ALTREP_UNSERIALIZE_CLASS(info); + if(clss == undefined) { + switch(type) { + case SexpType.LglSxp: + case SexpType.IntSxp: + case SexpType.RealSxp: + case SexpType.CplxSxp: + case SexpType.StrSxp: + case SexpType.RawSxp: + case SexpType.VecSxp: + case SexpType.ExprSxp: + console.warn(`cannot unserialize ALTVEC object of class '${(cSym as RObjectData).name}' + from package '${(pSym as RObjectData).name}' returning length zero vector`); + info.type = type; + info.value = []; + return info; + default: + throw new Error('cannot unserialize this ALTREP object'); + } + } + // + // const rtype = ALTREP_CLASS_BASE_TYPE(clss); + // if(type !== rtype) { + // console.warn(`serialized class '${(cSym as RObjectData).name}' from package + // '${(pSym as RObjectData).name}' has type ${SexpType[type]} registered class has type ${SexpType[rtype]}`); + // } + // + // const altrep_methods_t = CLASS_METHODS_TABLE(c); + // const val = altrep_methods_t.UnserializeEX(clss, state, attr, objf, levs); + // return val; + return {} as RObjectData; + } + + /** + * Looks up the ALTREP class for the given class/package symbol pair. + * @param info - The info to be looked up. + * @returns The class {@link RObjectData} when found, `undefined` if unregistered, or `null` if `info` is not a `ListSxp`. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/altrep.c#L279-L296 | R source: ALTREP_UNSERIALIZE_CLASS} + */ + ALTREP_UNSERIALIZE_CLASS(info: RObjectData) { + if(info.type == SexpType.ListSxp) { + const cSym = info.car as RObjectData; + const pSym = (info.cdr as RObjectData).car as RObjectData; + let clss = this.LookupClass(cSym, pSym); + if(clss === undefined) { + const pName = this.ScalarString(pSym.name as string); + try { + this.R_FindNamespace(pName); + } catch(e){ + console.log(`${pName.value as string} ${e as string}`); + } + clss = this.LookupClass(cSym, pSym); + } + return clss; + } + return null; + } + + /** + * Looks up an ALTREP class entry by class and package symbol. + * @param cSym - Class symbol. + * @param pSym - Package symbol. + * @returns The {@link RObjectData} or `undefined` if not registered. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/altrep.c#L90-L94 | R source: LookupClass} + */ + LookupClass(cSym: RObjectData, pSym: RObjectData) { + const entry = this.LookupClassEntry(cSym, pSym); + return entry === undefined || entry === null ? undefined : entry.car as RObjectData; + } + + /** + * Searches the ALTREP class registry for an entry matching the given symbols. + * @param cSym - Class symbol to match. + * @param pSym - Package symbol to match. + * @returns The matching registry node or `null` if not found. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/altrep.c#L53-L59 | R source: LookupClassEntry} + */ + LookupClassEntry(cSym: RObject, pSym: RObject): RObjectData | null{ + if(!this.Registry) { + return null; + } + + for(let chain: RObjectData | null = (this.Registry).cdr as RObjectData || null; chain; chain = chain.cdr as RObjectData | null) { + if((chain.car as RObjectData).tag == cSym && ((chain.car as RObjectData).cdr as RObjectData).car === pSym) { + return chain.car as RObjectData; + } + } + return null; + } + + /** + * Creates a length-1 character vector wrapping the given string. + * @param x - The string value to wrap. + * @returns A {@link SexpType.StrSxp} {@link RObjectData} with one element. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/include/Rinlinedfuns.h#L1044-L1052 | R source: ScalarString} + */ + ScalarString(x: string): RObjectData{ + const ans: RObjectData = {}; + ans.type = SexpType.StrSxp; + ans.value = new Array(1); + this.SET_STRING_ELT(ans, 0, { name: x }); + return ans; + } + + /** + * Recomputes and restores the cached hash-table priority count for an environment. + * @param s - The {@link RObjectData} whose hash table is to be repaired. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/envir.c#L3685-L3698 | R source: R_RestoreHashCount} + */ + restoreHashCount(s: RObjectData): void{ + if(s.hashTab !== RValues.NilValue) { + const table = s.hashTab as RObjectData; + const size = (table.value as RObject[]).length; + let _count = 0; + for(let i= 0; i < size; i++) { + if(this.VECTOR_ELT(table, i) !== RValues.NilValue) { + _count++; + } + } + // SET_HASHPRI(table, count); + } + } + + /** + * Converts a linked-list based R object tree into a flat array representation. + * @param node - Root node of the deserialized object tree. + * @param shortcut - Whether payload values should be omitted. + * @returns Flattened array of top-level objects. + */ + flattenRObject(node: RObject, shortcut: boolean): RObjectData[] { + const result: RObjectData[] = []; + + function walk(n: RObject | null, shortcut: boolean) { + if(!n || n === RValues.NilValue) { + return; + } + + const name = (n.tag as RObjectData)?.name; + + if(name !== undefined) { + let copy: RObjectData = {}; + if(shortcut) { + copy = { + name: (n.tag as RObjectData).name, + type: (n.car as RObjectData).type, + }; + } else { + copy= { + name: (n.tag as RObjectData).name, + value: (n.car as RObjectData).value, + hasAttribute: !!n.hasAttribute, + attributes: n.attributes, + type: (n.car as RObjectData).type, + tag: RValues.NilValue + }; + } + result.push(copy); + } + + if(n.cdr && n.cdr !== RValues.NilValue) { + walk(n.cdr, shortcut); + } + } + walk(node, shortcut); + + return result; + } +} \ No newline at end of file diff --git a/src/project/plugins/file-plugins/files/r-fun-tab.ts b/src/project/plugins/file-plugins/files/r-fun-tab.ts new file mode 100644 index 00000000000..1e466a5dc04 --- /dev/null +++ b/src/project/plugins/file-plugins/files/r-fun-tab.ts @@ -0,0 +1,1655 @@ +export type PPKind = + | 'PP_INVALID' + | 'PP_ASSIGN' + | 'PP_ASSIGN2' + | 'PP_BINARY' + | 'PP_BINARY2' + | 'PP_BREAK' + | 'PP_CURLY' + | 'PP_FOR' + | 'PP_FUNCALL' + | 'PP_FUNCTION' + | 'PP_IF' + | 'PP_NEXT' + | 'PP_PAREN' + | 'PP_REPEAT' + | 'PP_RETURN' + | 'PP_SUBASS' + | 'PP_SUBSET' + | 'PP_UNARY' + | 'PP_WHILE' + | 'PP_DOLLAR' + | 'PP_FOREIGN' + | 'PP_TILDE'; + +export type Precedence = + | 'PREC_FN' + | 'PREC_LEFT' + | 'PREC_EQ' + | 'PREC_RIGHT' + | 'PREC_TILDE' + | 'PREC_OR' + | 'PREC_AND' + | 'PREC_NOT' + | 'PREC_COMPARE' + | 'PREC_SUM' + | 'PREC_PROD' + | 'PREC_PERCENT' + | 'PREC_COLON' + | 'PREC_SIGN' + | 'PREC_POWER' + | 'PREC_SUBSET' + | 'PREC_DOLLAR' + | 'PREC_NS'; + +export interface FunTabEntry { + /** R function name (printname) */ + name: string; + /** C entry point name */ + cEntry: string; + /** Offset passed to the C function */ + offset: number | string; + /** Eval flags */ + eval: number; + /** Arity (-1 = variadic) */ + arity: number; + /** Pretty-print kind */ + ppKind: PPKind; + /** Operator precedence */ + precedence: Precedence; + /** Right-associative flag */ + rightAssoc: 0 | 1; +} + +const entry = ( + name: string, + cEntry: string, + offset: number | string, + evalFlag: number, + arity: number, + ppKind: PPKind, + precedence: Precedence, + rightAssoc: 0 | 1 +): FunTabEntry => ({ name, cEntry, offset, eval: evalFlag, arity, ppKind, precedence, rightAssoc }); + +/** + * Used for information about built-in functions. Currently wip. + * @see {@link https://github.com/wch/r-source/blob/2196e6982a8f49082ee5c3d3521f6dd6596ea72c/src/main/names.c#L83-L1017 | R source: names.c} + */ +export const RFunTab: FunTabEntry[] = [ + /* -- Language Related Constructs -- */ + entry('if', 'do_if', 0, 200, -1, 'PP_IF', 'PREC_FN', 1), + entry('while', 'do_while', 0, 100, 2, 'PP_WHILE', 'PREC_FN', 0), + entry('for', 'do_for', 0, 100, 3, 'PP_FOR', 'PREC_FN', 0), + entry('repeat', 'do_repeat', 0, 100, 1, 'PP_REPEAT', 'PREC_FN', 0), + entry('break', 'do_break', 'CTXT_BREAK', 0, 0, 'PP_BREAK', 'PREC_FN', 0), + entry('next', 'do_break', 'CTXT_NEXT', 0, 0, 'PP_NEXT', 'PREC_FN', 0), + entry('return', 'do_return', 0, 0, -1, 'PP_RETURN', 'PREC_FN', 0), + entry('function', 'do_function', 0, 0, -1, 'PP_FUNCTION', 'PREC_FN', 0), + entry('<-', 'do_set', 1, 100, -1, 'PP_ASSIGN', 'PREC_LEFT', 1), + entry('=', 'do_set', 3, 100, -1, 'PP_ASSIGN', 'PREC_EQ', 1), + entry('<<-', 'do_set', 2, 100, -1, 'PP_ASSIGN2', 'PREC_LEFT', 1), + entry('{', 'do_begin', 0, 200, -1, 'PP_CURLY', 'PREC_FN', 0), + entry('(', 'do_paren', 0, 1, 1, 'PP_PAREN', 'PREC_FN', 0), + entry('.subset', 'do_subset_dflt', 1, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.subset2', 'do_subset2_dflt', 2, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('[', 'do_subset', 1, 0, -1, 'PP_SUBSET', 'PREC_SUBSET', 0), + entry('[[', 'do_subset2', 2, 0, -1, 'PP_SUBSET', 'PREC_SUBSET', 0), + entry('$', 'do_subset3', 3, 0, 2, 'PP_DOLLAR', 'PREC_DOLLAR', 0), + entry('@', 'do_AT', 0, 0, 2, 'PP_DOLLAR', 'PREC_DOLLAR', 0), + entry('[<-', 'do_subassign', 0, 0, 3, 'PP_SUBASS', 'PREC_LEFT', 1), + entry('[[<-', 'do_subassign2', 1, 0, 3, 'PP_SUBASS', 'PREC_LEFT', 1), + entry('$<-', 'do_subassign3', 1, 0, 3, 'PP_SUBASS', 'PREC_LEFT', 1), + entry('switch', 'do_switch', 0, 200, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('browser', 'do_browser', 0, 101, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.primTrace', 'do_trace', 0, 101, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.primUntrace', 'do_trace', 1, 101, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.Internal', 'do_internal', 0, 200, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.Primitive', 'do_primitive', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('call', 'do_call', 0, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('quote', 'do_quote', 0, 0, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('substitute', 'do_substitute', 0, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('missing', 'do_missing', 1, 0, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('nargs', 'do_nargs', 1, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('on.exit', 'do_onexit', 0, 100, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('forceAndCall', 'do_forceAndCall', 0, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('declare', 'do_declare', 0, 100, -1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- .Internals -- */ + entry('stop', 'do_stop', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('warning', 'do_warning', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gettext', 'do_gettext', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('ngettext', 'do_ngettext', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bindtextdomain', 'do_bindtextdomain', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.addCondHands', 'do_addCondHands', 0, 111, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.addGlobHands', 'do_addGlobHands', 0, 111, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.resetCondHands', 'do_resetCondHands', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.signalCondition', 'do_signalCondition', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.dfltStop', 'do_dfltStop', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.dfltWarn', 'do_dfltWarn', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.addRestart', 'do_addRestart', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.getRestart', 'do_getRestart', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.invokeRestart', 'do_invokeRestart', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.addTryHandlers', 'do_addTryHandlers', 0, 111, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('geterrmessage', 'do_geterrmessage', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('seterrmessage', 'do_seterrmessage', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('printDeferredWarnings', 'do_printDeferredWarnings', 0, 111, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('interruptsSuspended', 'do_interruptsSuspended', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.function.default', 'do_asfunction', 0, 11, 2, 'PP_FUNCTION', 'PREC_FN', 0), + entry('debug', 'do_debug', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('undebug', 'do_debug', 1, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isdebugged', 'do_debug', 2, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('debugonce', 'do_debug', 3, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Recall', 'do_recall', 0, 210, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('delayedAssign', 'do_delayed', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('makeLazy', 'do_makelazy', 0, 111, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('identical', 'do_identical', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('C_tryCatchHelper', 'do_tryCatchHelper', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getNamespaceValue', 'do_getNSValue', 0, 211, 3, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Binary Operators -- */ + entry('+', 'do_arith', 'PLUSOP', 1, -1, 'PP_BINARY', 'PREC_SUM', 0), + entry('-', 'do_arith', 'MINUSOP', 1, -1, 'PP_BINARY', 'PREC_SUM', 0), + entry('*', 'do_arith', 'TIMESOP', 1, 2, 'PP_BINARY', 'PREC_PROD', 0), + entry('/', 'do_arith', 'DIVOP', 1, 2, 'PP_BINARY2', 'PREC_PROD', 0), + entry('^', 'do_arith', 'POWOP', 1, 2, 'PP_BINARY2', 'PREC_POWER', 1), + entry('%%', 'do_arith', 'MODOP', 1, 2, 'PP_BINARY2', 'PREC_PERCENT', 0), + entry('%/%', 'do_arith', 'IDIVOP', 1, 2, 'PP_BINARY2', 'PREC_PERCENT', 0), + entry('%*%', 'do_matprod', 0, 1, 2, 'PP_BINARY', 'PREC_PERCENT', 0), + + entry('==', 'do_relop', 'EQOP', 1, 2, 'PP_BINARY', 'PREC_COMPARE', 0), + entry('!=', 'do_relop', 'NEOP', 1, 2, 'PP_BINARY', 'PREC_COMPARE', 0), + entry('<', 'do_relop', 'LTOP', 1, 2, 'PP_BINARY', 'PREC_COMPARE', 0), + entry('<=', 'do_relop', 'LEOP', 1, 2, 'PP_BINARY', 'PREC_COMPARE', 0), + entry('>=', 'do_relop', 'GEOP', 1, 2, 'PP_BINARY', 'PREC_COMPARE', 0), + entry('>', 'do_relop', 'GTOP', 1, 2, 'PP_BINARY', 'PREC_COMPARE', 0), + + entry('&', 'do_logic', 1, 1, 2, 'PP_BINARY', 'PREC_AND', 0), + entry('|', 'do_logic', 2, 1, 2, 'PP_BINARY', 'PREC_OR', 0), + entry('!', 'do_logic', 3, 1, 1, 'PP_UNARY', 'PREC_NOT', 0), + entry('&&', 'do_logic2', 1, 0, 2, 'PP_BINARY', 'PREC_AND', 0), + entry('||', 'do_logic2', 2, 0, 2, 'PP_BINARY', 'PREC_OR', 0), + entry(':', 'do_colon', 0, 1, 2, 'PP_BINARY2', 'PREC_COLON', 0), + entry('~', 'do_tilde', 0, 0, -1, 'PP_BINARY', 'PREC_TILDE', 0), + entry('::', 'do_colon2', 0, 200, 2, 'PP_BINARY2', 'PREC_NS', 0), + entry(':::', 'do_colon3', 0, 200, 2, 'PP_BINARY2', 'PREC_NS', 0), + + /* -- Logic -- */ + entry('all', 'do_logic3', 1, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('any', 'do_logic3', 2, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Vectors / Matrices / Arrays -- */ + entry('...elt', 'do_dotsElt', 0, 201, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('...length', 'do_dotsLength', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('...names', 'do_dotsNames', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('length', 'do_length', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('length<-', 'do_lengthgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('c', 'do_c', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('oldClass', 'do_class', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('oldClass<-', 'do_classgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('class', 'R_do_data_class', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.cache_class', 'R_do_data_class', 1, 1, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.class2', 'R_do_data_class', 2, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('class<-', 'R_do_set_class', 0, 1, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unclass', 'do_unclass', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('names', 'do_names', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('names<-', 'do_namesgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('dimnames', 'do_dimnames', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dimnames<-', 'do_dimnamesgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('dim', 'do_dim', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dim<-', 'do_dimgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('attributes', 'do_attributes', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('attributes<-', 'do_attributesgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('attr', 'do_attr', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('attr<-', 'do_attrgets', 0, 1, 3, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('@<-', 'do_attrgets', 1, 0, 3, 'PP_SUBASS', 'PREC_LEFT', 1), + entry('levels<-', 'do_levelsgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + + entry('vector', 'do_makevector', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('complex', 'do_complex', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('matrix', 'do_matrix', 0, 11, 7, 'PP_FUNCALL', 'PREC_FN', 0), + entry('array', 'do_array', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('diag', 'do_diag', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('backsolve', 'do_backsolve', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('max.col', 'do_maxcol', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('row', 'do_rowscols', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('col', 'do_rowscols', 2, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unlist', 'do_unlist', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cbind', 'do_bind', 1, 10, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rbind', 'do_bind', 2, 10, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('drop', 'do_drop', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('all.names', 'do_allnames', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('comment', 'do_comment', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('comment<-', 'do_commentgets', 0, 11, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('get', 'do_get', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('get0', 'do_get', 2, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mget', 'do_mget', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('exists', 'do_get', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('assign', 'do_assign', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('list2env', 'do_list2env', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('remove', 'do_remove', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('duplicated', 'do_duplicated', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unique', 'do_duplicated', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('anyDuplicated', 'do_duplicated', 2, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('anyNA', 'do_anyNA', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('which', 'do_which', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('which.min', 'do_first_min', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pmin', 'do_pmin', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pmax', 'do_pmin', 1, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('which.max', 'do_first_min', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('match', 'do_match', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pmatch', 'do_pmatch', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('charmatch', 'do_charmatch', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('match.call', 'do_matchcall', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('crossprod', 'do_matprod', 1, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tcrossprod', 'do_matprod', 2, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('asplit', 'do_asplit', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lengths', 'do_lengths', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sequence', 'do_sequence', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('vhash', 'do_vhash', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('attach', 'do_attach', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('detach', 'do_detach', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('search', 'do_search', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setFileTime', 'do_setFileTime', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Math (primitives) -- */ + entry('round', 'do_Math2', 10001, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('signif', 'do_Math2', 10004, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('log', 'do_log', 10003, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('log10', 'do_log1arg', 10010, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('log2', 'do_log1arg', 10002, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('abs', 'do_abs', 6, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('floor', 'do_math1', 1, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('ceiling', 'do_math1', 2, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sqrt', 'do_math1', 3, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sign', 'do_math1', 4, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('trunc', 'do_trunc', 5, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('exp', 'do_math1', 10, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('expm1', 'do_math1', 11, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('log1p', 'do_math1', 12, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cos', 'do_math1', 20, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sin', 'do_math1', 21, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tan', 'do_math1', 22, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('acos', 'do_math1', 23, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('asin', 'do_math1', 24, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('atan', 'do_math1', 25, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cosh', 'do_math1', 30, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sinh', 'do_math1', 31, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tanh', 'do_math1', 32, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('acosh', 'do_math1', 33, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('asinh', 'do_math1', 34, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('atanh', 'do_math1', 35, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lgamma', 'do_math1', 40, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gamma', 'do_math1', 41, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('digamma', 'do_math1', 42, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('trigamma', 'do_math1', 43, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cospi', 'do_math1', 47, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sinpi', 'do_math1', 48, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tanpi', 'do_math1', 49, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Math 2 -- */ + entry('atan2', 'do_math2', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lbeta', 'do_math2', 2, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('beta', 'do_math2', 3, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lchoose', 'do_math2', 4, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('choose', 'do_math2', 5, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dchisq', 'do_math2', 6, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pchisq', 'do_math2', 7, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qchisq', 'do_math2', 8, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dexp', 'do_math2', 9, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pexp', 'do_math2', 10, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qexp', 'do_math2', 11, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dgeom', 'do_math2', 12, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pgeom', 'do_math2', 13, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qgeom', 'do_math2', 14, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dpois', 'do_math2', 15, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('ppois', 'do_math2', 16, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qpois', 'do_math2', 17, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dt', 'do_math2', 18, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pt', 'do_math2', 19, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qt', 'do_math2', 20, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dsignrank', 'do_math2', 21, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('psignrank', 'do_math2', 22, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qsignrank', 'do_math2', 23, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('besselJ', 'do_math2', 24, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('besselY', 'do_math2', 25, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('psigamma', 'do_math2', 26, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Complex math -- */ + entry('Re', 'do_cmathfuns', 1, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Im', 'do_cmathfuns', 2, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Mod', 'do_cmathfuns', 3, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Arg', 'do_cmathfuns', 4, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Conj', 'do_cmathfuns', 5, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Math 3 -- */ + entry('dbeta', 'do_math3', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pbeta', 'do_math3', 2, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qbeta', 'do_math3', 3, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dbinom', 'do_math3', 4, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pbinom', 'do_math3', 5, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qbinom', 'do_math3', 6, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dcauchy', 'do_math3', 7, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pcauchy', 'do_math3', 8, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qcauchy', 'do_math3', 9, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('df', 'do_math3', 10, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pf', 'do_math3', 11, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qf', 'do_math3', 12, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dgamma', 'do_math3', 13, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pgamma', 'do_math3', 14, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qgamma', 'do_math3', 15, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dlnorm', 'do_math3', 16, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('plnorm', 'do_math3', 17, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qlnorm', 'do_math3', 18, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dlogis', 'do_math3', 19, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('plogis', 'do_math3', 20, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qlogis', 'do_math3', 21, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnbinom', 'do_math3', 22, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnbinom', 'do_math3', 23, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnbinom', 'do_math3', 24, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnorm', 'do_math3', 25, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnorm', 'do_math3', 26, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnorm', 'do_math3', 27, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dunif', 'do_math3', 28, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('punif', 'do_math3', 29, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qunif', 'do_math3', 30, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dweibull', 'do_math3', 31, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pweibull', 'do_math3', 32, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qweibull', 'do_math3', 33, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnchisq', 'do_math3', 34, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnchisq', 'do_math3', 35, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnchisq', 'do_math3', 36, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnt', 'do_math3', 37, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnt', 'do_math3', 38, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnt', 'do_math3', 39, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dwilcox', 'do_math3', 40, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pwilcox', 'do_math3', 41, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qwilcox', 'do_math3', 42, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('besselI', 'do_math3', 43, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('besselK', 'do_math3', 44, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnbinom_mu', 'do_math3', 45, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnbinom_mu', 'do_math3', 46, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnbinom_mu', 'do_math3', 47, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Math 4 -- */ + entry('dhyper', 'do_math4', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('phyper', 'do_math4', 2, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qhyper', 'do_math4', 3, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnbeta', 'do_math4', 4, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnbeta', 'do_math4', 5, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnbeta', 'do_math4', 6, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dnf', 'do_math4', 7, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pnf', 'do_math4', 8, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qnf', 'do_math4', 9, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dtukey', 'do_math4', 10, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('ptukey', 'do_math4', 11, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qtukey', 'do_math4', 12, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Random Numbers -- */ + entry('rchisq', 'do_random1', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rexp', 'do_random1', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rgeom', 'do_random1', 2, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rpois', 'do_random1', 3, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rt', 'do_random1', 4, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rsignrank', 'do_random1', 5, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rbeta', 'do_random2', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rbinom', 'do_random2', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rcauchy', 'do_random2', 2, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rf', 'do_random2', 3, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rgamma', 'do_random2', 4, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rlnorm', 'do_random2', 5, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rlogis', 'do_random2', 6, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rnbinom', 'do_random2', 7, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rnbinom_mu', 'do_random2', 13, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rnchisq', 'do_random2', 12, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rnorm', 'do_random2', 8, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('runif', 'do_random2', 9, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rweibull', 'do_random2', 10, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rwilcox', 'do_random2', 11, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rhyper', 'do_random3', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sample', 'do_sample', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sample2', 'do_sample2', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('RNGkind', 'do_RNGkind', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('set.seed', 'do_setseed', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Data Summaries -- */ + entry('sum', 'do_summary', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('min', 'do_summary', 2, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('max', 'do_summary', 3, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('prod', 'do_summary', 4, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mean', 'do_summary', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('range', 'do_range', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cumsum', 'do_cum', 1, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cumprod', 'do_cum', 2, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cummax', 'do_cum', 3, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cummin', 'do_cum', 4, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Type Coercion -- */ + entry('as.character', 'do_asatomic', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.integer', 'do_asatomic', 1, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.double', 'do_asatomic', 2, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.numeric', 'do_asatomic', 2, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.complex', 'do_asatomic', 3, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.logical', 'do_asatomic', 4, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.raw', 'do_asatomic', 5, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.call', 'do_ascall', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.environment', 'do_as_environment', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('storage.mode<-', 'do_storage_mode', 0, 1, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('asCharacterFactor', 'do_asCharacterFactor', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.vector', 'do_asvector', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('paste', 'do_paste', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('paste0', 'do_paste', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.path', 'do_filepath', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('format', 'do_format', 0, 11, 9, 'PP_FUNCALL', 'PREC_FN', 0), + entry('format.info', 'do_formatinfo', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('cat', 'do_cat', 0, 111, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('do.call', 'do_docall', 0, 211, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('str2lang', 'do_str2lang', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('str2expression', 'do_str2lang', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- String Manipulation -- */ + entry('nchar', 'do_nchar', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('nzchar', 'do_nzchar', 1, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('substr', 'do_substr', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('startsWith', 'do_startsWith', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('endsWith', 'do_startsWith', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('substr<-', 'do_substrgets', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('strsplit', 'do_strsplit', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('abbreviate', 'do_abbrev', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('make.names', 'do_makenames', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pcre_config', 'do_pcre_config', 1, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('grep', 'do_grep', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('grepl', 'do_grep', 1, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('grepRaw', 'do_grepraw', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sub', 'do_gsub', 0, 11, 7, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gsub', 'do_gsub', 1, 11, 7, 'PP_FUNCALL', 'PREC_FN', 0), + entry('regexpr', 'do_regexpr', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gregexpr', 'do_regexpr', 1, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('regexec', 'do_regexec', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('agrep', 'do_agrep', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('agrepl', 'do_agrep', 1, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('adist', 'do_adist', 1, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('aregexec', 'do_aregexec', 1, 11, 7, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tolower', 'do_tolower', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('toupper', 'do_tolower', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('chartr', 'do_chartr', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sprintf', 'do_sprintf', 1, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('make.unique', 'do_makeunique', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('charToRaw', 'do_charToRaw', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rawToChar', 'do_rawToChar', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rawShift', 'do_rawShift', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('intToBits', 'do_intToBits', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('numToBits', 'do_numToBits', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('numToInts', 'do_numToInts', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rawToBits', 'do_rawToBits', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('packBits', 'do_packBits', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('utf8ToInt', 'do_utf8ToInt', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('intToUtf8', 'do_intToUtf8', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('validUTF8', 'do_validUTF8', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('validEnc', 'do_validEnc', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('encodeString', 'do_encodeString', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('iconv', 'do_iconv', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('strtrim', 'do_strtrim', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('strtoi', 'do_strtoi', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('strrep', 'do_strrep', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Type Checking -- */ + entry('is.null', 'do_is', 'NILSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.logical', 'do_is', 'LGLSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.integer', 'do_is', 'INTSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.double', 'do_is', 'REALSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.complex', 'do_is', 'CPLXSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.character', 'do_is', 'STRSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.symbol', 'do_is', 'SYMSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.name', 'do_is', 'SYMSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.environment', 'do_is', 'ENVSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.list', 'do_is', 'VECSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.pairlist', 'do_is', 'LISTSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.expression', 'do_is', 'EXPRSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.raw', 'do_is', 'RAWSXP', 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.object', 'do_is', 50, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isS4', 'do_is', 51, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.numeric', 'do_is', 100, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.matrix', 'do_is', 101, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.array', 'do_is', 102, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.atomic', 'do_is', 200, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.recursive', 'do_is', 201, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.call', 'do_is', 300, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.language', 'do_is', 301, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.function', 'do_is', 302, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.single', 'do_is', 999, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.na', 'do_isna', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.nan', 'do_isnan', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.finite', 'do_isfinite', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.infinite', 'do_isinfinite', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.vector', 'do_isvector', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- Miscellaneous (primitives) -- */ + entry('proc.time', 'do_proctime', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gc.time', 'do_gctime', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('withVisible', 'do_withVisible', 1, 10, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('expression', 'do_expression', 1, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('interactive', 'do_interactive', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('invisible', 'do_invisible', 0, 101, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rep', 'do_rep', 0, 0, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rep.int', 'do_rep_int', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rep_len', 'do_rep_len', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('seq.int', 'do_seq', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('seq_len', 'do_seq_len', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('seq_along', 'do_seq_along', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('list', 'do_makelist', 1, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('xtfrm', 'do_xtfrm', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('enc2native', 'do_enc2', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('enc2utf8', 'do_enc2', 1, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('emptyenv', 'do_emptyenv', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('baseenv', 'do_baseenv', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('globalenv', 'do_globalenv', 0, 1, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('environment<-', 'do_envirgets', 0, 1, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('pos.to.env', 'do_pos2env', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Foreign interface */ + entry('.C', 'do_dotCode', 0, 1, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('.Fortran', 'do_dotCode', 1, 1, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('.External', 'do_External', 0, 1, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('.External2', 'do_External', 1, 201, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('.Call', 'do_dotcall', 0, 1, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('.External.graphics', 'do_Externalgr', 0, 1, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('.Call.graphics', 'do_dotcallgr', 0, 1, -1, 'PP_FOREIGN', 'PREC_FN', 0), + + /* .Internal apply family */ + entry('eapply', 'do_eapply', 0, 10, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lapply', 'do_lapply', 0, 10, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('vapply', 'do_vapply', 0, 10, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mapply', 'do_mapply', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + + /* System info */ + entry('Version', 'do_version', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('machine', 'do_machine', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('commandArgs', 'do_commandArgs', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('internalsID', 'do_internalsID', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('system', 'do_system', 0, 211, 4, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Parse / deparse / IO */ + entry('parse', 'do_parse', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('save', 'do_save', 0, 111, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('saveToConn', 'do_saveToConn', 0, 111, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('load', 'do_load', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('loadFromConn2', 'do_loadFromConn2', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('loadInfoFromConn2', 'do_loadFromConn2', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('serializeToConn', 'do_serializeToConn', 0, 111, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unserializeFromConn', 'do_unserializeFromConn', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('serializeInfoFromConn', 'do_unserializeFromConn', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('deparse', 'do_deparse', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dput', 'do_dput', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dump', 'do_dump', 0, 111, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('quit', 'do_quit', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('readline', 'do_readln', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('print.default', 'do_printdefault', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('prmatrix', 'do_prmatrix', 0, 111, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gc', 'do_gc', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gcinfo', 'do_gcinfo', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gctorture', 'do_gctorture', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gctorture2', 'do_gctorture2', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('memory.profile', 'do_memoryprofile', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mem.maxVSize', 'do_maxVSize', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mem.maxNSize', 'do_maxNSize', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('split', 'do_split', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.loaded', 'do_isloaded', 0, 11, -1, 'PP_FOREIGN', 'PREC_FN', 0), + entry('recordGraphics', 'do_recordGraphics', 0, 211, 3, 'PP_FOREIGN', 'PREC_FN', 0), + entry('dyn.load', 'do_dynload', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dyn.unload', 'do_dynunload', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('ls', 'do_ls', 1, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('typeof', 'do_typeof', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('eval', 'do_eval', 0, 211, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Exec', 'do_tailcall', 0, 200, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Tailcall', 'do_tailcall', 1, 200, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('returnValue', 'do_returnValue', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.parent', 'do_sys', 1, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.call', 'do_sys', 2, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.frame', 'do_sys', 3, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.nframe', 'do_sys', 4, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.calls', 'do_sys', 5, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.frames', 'do_sys', 6, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.on.exit', 'do_sys', 7, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.parents', 'do_sys', 8, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sys.function', 'do_sys', 9, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('traceback', 'do_traceback', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('browserText', 'do_sysbrowser', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('browserCondition', 'do_sysbrowser', 2, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('browserSetDebug', 'do_sysbrowser', 3, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('parent.frame', 'do_parentframe', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sort', 'do_sort', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.unsorted', 'do_isunsorted', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sorted_fpass', 'do_sorted_fpass', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('psort', 'do_psort', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qsort', 'do_qsort', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('radixsort', 'do_radixsort', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('order', 'do_order', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rank', 'do_rank', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('scan', 'do_scan', 0, 11, 19, 'PP_FUNCALL', 'PREC_FN', 0), + entry('t.default', 'do_transpose', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('aperm', 'do_aperm', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('builtins', 'do_builtins', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('args', 'do_args', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('formals', 'do_formals', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('body', 'do_body', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bodyCode', 'do_bodyCode', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('environment', 'do_envir', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('environmentName', 'do_envirName', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('env2list', 'do_env2list', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('reg.finalizer', 'do_regFinaliz', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('options', 'do_options', 0, 211, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getOption', 'do_getOption', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sink', 'do_sink', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sink.number', 'do_sinknumber', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rapply', 'do_rapply', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('islistfactor', 'do_islistfactor', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('colSums', 'do_colsum', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('colMeans', 'do_colsum', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rowSums', 'do_colsum', 2, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rowMeans', 'do_colsum', 3, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tracemem', 'do_tracemem', 0, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('retracemem', 'do_retracemem', 0, 201, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('untracemem', 'do_untracemem', 0, 101, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('inspect', 'do_inspect', 0, 111, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('address', 'do_address', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('named', 'do_named', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('refcnt', 'do_refcnt', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('merge', 'do_merge', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('capabilities', 'do_capabilities', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('capabilitiesX11', 'do_capabilitiesX11', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('new.env', 'do_newenv', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('parent.env', 'do_parentenv', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('parent.env<-', 'do_parentenvgets', 0, 11, 2, 'PP_FUNCALL', 'PREC_LEFT', 1), + entry('topenv', 'do_topenv', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('l10n_info', 'do_l10n_info', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Cstack_info', 'do_Cstack_info', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mmap_file', 'do_mmap_file', 0, 11, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('munmap_file', 'do_munmap_file', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('wrap_meta', 'do_wrap_meta', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tryWrap', 'do_tryWrap', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('altrep_class', 'do_altrep_class', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* -- OS interaction -- */ + entry('file.show', 'do_fileshow', 0, 111, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.create', 'do_filecreate', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.remove', 'do_fileremove', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.rename', 'do_filerename', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.append', 'do_fileappend', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.symlink', 'do_filesymlink', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.link', 'do_filelink', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.copy', 'do_filecopy', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('list.files', 'do_listfiles', 0, 11, 9, 'PP_FUNCALL', 'PREC_FN', 0), + entry('list.dirs', 'do_listdirs', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.exists', 'do_fileexists', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.choose', 'do_filechoose', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.info', 'do_fileinfo', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file.access', 'do_fileaccess', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dir.exists', 'do_direxists', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dir.create', 'do_dircreate', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tempfile', 'do_tempfile', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tempdir', 'do_tempdir', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('R.home', 'do_Rhome', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('date', 'do_date', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.getenv', 'do_getenv', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.setenv', 'do_setenv', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.unsetenv', 'do_unsetenv', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getwd', 'do_getwd', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setwd', 'do_setwd', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('basename', 'do_basename', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('dirname', 'do_dirname', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.chmod', 'do_syschmod', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.umask', 'do_sysumask', 0, 211, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.readlink', 'do_readlink', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.info', 'do_sysinfo', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.sleep', 'do_syssleep', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.getlocale', 'do_getlocale', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.setlocale', 'do_setlocale', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.localeconv', 'do_localeconv', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('path.expand', 'do_pathexpand', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.getpid', 'do_sysgetpid', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('normalizePath', 'do_normalizepath', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Sys.glob', 'do_glob', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unlink', 'do_unlink', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Complex */ + entry('polyroot', 'do_polyroot', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Objects / S3 */ + entry('inherits', 'do_inherits', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('UseMethod', 'do_usemethod', 0, 200, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('NextMethod', 'do_nextmethod', 0, 210, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('standardGeneric', 'do_standardGeneric', 0, 201, -1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Date-time */ + entry('Sys.time', 'do_systime', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.POSIXct', 'do_asPOSIXct', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('as.POSIXlt', 'do_asPOSIXlt', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('format.POSIXlt', 'do_formatPOSIXlt', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('strptime', 'do_strptime', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('Date2POSIXlt', 'do_D2POSIXlt', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('POSIXlt2Date', 'do_POSIXlt2D', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('balancePOSIXlt', 'do_balancePOSIXlt', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unCfillPOSIXlt', 'do_balancePOSIXlt', 1, 1, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('compareNumericVersion', 'do_compareNumericVersion', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Bytecode compiler */ + entry('mkCode', 'do_mkcode', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bcClose', 'do_bcclose', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('is.builtin.internal', 'do_is_builtin_internal', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('disassemble', 'do_disassemble', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bcVersion', 'do_bcversion', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('load.from.file', 'do_loadfile', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('save.to.file', 'do_savefile', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('growconst', 'do_growconst', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('putconst', 'do_putconst', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getconst', 'do_getconst', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('enableJIT', 'do_enablejit', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('compilePKGS', 'do_compilepkgs', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setNumMathThreads', 'do_setnumthreads', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setMaxNumMathThreads', 'do_setmaxnumthreads', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Connections */ + entry('stdin', 'do_stdin', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('stdout', 'do_stdout', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('stderr', 'do_stderr', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isatty', 'do_isatty', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('readLines', 'do_readLines', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('writeLines', 'do_writelines', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('readBin', 'do_readbin', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('writeBin', 'do_writebin', 0, 211, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('readChar', 'do_readchar', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('writeChar', 'do_writechar', 0, 211, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('open', 'do_open', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isOpen', 'do_isopen', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isIncomplete', 'do_isincomplete', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isSeekable', 'do_isseekable', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('close', 'do_close', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('flush', 'do_flush', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('file', 'do_url', 1, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('url', 'do_url', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pipe', 'do_pipe', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('fifo', 'do_fifo', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gzfile', 'do_gzfile', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bzfile', 'do_gzfile', 1, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('xzfile', 'do_gzfile', 2, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('zstdfile', 'do_gzfile', 3, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unz', 'do_unz', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('seek', 'do_seek', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('truncate', 'do_truncate', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pushBack', 'do_pushback', 0, 111, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('clearPushBack', 'do_clearpushback', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pushBackLength', 'do_pushbacklength', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rawConnection', 'do_rawconnection', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rawConnectionValue', 'do_rawconvalue', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('textConnection', 'do_textconnection', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('textConnectionValue', 'do_textconvalue', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('socketConnection', 'do_sockconn', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('socketAccept', 'do_sockconn', 1, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('sockSelect', 'do_sockselect', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('serverSocket', 'do_serversocket', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('socketTimeout', 'do_socktimeout', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getConnection', 'do_getconnection', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getAllConnections', 'do_getallconnections', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('summary.connection', 'do_sumconnection', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('gzcon', 'do_gzcon', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('memCompress', 'do_memCompress', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('memDecompress', 'do_memDecompress', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('readDCF', 'do_readDCF', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Namespace / environment locking */ + entry('lockEnvironment', 'do_lockEnv', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('environmentIsLocked', 'do_envIsLocked', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lockBinding', 'do_lockBnd', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unlockBinding', 'do_lockBnd', 1, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bindingIsLocked', 'do_bndIsLocked', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('makeActiveBinding', 'do_mkActiveBnd', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bindingIsActive', 'do_bndIsActive', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('activeBindingFunction', 'do_activeBndFun', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('mkUnbound', 'do_mkUnbound', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isNamespaceEnv', 'do_isNSEnv', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('registerNamespace', 'do_regNS', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unregisterNamespace', 'do_unregNS', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getRegisteredNamespace', 'do_getRegNS', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('isRegisteredNamespace', 'do_getRegNS', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getNamespaceRegistry', 'do_getNSRegistry', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('importIntoEnv', 'do_importIntoEnv', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('env.profile', 'do_envprofile', 0, 211, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Misc internals */ + entry('Encoding', 'do_encoding', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setEncoding', 'do_setencoding', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setTimeLimit', 'do_setTimeLimit', 0, 111, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setSessionTimeLimit', 'do_setSessionTimeLimit', 0, 111, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('icuSetCollate', 'do_ICUset', 0, 111, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('icuGetCollate', 'do_ICUget', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('readRenviron', 'do_readEnviron', 0, 111, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('shortRowNames', 'do_shortRowNames', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('copyDFattr', 'do_copyDFattr', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getRegisteredRoutines', 'do_getRegisteredRoutines', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getLoadedDLLs', 'do_getDllTable', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getSymbolInfo', 'do_getSymbolInfo', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('.isMethodsDispatchOn', 'do_S4on', 0, 1, -1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lazyLoadDBfetch', 'do_lazyLoadDBfetch', 0, 1, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lazyLoadDBflush', 'do_lazyLoadDBflush', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('getVarsFromFrame', 'do_getVarsFromFrame', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('lazyLoadDBinsertValue', 'do_lazyLoadDBinsertValue', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bincode', 'do_bincode', 0, 11, 4, 'PP_FUNCALL', 'PREC_FN', 0), + entry('tabulate', 'do_tabulate', 0, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('findInterval', 'do_findinterval', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('pretty', 'do_pretty', 0, 11, 8, 'PP_FUNCALL', 'PREC_FN', 0), + entry('formatC', 'do_formatC', 0, 11, 7, 'PP_FUNCALL', 'PREC_FN', 0), + entry('crc64', 'do_crc64', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bitwiseAnd', 'do_bitwise', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bitwiseNot', 'do_bitwise', 2, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bitwiseOr', 'do_bitwise', 3, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bitwiseXor', 'do_bitwise', 4, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bitwiseShiftL', 'do_bitwise', 5, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bitwiseShiftR', 'do_bitwise', 6, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('serialize', 'do_serialize', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('serializeb', 'do_serialize', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('unserialize', 'do_serialize', 2, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rowsum_matrix', 'do_rowsum', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('rowsum_df', 'do_rowsum', 1, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('setS4Object', 'do_setS4Object', 0, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('traceOnOff', 'do_traceOnOff', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('debugOnOff', 'do_traceOnOff', 1, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + + /* LAPACK */ + entry('La_qr_cmplx', 'do_lapack', 0, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_rs', 'do_lapack', 1, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_rs_cmplx', 'do_lapack', 2, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_rg', 'do_lapack', 3, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_rg_cmplx', 'do_lapack', 41, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_dlange', 'do_lapack', 6, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_dgecon', 'do_lapack', 7, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_dtrcon', 'do_lapack', 8, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_dtrcon3', 'do_lapack', 81, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_zlange', 'do_lapack', 61, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_zgecon', 'do_lapack', 9, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_ztrcon', 'do_lapack', 10, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_ztrcon3', 'do_lapack', 13, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_solve_cmplx', 'do_lapack', 11, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_solve', 'do_lapack', 100, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_qr', 'do_lapack', 101, 11, 1, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_chol', 'do_lapack', 200, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_chol2inv', 'do_lapack', 201, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qr_coef_real', 'do_lapack', 300, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qr_qy_real', 'do_lapack', 301, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('det_ge_real', 'do_lapack', 302, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qr_coef_cmplx', 'do_lapack', 303, 11, 2, 'PP_FUNCALL', 'PREC_FN', 0), + entry('qr_qy_cmplx', 'do_lapack', 304, 11, 3, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_svd', 'do_lapack', 400, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_svd_cmplx', 'do_lapack', 401, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_version', 'do_lapack', 1000, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('La_library', 'do_lapack', 1001, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + + /* Bytecode profiling */ + entry('bcprofcounts', 'do_bcprofcounts', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bcprofstart', 'do_bcprofstart', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('bcprofstop', 'do_bcprofstop', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + + /* curl / network */ + entry('eSoftVersion', 'do_eSoftVersion', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('curlVersion', 'do_curlVersion', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), + entry('curlGetHeaders', 'do_curlGetHeaders', 0, 11, 5, 'PP_FUNCALL', 'PREC_FN', 0), + entry('curlDownload', 'do_curlDownload', 0, 11, 6, 'PP_FUNCALL', 'PREC_FN', 0), + entry('compilerVersion', 'do_compilerVersion', 0, 11, 0, 'PP_FUNCALL', 'PREC_FN', 0), +]; + +export const RFunTabOffsets = { + 'if': 0, + 'while': 0, + 'for': 0, + 'repeat': 0, + 'break': 'CTXT_BREAK', + 'next': 'CTXT_NEXT', + 'return': 0, + 'function': 0, + '<-': 1, + '=': 3, + '<<-': 2, + '{': 0, + '(': 0, + '.subset': 1, + '.subset2': 2, + '[': 1, + '[[': 2, + '$': 3, + '@': 0, + '[<-': 0, + '[[<-': 1, + '$<-': 1, + 'switch': 0, + 'browser': 0, + '.primTrace': 0, + '.primUntrace': 1, + '.Internal': 0, + '.Primitive': 0, + 'call': 0, + 'quote': 0, + 'substitute': 0, + 'missing': 1, + 'nargs': 1, + 'on.exit': 0, + 'forceAndCall': 0, + 'declare': 0, + 'stop': 0, + 'warning': 0, + 'gettext': 0, + 'ngettext': 0, + 'bindtextdomain': 0, + '.addCondHands': 0, + '.addGlobHands': 0, + '.resetCondHands': 0, + '.signalCondition': 0, + '.dfltStop': 0, + '.dfltWarn': 0, + '.addRestart': 0, + '.getRestart': 0, + '.invokeRestart': 0, + '.addTryHandlers': 0, + 'geterrmessage': 0, + 'seterrmessage': 0, + 'printDeferredWarnings': 0, + 'interruptsSuspended': 0, + 'as.function.default': 0, + 'debug': 0, + 'undebug': 1, + 'isdebugged': 2, + 'debugonce': 3, + 'Recall': 0, + 'delayedAssign': 0, + 'makeLazy': 0, + 'identical': 0, + 'C_tryCatchHelper': 0, + 'getNamespaceValue': 0, + '+': 'PLUSOP', + '-': 'MINUSOP', + '*': 'TIMESOP', + '/': 'DIVOP', + '^': 'POWOP', + '%%': 'MODOP', + '%/%': 'IDIVOP', + '%*%': 0, + '==': 'EQOP', + '!=': 'NEOP', + '<': 'LTOP', + '<=': 'LEOP', + '>=': 'GEOP', + '>': 'GTOP', + '&': 1, + '|': 2, + '!': 3, + '&&': 1, + '||': 2, + ':': 0, + '~': 0, + '::': 0, + ':::': 0, + 'all': 1, + 'any': 2, + '...elt': 0, + '...length': 0, + '...names': 0, + 'length': 0, + 'length<-': 0, + 'c': 0, + 'oldClass': 0, + 'oldClass<-': 0, + 'class': 0, + '.cache_class': 1, + '.class2': 2, + 'class<-': 0, + 'unclass': 0, + 'names': 0, + 'names<-': 0, + 'dimnames': 0, + 'dimnames<-': 0, + 'dim': 0, + 'dim<-': 0, + 'attributes': 0, + 'attributes<-': 0, + 'attr': 0, + 'attr<-': 0, + '@<-': 1, + 'levels<-': 0, + 'vector': 0, + 'complex': 0, + 'matrix': 0, + 'array': 0, + 'diag': 0, + 'backsolve': 0, + 'max.col': 0, + 'row': 1, + 'col': 2, + 'unlist': 0, + 'cbind': 1, + 'rbind': 2, + 'drop': 0, + 'all.names': 0, + 'comment': 0, + 'comment<-': 0, + 'get': 1, + 'get0': 2, + 'mget': 1, + 'exists': 0, + 'assign': 0, + 'list2env': 0, + 'remove': 0, + 'duplicated': 0, + 'unique': 1, + 'anyDuplicated': 2, + 'anyNA': 0, + 'which': 0, + 'which.min': 0, + 'pmin': 0, + 'pmax': 1, + 'which.max': 1, + 'match': 0, + 'pmatch': 0, + 'charmatch': 0, + 'match.call': 0, + 'crossprod': 1, + 'tcrossprod': 2, + 'asplit': 0, + 'lengths': 0, + 'sequence': 0, + 'vhash': 0, + 'attach': 0, + 'detach': 0, + 'search': 0, + 'setFileTime': 0, + 'round': 10001, + 'signif': 10004, + 'log': 10003, + 'log10': 10010, + 'log2': 10002, + 'abs': 6, + 'floor': 1, + 'ceiling': 2, + 'sqrt': 3, + 'sign': 4, + 'trunc': 5, + 'exp': 10, + 'expm1': 11, + 'log1p': 12, + 'cos': 20, + 'sin': 21, + 'tan': 22, + 'acos': 23, + 'asin': 24, + 'atan': 25, + 'cosh': 30, + 'sinh': 31, + 'tanh': 32, + 'acosh': 33, + 'asinh': 34, + 'atanh': 35, + 'lgamma': 40, + 'gamma': 41, + 'digamma': 42, + 'trigamma': 43, + 'cospi': 47, + 'sinpi': 48, + 'tanpi': 49, + 'atan2': 0, + 'lbeta': 2, + 'beta': 3, + 'lchoose': 4, + 'choose': 5, + 'dchisq': 6, + 'pchisq': 7, + 'qchisq': 8, + 'dexp': 9, + 'pexp': 10, + 'qexp': 11, + 'dgeom': 12, + 'pgeom': 13, + 'qgeom': 14, + 'dpois': 15, + 'ppois': 16, + 'qpois': 17, + 'dt': 18, + 'pt': 19, + 'qt': 20, + 'dsignrank': 21, + 'psignrank': 22, + 'qsignrank': 23, + 'besselJ': 24, + 'besselY': 25, + 'psigamma': 26, + 'Re': 1, + 'Im': 2, + 'Mod': 3, + 'Arg': 4, + 'Conj': 5, + 'dbeta': 1, + 'pbeta': 2, + 'qbeta': 3, + 'dbinom': 4, + 'pbinom': 5, + 'qbinom': 6, + 'dcauchy': 7, + 'pcauchy': 8, + 'qcauchy': 9, + 'df': 10, + 'pf': 11, + 'qf': 12, + 'dgamma': 13, + 'pgamma': 14, + 'qgamma': 15, + 'dlnorm': 16, + 'plnorm': 17, + 'qlnorm': 18, + 'dlogis': 19, + 'plogis': 20, + 'qlogis': 21, + 'dnbinom': 22, + 'pnbinom': 23, + 'qnbinom': 24, + 'dnorm': 25, + 'pnorm': 26, + 'qnorm': 27, + 'dunif': 28, + 'punif': 29, + 'qunif': 30, + 'dweibull': 31, + 'pweibull': 32, + 'qweibull': 33, + 'dnchisq': 34, + 'pnchisq': 35, + 'qnchisq': 36, + 'dnt': 37, + 'pnt': 38, + 'qnt': 39, + 'dwilcox': 40, + 'pwilcox': 41, + 'qwilcox': 42, + 'besselI': 43, + 'besselK': 44, + 'dnbinom_mu': 45, + 'pnbinom_mu': 46, + 'qnbinom_mu': 47, + 'dhyper': 1, + 'phyper': 2, + 'qhyper': 3, + 'dnbeta': 4, + 'pnbeta': 5, + 'qnbeta': 6, + 'dnf': 7, + 'pnf': 8, + 'qnf': 9, + 'dtukey': 10, + 'ptukey': 11, + 'qtukey': 12, + 'rchisq': 0, + 'rexp': 1, + 'rgeom': 2, + 'rpois': 3, + 'rt': 4, + 'rsignrank': 5, + 'rbeta': 0, + 'rbinom': 1, + 'rcauchy': 2, + 'rf': 3, + 'rgamma': 4, + 'rlnorm': 5, + 'rlogis': 6, + 'rnbinom': 7, + 'rnbinom_mu': 13, + 'rnchisq': 12, + 'rnorm': 8, + 'runif': 9, + 'rweibull': 10, + 'rwilcox': 11, + 'rhyper': 0, + 'sample': 0, + 'sample2': 0, + 'RNGkind': 0, + 'set.seed': 0, + 'sum': 0, + 'min': 2, + 'max': 3, + 'prod': 4, + 'mean': 1, + 'range': 0, + 'cumsum': 1, + 'cumprod': 2, + 'cummax': 3, + 'cummin': 4, + 'as.character': 0, + 'as.integer': 1, + 'as.double': 2, + 'as.numeric': 2, + 'as.complex': 3, + 'as.logical': 4, + 'as.raw': 5, + 'as.call': 0, + 'as.environment': 0, + 'storage.mode<-': 0, + 'asCharacterFactor': 0, + 'as.vector': 0, + 'paste': 0, + 'paste0': 1, + 'file.path': 0, + 'format': 0, + 'format.info': 0, + 'cat': 0, + 'do.call': 0, + 'str2lang': 0, + 'str2expression': 1, + 'nchar': 1, + 'nzchar': 1, + 'substr': 1, + 'startsWith': 0, + 'endsWith': 1, + 'substr<-': 1, + 'strsplit': 1, + 'abbreviate': 1, + 'make.names': 0, + 'pcre_config': 1, + 'grep': 0, + 'grepl': 1, + 'grepRaw': 0, + 'sub': 0, + 'gsub': 1, + 'regexpr': 0, + 'gregexpr': 1, + 'regexec': 1, + 'agrep': 0, + 'agrepl': 1, + 'adist': 1, + 'aregexec': 1, + 'tolower': 0, + 'toupper': 1, + 'chartr': 1, + 'sprintf': 1, + 'make.unique': 0, + 'charToRaw': 1, + 'rawToChar': 1, + 'rawShift': 1, + 'intToBits': 1, + 'numToBits': 1, + 'numToInts': 1, + 'rawToBits': 1, + 'packBits': 1, + 'utf8ToInt': 1, + 'intToUtf8': 1, + 'validUTF8': 1, + 'validEnc': 1, + 'encodeString': 1, + 'iconv': 0, + 'strtrim': 0, + 'strtoi': 0, + 'strrep': 0, + 'is.null': 'NILSXP', + 'is.logical': 'LGLSXP', + 'is.integer': 'INTSXP', + 'is.double': 'REALSXP', + 'is.complex': 'CPLXSXP', + 'is.character': 'STRSXP', + 'is.symbol': 'SYMSXP', + 'is.name': 'SYMSXP', + 'is.environment': 'ENVSXP', + 'is.list': 'VECSXP', + 'is.pairlist': 'LISTSXP', + 'is.expression': 'EXPRSXP', + 'is.raw': 'RAWSXP', + 'is.object': 50, + 'isS4': 51, + 'is.numeric': 100, + 'is.matrix': 101, + 'is.array': 102, + 'is.atomic': 200, + 'is.recursive': 201, + 'is.call': 300, + 'is.language': 301, + 'is.function': 302, + 'is.single': 999, + 'is.na': 0, + 'is.nan': 0, + 'is.finite': 0, + 'is.infinite': 0, + 'is.vector': 0, + 'proc.time': 0, + 'gc.time': 0, + 'withVisible': 1, + 'expression': 1, + 'interactive': 0, + 'invisible': 0, + 'rep': 0, + 'rep.int': 0, + 'rep_len': 0, + 'seq.int': 0, + 'seq_len': 0, + 'seq_along': 0, + 'list': 1, + 'xtfrm': 0, + 'enc2native': 0, + 'enc2utf8': 1, + 'emptyenv': 0, + 'baseenv': 0, + 'globalenv': 0, + 'environment<-': 0, + 'pos.to.env': 0, + '.C': 0, + '.Fortran': 1, + '.External': 0, + '.External2': 1, + '.Call': 0, + '.External.graphics': 0, + '.Call.graphics': 0, + 'eapply': 0, + 'lapply': 0, + 'vapply': 0, + 'mapply': 0, + 'Version': 0, + 'machine': 0, + 'commandArgs': 0, + 'internalsID': 0, + 'system': 0, + 'parse': 0, + 'save': 0, + 'saveToConn': 0, + 'load': 0, + 'loadFromConn2': 0, + 'loadInfoFromConn2': 1, + 'serializeToConn': 0, + 'unserializeFromConn': 0, + 'serializeInfoFromConn': 1, + 'deparse': 0, + 'dput': 0, + 'dump': 0, + 'quit': 0, + 'readline': 0, + 'print.default': 0, + 'prmatrix': 0, + 'gc': 0, + 'gcinfo': 0, + 'gctorture': 0, + 'gctorture2': 0, + 'memory.profile': 0, + 'mem.maxVSize': 0, + 'mem.maxNSize': 0, + 'split': 0, + 'is.loaded': 0, + 'recordGraphics': 0, + 'dyn.load': 0, + 'dyn.unload': 0, + 'ls': 1, + 'typeof': 1, + 'eval': 0, + 'Exec': 0, + 'Tailcall': 1, + 'returnValue': 0, + 'sys.parent': 1, + 'sys.call': 2, + 'sys.frame': 3, + 'sys.nframe': 4, + 'sys.calls': 5, + 'sys.frames': 6, + 'sys.on.exit': 7, + 'sys.parents': 8, + 'sys.function': 9, + 'traceback': 0, + 'browserText': 1, + 'browserCondition': 2, + 'browserSetDebug': 3, + 'parent.frame': 0, + 'sort': 1, + 'is.unsorted': 0, + 'sorted_fpass': 0, + 'psort': 0, + 'qsort': 0, + 'radixsort': 0, + 'order': 0, + 'rank': 0, + 'scan': 0, + 't.default': 0, + 'aperm': 0, + 'builtins': 0, + 'args': 0, + 'formals': 0, + 'body': 0, + 'bodyCode': 0, + 'environment': 0, + 'environmentName': 0, + 'env2list': 0, + 'reg.finalizer': 0, + 'options': 0, + 'getOption': 0, + 'sink': 0, + 'sink.number': 0, + 'rapply': 0, + 'islistfactor': 0, + 'colSums': 0, + 'colMeans': 1, + 'rowSums': 2, + 'rowMeans': 3, + 'tracemem': 0, + 'retracemem': 0, + 'untracemem': 0, + 'inspect': 0, + 'address': 0, + 'named': 0, + 'refcnt': 0, + 'merge': 0, + 'capabilities': 0, + 'capabilitiesX11': 0, + 'new.env': 0, + 'parent.env': 0, + 'parent.env<-': 0, + 'topenv': 0, + 'l10n_info': 0, + 'Cstack_info': 0, + 'mmap_file': 0, + 'munmap_file': 0, + 'wrap_meta': 0, + 'tryWrap': 0, + 'altrep_class': 0, + 'file.show': 0, + 'file.create': 0, + 'file.remove': 0, + 'file.rename': 0, + 'file.append': 0, + 'file.symlink': 0, + 'file.link': 0, + 'file.copy': 0, + 'list.files': 0, + 'list.dirs': 0, + 'file.exists': 0, + 'file.choose': 0, + 'file.info': 0, + 'file.access': 0, + 'dir.exists': 0, + 'dir.create': 0, + 'tempfile': 0, + 'tempdir': 0, + 'R.home': 0, + 'date': 0, + 'Sys.getenv': 0, + 'Sys.setenv': 0, + 'Sys.unsetenv': 0, + 'getwd': 0, + 'setwd': 0, + 'basename': 0, + 'dirname': 0, + 'Sys.chmod': 0, + 'Sys.umask': 0, + 'Sys.readlink': 0, + 'Sys.info': 0, + 'Sys.sleep': 0, + 'Sys.getlocale': 0, + 'Sys.setlocale': 0, + 'Sys.localeconv': 0, + 'path.expand': 0, + 'Sys.getpid': 0, + 'normalizePath': 0, + 'Sys.glob': 0, + 'unlink': 0, + 'polyroot': 0, + 'inherits': 0, + 'UseMethod': 0, + 'NextMethod': 0, + 'standardGeneric': 0, + 'Sys.time': 0, + 'as.POSIXct': 0, + 'as.POSIXlt': 0, + 'format.POSIXlt': 0, + 'strptime': 0, + 'Date2POSIXlt': 0, + 'POSIXlt2Date': 0, + 'balancePOSIXlt': 0, + 'unCfillPOSIXlt': 1, + 'compareNumericVersion': 0, + 'mkCode': 0, + 'bcClose': 0, + 'is.builtin.internal': 0, + 'disassemble': 0, + 'bcVersion': 0, + 'load.from.file': 0, + 'save.to.file': 0, + 'growconst': 0, + 'putconst': 0, + 'getconst': 0, + 'enableJIT': 0, + 'compilePKGS': 0, + 'setNumMathThreads': 0, + 'setMaxNumMathThreads': 0, + 'stdin': 0, + 'stdout': 0, + 'stderr': 0, + 'isatty': 0, + 'readLines': 0, + 'writeLines': 0, + 'readBin': 0, + 'writeBin': 0, + 'readChar': 0, + 'writeChar': 0, + 'open': 0, + 'isOpen': 0, + 'isIncomplete': 0, + 'isSeekable': 0, + 'close': 0, + 'flush': 0, + 'file': 1, + 'url': 0, + 'pipe': 0, + 'fifo': 0, + 'gzfile': 0, + 'bzfile': 1, + 'xzfile': 2, + 'zstdfile': 3, + 'unz': 0, + 'seek': 0, + 'truncate': 0, + 'pushBack': 0, + 'clearPushBack': 0, + 'pushBackLength': 0, + 'rawConnection': 0, + 'rawConnectionValue': 0, + 'textConnection': 0, + 'textConnectionValue': 0, + 'socketConnection': 0, + 'socketAccept': 1, + 'sockSelect': 0, + 'serverSocket': 0, + 'socketTimeout': 0, + 'getConnection': 0, + 'getAllConnections': 0, + 'summary.connection': 0, + 'gzcon': 0, + 'memCompress': 0, + 'memDecompress': 0, + 'readDCF': 0, + 'lockEnvironment': 0, + 'environmentIsLocked': 0, + 'lockBinding': 0, + 'unlockBinding': 1, + 'bindingIsLocked': 0, + 'makeActiveBinding': 0, + 'bindingIsActive': 0, + 'activeBindingFunction': 0, + 'mkUnbound': 0, + 'isNamespaceEnv': 0, + 'registerNamespace': 0, + 'unregisterNamespace': 0, + 'getRegisteredNamespace': 0, + 'isRegisteredNamespace': 1, + 'getNamespaceRegistry': 0, + 'importIntoEnv': 0, + 'env.profile': 0, + 'Encoding': 0, + 'setEncoding': 0, + 'setTimeLimit': 0, + 'setSessionTimeLimit': 0, + 'icuSetCollate': 0, + 'icuGetCollate': 0, + 'readRenviron': 0, + 'shortRowNames': 0, + 'copyDFattr': 0, + 'getRegisteredRoutines': 0, + 'getLoadedDLLs': 0, + 'getSymbolInfo': 0, + '.isMethodsDispatchOn': 0, + 'lazyLoadDBfetch': 0, + 'lazyLoadDBflush': 0, + 'getVarsFromFrame': 0, + 'lazyLoadDBinsertValue': 0, + 'bincode': 0, + 'tabulate': 0, + 'findInterval': 0, + 'pretty': 0, + 'formatC': 0, + 'crc64': 0, + 'bitwiseAnd': 1, + 'bitwiseNot': 2, + 'bitwiseOr': 3, + 'bitwiseXor': 4, + 'bitwiseShiftL': 5, + 'bitwiseShiftR': 6, + 'serialize': 0, + 'serializeb': 1, + 'unserialize': 2, + 'rowsum_matrix': 0, + 'rowsum_df': 1, + 'setS4Object': 0, + 'traceOnOff': 0, + 'debugOnOff': 1, + 'La_qr_cmplx': 0, + 'La_rs': 5, + 'La_rs_cmplx': 51, + 'La_rg': 3, + 'La_rg_cmplx': 41, + 'La_dlange': 6, + 'La_dgecon': 7, + 'La_dtrcon': 8, + 'La_dtrcon3': 81, + 'La_zlange': 61, + 'La_zgecon': 9, + 'La_ztrcon': 10, + 'La_ztrcon3': 13, + 'La_solve_cmplx': 11, + 'La_solve': 100, + 'La_qr': 101, + 'La_chol': 200, + 'La_chol2inv': 201, + 'qr_coef_real': 300, + 'qr_qy_real': 301, + 'det_ge_real': 302, + 'qr_coef_cmplx': 303, + 'qr_qy_cmplx': 304, + 'La_svd': 400, + 'La_svd_cmplx': 401, + 'La_version': 1000, + 'La_library': 1001, + 'bcprofcounts': 0, + 'bcprofstart': 0, + 'bcprofstop': 0, + 'eSoftVersion': 0, + 'curlVersion': 0, + 'curlGetHeaders': 0, + 'curlDownload': 0, + 'compilerVersion': 0, +} as const; \ No newline at end of file diff --git a/test/functionality/dataflow/main/functions/dataflow-load.test.ts b/test/functionality/dataflow/main/functions/dataflow-load.test.ts new file mode 100644 index 00000000000..e2464cbd829 --- /dev/null +++ b/test/functionality/dataflow/main/functions/dataflow-load.test.ts @@ -0,0 +1,396 @@ +import { describe, expect, it } from 'vitest'; +import { assertDataflow, withTreeSitter } from '../../../_helper/shell'; +import { label } from '../../../_helper/label'; +import { emptyGraph } from '../../../../../src/dataflow/graph/dataflowgraph-builder'; +import fs from 'fs'; +import path from 'path'; +import { RShellExecutor } from '../../../../../src/r-bridge/shell-executor'; +import { getVarsAndTypesFromShell } from '../../../project/plugin/load-pipeline/load-pipeline.test'; +import { argumentInCall, defaultEnv } from '../../../_helper/dataflow/environment-builder'; +import seedrandom from 'seedrandom'; +import { RandomRCodeGenerator, RObjectType, SeededRandom } from '../../../util/project/plugin/random-r-code-generator'; +import os from 'os'; +import { FlowrConfig } from '../../../../../src/config'; +import { BuiltInProcName } from '../../../../../src/dataflow/environments/built-in-proc-name'; +import { NodeId } from '../../../../../src/r-bridge/lang-4.x/ast/model/processing/node-id'; + +describe('load real-world', withTreeSitter(parser => { + const dir = 'test/functionality/project/plugin/load-pipeline/_zenodo/files'; + if(!(fs.existsSync(dir) && fs.readdirSync(dir).length > 0)) { + it.skip('skipped - no RDA files found', () => {}); + return; + } + + const files = fs.readdirSync(dir) + .filter(file => file.toLowerCase().endsWith('.rdata') || file.toLowerCase().endsWith('.rda')) + .map(file => path.join(dir, file)); + + describe('defines variables', () => { + for(const file of files) { + const rShell = new RShellExecutor(); + const varsAndTypesFromShell = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + if(!varsAndTypesFromShell || varsAndTypesFromShell.size === 0) { + continue; + } + + let graph = emptyGraph(); + for(const [varName, varType] of varsAndTypesFromShell) { + const syntheticId = `3:loaded:${varName}`; + const cds = [{ id: '3', when: true }]; + + if(varType === 'closure' || varType === 'special' || varType === 'builtin') { + const fdefId = `${syntheticId}:fdef`; + graph = graph.defineFunction(fdefId, [], { + entryPoint: fdefId, + graph: new Set(), + out: [], + in: [], + unknownReferences: [], + hooks: [], + environment: defaultEnv(), + }, { cds }); + graph = graph.defineVariable(syntheticId, undefined, { cds }); + graph = graph.definedBy(syntheticId, fdefId); + } else { + graph = graph.defineVariable(syntheticId, undefined, { cds }); + } + } + + assertDataflow( + label(`load defines variables from ${path.basename(file)}`, ['name-normal']), + parser, + `load("${file}")`, + graph, + { expectIsSubgraph: true } + ); + } + }); + + describe('overwrite behavior', () => { + for(const file of files) { + const rShell = new RShellExecutor(); + const varsAndTypesFromShell = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + if(!varsAndTypesFromShell || varsAndTypesFromShell.size === 0) { + continue; + } + + const firstEntry = [...varsAndTypesFromShell.entries()][0]; + if(!firstEntry) { + continue; + } + const [firstVar] = firstEntry; + + assertDataflow( + label(`loading ${path.basename(file)} overwrites an existing variable`, ['name-normal']), + parser, + `${firstVar} <- 42\nload("${file}")\nprint(${firstVar})`, + emptyGraph() + .use(`3@${firstVar}`, firstVar) + .reads(`3@${firstVar}`, `6:loaded:${firstVar}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + + assertDataflow( + label(`assignment overwrites loaded variable from ${path.basename(file)}`, ['name-normal']), + parser, + `load("${file}")\n${firstVar} <- 42\nprint(${firstVar})`, + emptyGraph() + .use(`3@${firstVar}`, firstVar) + .reads(`3@${firstVar}`, `2@${firstVar}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + } + }); + + describe('function call', () => { + for(const file of files) { + const rShell = new RShellExecutor(); + const varsAndTypesFromShell = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + const firstClosure = [...varsAndTypesFromShell.entries()].find(([, type]) => type === 'closure'); + if(!firstClosure) { + continue; + } + const [closureName] = firstClosure; + + assertDataflow( + label(`function from load is callable in ${path.basename(file)}`, ['name-normal']), + parser, + `load("${file}")\n${closureName}()`, + emptyGraph() + .defineVariable(`3:loaded:${closureName}`, undefined, { cds: [{ id: 3, when: true }] }) + .defineFunction(`3:loaded:${closureName}:fdef`, [], { + entryPoint: `3:loaded:${closureName}:fdef`, + graph: new Set(), + out: [], + in: [], + unknownReferences: [], + hooks: [], + environment: defaultEnv() + }, { cds: [{ id: 3, when: true }] }) + .definedBy(`3:loaded:${closureName}`, `3:loaded:${closureName}:fdef`) + .reads(`2@${closureName}`, `3:loaded:${closureName}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + } + }); + + describe('ignore load calls config', () => { + const rShell = new RShellExecutor(); + const varsAndTypesFromShell = getVarsAndTypesFromShell(files[0], rShell); + rShell.close(); + const firstEntry = [...varsAndTypesFromShell.entries()][0]; + if(firstEntry) { + const [firstVar] = firstEntry; + assertDataflow( + label('load is ignored when ignoreLoadCalls is true', ['name-normal']), + parser, + `load("${files[0]}")`, + emptyGraph(), + { + expectIsSubgraph: true, + mustNotHaveVertices: new Set([`3:loaded:${firstVar}`]), + }, + 0, + FlowrConfig.setInConfig(FlowrConfig.default(), 'ignoreLoadCalls', true) + ); + } + }); +})); + +describe('load random', withTreeSitter(parser => { + const seed = 0; + const rng = seedrandom(seed.toString()); + const rnd = new SeededRandom(rng); + const rcg = new RandomRCodeGenerator(rnd); + const tempFolder = fs.mkdtempSync(path.resolve(os.tmpdir(), '/tmp/flowr-load-test')); + + const createRda = (types: RObjectType[], filename: string): { file: string, vars: string[] } => { + const { rCode, vars } = rcg.generateRCodeWithTypes(types); + const file = path.join(tempFolder, filename); + const rShell = new RShellExecutor(); + rShell.run(`${rCode}\nsave(${vars.join(', ')}, file="${file}")`); + rShell.close(); + return { file, vars }; + }; + + describe('defines variables', () => { + const { file, vars } = createRda([ + RObjectType.Literal, + RObjectType.Vector, + RObjectType.Function, + RObjectType.List, + ], 'defines_variables.rda'); + + const rShell = new RShellExecutor(); + const varsAndTypes = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + it('generated rda contains expected variable names', () => { + expect([...varsAndTypes.keys()].sort()).toEqual(vars.sort()); + }); + + let graph = emptyGraph(); + for(const [varName, varType] of varsAndTypes) { + const syntheticId = `3:loaded:${varName}`; + const cds = [{ id: '3', when: true }]; + if(varType === 'closure' || varType === 'special' || varType === 'builtin') { + const fdefId = `${syntheticId}:fdef`; + graph = graph.defineFunction(fdefId, [], { + entryPoint: fdefId, graph: new Set(), out: [], in: [], unknownReferences: [], hooks: [], environment: defaultEnv() + }, { cds }); + graph = graph.defineVariable(syntheticId, undefined, { cds }); + graph = graph.definedBy(syntheticId, fdefId); + } else { + graph = graph.defineVariable(syntheticId, undefined, { cds }); + } + } + + assertDataflow( + label('load defines variables from generated rda', ['name-normal']), + parser, + `load("${file}")`, + graph, + { expectIsSubgraph: true } + ); + }); + + describe('overwrite behavior', () => { + const { file: fileNoClosure, vars: varsNoClosure } = createRda([RObjectType.Literal], 'overwrite_no_closure.rda'); + const { file: fileClosure, vars: varsClosure } = createRda([RObjectType.Function], 'overwrite_closure.rda'); + + const rShell = new RShellExecutor(); + const varsNoClosureFromShell = getVarsAndTypesFromShell(fileNoClosure, rShell); + const varsClosureFromShell = getVarsAndTypesFromShell(fileClosure, rShell); + rShell.close(); + + it('generated rda contains expected variable names', () => { + expect([...varsNoClosureFromShell.keys()].sort()).toEqual(varsNoClosure.sort()); + expect([...varsClosureFromShell.keys()].sort()).toEqual(varsClosure.sort()); + }); + + const firstNoClosureEntry = [...varsNoClosureFromShell.entries()][0]; + const firstClosureEntry = [...varsClosureFromShell.entries()][0]; + + if(firstNoClosureEntry) { + const [firstVar] = firstNoClosureEntry; + + assertDataflow( + label('load overwrites existing variable (no closure)', ['name-normal']), + parser, + `${firstVar} <- 42\nload("${fileNoClosure}")\nprint(${firstVar})`, + emptyGraph() + .use(`3@${firstVar}`, firstVar) + .reads(`3@${firstVar}`, `6:loaded:${firstVar}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + + assertDataflow( + label('assignment overwrites loaded variable (no closure)', ['name-normal']), + parser, + `load("${fileNoClosure}")\n${firstVar} <- 42\nprint(${firstVar})`, + emptyGraph() + .use(`3@${firstVar}`, firstVar) + .reads(`3@${firstVar}`, `2@${firstVar}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + } + + if(firstClosureEntry) { + const [firstVar] = firstClosureEntry; + + assertDataflow( + label('load overwrites existing closure variable', ['name-normal']), + parser, + `${firstVar} <- 42\nload("${fileClosure}")\nprint(${firstVar})`, + emptyGraph() + .use(`3@${firstVar}`, firstVar) + .reads(`3@${firstVar}`, `6:loaded:${firstVar}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + + assertDataflow( + label('assignment overwrites loaded closure variable', ['name-normal']), + parser, + `load("${fileClosure}")\n${firstVar} <- 42\nprint(${firstVar})`, + emptyGraph() + .use(`3@${firstVar}`, firstVar) + .reads(`3@${firstVar}`, `2@${firstVar}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + } + }); + + describe('function call', () => { + const { file, vars } = createRda([RObjectType.Function], 'function_call.rda'); + const rShell = new RShellExecutor(); + const varsAndTypes = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + it('generated rda contains expected variable names', () => { + expect([...varsAndTypes.keys()].sort()).toEqual(vars.sort()); + }); + + const firstClosure = [...varsAndTypes.entries()].find(([, type]) => type === 'closure'); + if(firstClosure) { + const [closureName] = firstClosure; + + assertDataflow( + label('function from generated load is callable', ['name-normal']), + parser, + `load("${file}")\n${closureName}()`, + emptyGraph() + .defineVariable(`3:loaded:${closureName}`, undefined, { cds: [{ id: 3, when: true }] }) + .defineFunction(`3:loaded:${closureName}:fdef`, [], { + entryPoint: `3:loaded:${closureName}:fdef`, graph: new Set(), out: [], in: [], unknownReferences: [], hooks: [], environment: defaultEnv() + }, { cds: [{ id: 3, when: true }] }) + .definedBy(`3:loaded:${closureName}`, `3:loaded:${closureName}:fdef`) + .reads(`2@${closureName}`, `3:loaded:${closureName}`), + { expectIsSubgraph: true, resolveIdsAsCriterion: true } + ); + } + }); + + describe('ignore load calls config', () => { + const { file, vars } = createRda([RObjectType.Literal], 'ignore_config.rda'); + const rShell = new RShellExecutor(); + const varsAndTypes = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + it('generated rda contains expected variable names', () => { + expect([...varsAndTypes.keys()].sort()).toEqual(vars.sort()); + }); + + const firstEntry = [...varsAndTypes.entries()][0]; + if(firstEntry) { + const [firstVar] = firstEntry; + assertDataflow( + label('load is ignored when ignoreLoadCalls is true (generated)', ['name-normal']), + parser, + `load("${file}")`, + emptyGraph() + .call('3', 'load', [argumentInCall('1')], { returns: [], reads: [NodeId.toBuiltIn('load')] }) + .argument('3', '1') + .calls('3', NodeId.toBuiltIn('load')) + .markIdForUnknownSideEffects('3'), + { + expectIsSubgraph: true, + mustNotHaveVertices: new Set([`3:loaded:${firstVar}`]) + }, + 0, + FlowrConfig.setInConfig(FlowrConfig.default(), 'ignoreLoadCalls', true) + ); + } + }); + + process.on('exit', () => { + try { + fs.rmSync(tempFolder, { recursive: true, force: true }); + } catch(e) { + console.error('Error during cleanup:', e); + } + }); +})); + +describe('file not found', withTreeSitter(parser => { + assertDataflow( + label('load with nonexistent file is unknown side effect', ['name-normal']), + parser, + 'load("nonexistent.rda")', + emptyGraph() + .call('3', 'load', [argumentInCall('1')], { returns: [], reads: [NodeId.toBuiltIn('load')] }) + .argument('3', '1') + .calls('3', NodeId.toBuiltIn('load')), + { expectIsSubgraph: true } + ); +})); + +describe('no resolvable file argument', withTreeSitter(parser => { + assertDataflow(label('load without any file argument is unknown side effect', ['unnamed-arguments']), parser, 'load()', emptyGraph() + .call('1', 'load', [], { returns: [], reads: [NodeId.toBuiltIn('load')], origin: [BuiltInProcName.Function] }) + .calls('1', NodeId.toBuiltIn('load')) + .markIdForUnknownSideEffects('1'), + { expectIsSubgraph: true } + ); + + assertDataflow(label('load with only envir argument is unknown side effect', ['unnamed-arguments', 'name-normal']), parser, 'e <- new.env()\nload(envir = e)', emptyGraph() + .use('6', 'e') + .reads('6', '0') + .use('7', 'envir') + .reads('7', '6') + .call('2', 'new.env', [], { returns: [], reads: [NodeId.toBuiltIn('new.env')] }) + .calls('2', NodeId.toBuiltIn('new.env')) + .call('3', '<-', [argumentInCall('0'), argumentInCall('2')], { returns: ['0'], reads: ['2', NodeId.toBuiltIn('<-')], onlyBuiltIn: true }) + .calls('3', NodeId.toBuiltIn('<-')) + .call('8', 'load', [argumentInCall('7', { name: 'envir' })], { returns: [], reads: [NodeId.toBuiltIn('load')], origin: [BuiltInProcName.Function] }) + .calls('8', NodeId.toBuiltIn('load')) + .defineVariable('0', 'e', { definedBy: ['2', '3'] }) + .markIdForUnknownSideEffects('8'), + { expectIsSubgraph: true } + ); +})); \ No newline at end of file diff --git a/test/functionality/project/plugin/load-pipeline/.gitignore b/test/functionality/project/plugin/load-pipeline/.gitignore new file mode 100644 index 00000000000..cbfd8b4da1d --- /dev/null +++ b/test/functionality/project/plugin/load-pipeline/.gitignore @@ -0,0 +1 @@ +**/files/ \ No newline at end of file diff --git a/test/functionality/project/plugin/load-pipeline/README.md b/test/functionality/project/plugin/load-pipeline/README.md new file mode 100644 index 00000000000..573532f5343 --- /dev/null +++ b/test/functionality/project/plugin/load-pipeline/README.md @@ -0,0 +1,3 @@ +# RDA-File / `load()` Tests + +*Currently work-in-progress* \ No newline at end of file diff --git a/test/functionality/project/plugin/load-pipeline/load-pipeline.test.ts b/test/functionality/project/plugin/load-pipeline/load-pipeline.test.ts new file mode 100644 index 00000000000..0af019d3f8e --- /dev/null +++ b/test/functionality/project/plugin/load-pipeline/load-pipeline.test.ts @@ -0,0 +1,186 @@ +import { describe, expect, it } from 'vitest'; +import { RandomRCodeGenerator, SeededRandom } from '../../../util/project/plugin/random-r-code-generator'; +import { RShellExecutor } from '../../../../../src/r-bridge/shell-executor'; +import type { RObjectData } from '../../../../../src/project/plugins/file-plugins/files/flowr-rda-file'; +import { RDAParser } from '../../../../../src/project/plugins/file-plugins/files/flowr-rda-file'; +import { FlowrTextFile } from '../../../../../src/project/context/flowr-file'; +import seedrandom from 'seedrandom'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; + +describe('rda-files', () => { + describe('load-pipeline random', () => { + const runs = 300; + const seed = 0; + const objectsPerRun = 5; + const maxNestingLevel = 1; + + const saveFormats = [ + 'TRUE', // ASCII + 'FALSE' // XDR + ]; + const versions = [ + // '1', + '2', + '3' + ]; + const compressions = [ + 'gzip', + 'bzip2', + 'xz', + 'none' + ]; + + const tempFolder = fs.mkdtempSync(path.join(os.tmpdir(), 'flowr-load-pipeline-test-')); + + for(let i = 0; i < runs; i++) { + const file = `${tempFolder}/test_${i}.rda`; + const rng = seedrandom((seed + i).toString()); + const rnd = new SeededRandom(rng); + + const encoding = rnd.pick(saveFormats); + const version = rnd.pick(versions); + const compression = rnd.pick(compressions); + + it(`Encoding: ${encoding}, Version: ${version}, Compression: ${compression} - run ${i} - seed ${seed + i}`, () => { + + const rcg = new RandomRCodeGenerator(rnd); + + const { rCode, vars } = rcg.generateRCode(objectsPerRun, maxNestingLevel); + + const shellCode = `${rCode} + save(${vars.join(', ')}, file="${file}", ascii = ${encoding}, version = ${version})`; + const rShell = new RShellExecutor(); + rShell.run(shellCode); + + const varsAndTypesFromShell = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + expect([...varsAndTypesFromShell.keys()].sort()) + .toEqual(vars.sort()); + + const result2 = new RDAParser(new FlowrTextFile(file), true).parseRDA(); + + expect(result2).toBeDefined(); + + expectNames(result2 as RObjectData[], varsAndTypesFromShell); + }); + } + + process.on('exit', () => { + try { + fs.rmSync(tempFolder, { recursive: true, force: true }); + } catch(e) { + console.error('Error during cleanup:', e); + } + }); + }); + + describe('load-pipeline real-world', () => { + const dir = 'test/functionality/project/plugin/load-pipeline/zenodo/files'; + if(!(fs.existsSync(dir) && fs.readdirSync(dir).length > 0)) { + it.skip('skipped - no RDA files found', () => {}); + return; + } + + const files = fs.readdirSync(dir).filter(file => file.toLowerCase().endsWith('.rdata') || file.toLowerCase().endsWith('.rda')).map(file => path.join(dir, file)); + + for(const file of files) { + it(`File: ${file}`, () => { + const rShell = new RShellExecutor(); + const varsAndTypesFromShell = getVarsAndTypesFromShell(file, rShell); + rShell.close(); + + if(!varsAndTypesFromShell || varsAndTypesFromShell.size === 0) { + return; + } + + const result2 = new RDAParser(new FlowrTextFile(file), true).parseRDA(); + + expect(result2).toBeDefined(); + + expectNames(result2 as RObjectData[], varsAndTypesFromShell); + }); + } + }); +}); + +/** + * Loads an RDA file in a fresh R environment and returns a map of variable names to their types. + * @param file - Path to the RDA file to load + * @param rShell - The R shell executor to use for running the R code + * @returns A map from variable name to its R type string + */ +export function getVarsAndTypesFromShell(file: string, rShell: RShellExecutor) { + const output = rShell.run(` + e <- new.env() + + vars <- load("${file}", envir = e) + + for(v in vars) { + cat(v, "::", typeof(e[[v]]), "\\n") + } + `); + + const result = new Map(); + + for(const line of output.split('\n')) { + const [name, type] = line.split('::').map(x => x.trim()); + + if(name && type) { + result.set(name, type); + } + } + + return result; +} + +function expectNames(result: RObjectData[], vars: ReadonlyMap) { + expect(result?.flatMap(x => x.name).sort()).toEqual([...vars.keys()].sort()); +} + +/* Maps the {@link SexpType} to its string representation in the R shell*/ +const SexpToRType = { + 0: 'NULL', + 1: 'symbol', + 2: 'pairlist', + 3: 'closure', + 4: 'environment', + 5: 'prom', + 6: 'language', + 7: 'special', + 8: 'builtin', + 9: 'character', + 10: 'logical', + 13: 'integer', + 14: 'double', + 15: 'complex', + 16: 'string', + 17: '...', + 19: 'list', + 20: 'expression', + 24: 'raw', + 25: 'S4', +} as const; + +function _expectTypes(result: RObjectData[], types: ReadonlyMap) { + for(const obj of result) { + const expected = types.get(obj.name as string); + + if(obj.type === 4) { + expect(['NULL', 'environment']).toContain(expected); + continue; + } + + const actualType = (SexpToRType as Record)[obj.type as number]; + + if(actualType !== expected) { + console.log(obj); + console.log(types.get(obj.name as string)); + + } + + expect(actualType).toBe(expected); + } +} \ No newline at end of file diff --git a/test/functionality/project/plugin/load-pipeline/setup.sh b/test/functionality/project/plugin/load-pipeline/setup.sh new file mode 100644 index 00000000000..d92c132c85a --- /dev/null +++ b/test/functionality/project/plugin/load-pipeline/setup.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -eu + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RDA_DIR="${SCRIPT_DIR}/zenodo" +CSV_FILE="${RDA_DIR}/zenodo_files.csv" + +if [ ! -f "${CSV_FILE}" ]; then + echo "CSV file not found at ${CSV_FILE}." + echo "Please provide the zenodo_files.csv file. See README.md for instructions." + exit 1 +fi + +mkdir -p "${RDA_DIR}/files" + +MAX_SIZE=$((1 * 1024 * 1024 * 1024)) + +echo "Normalizing CSV line endings..." +sed -i 's/\r//' "${CSV_FILE}" + + +echo "Downloading RDA/RData files..." +grep -oP "https://zenodo\.org/record/[0-9]+/files/[^\s'\"]+\.(?i:rda|rdata)[^\s'\"]*" "${CSV_FILE}" | while read -r url; do + echo "DEBUG: ${url}" + record_id=$(echo "${url}" | grep -oP "zenodo\.org/record/\K[0-9]+") + filename=$(basename "${url%%\?*}") + output="${RDA_DIR}/files/${record_id}_${filename}" + + if [ -f "${output}" ]; then + echo " SKIPPING ${filename} (already exists)" + continue + fi + + content_length=$(wget --server-response --spider "${url}" 2>&1 | grep -i "Content-Length" | tail -1 | grep -oP "[0-9]+" || echo "0") + + if [ "${content_length}" -gt "${MAX_SIZE}" ]; then + echo " SKIPPING ${filename} (${content_length} bytes > 1GB)" + continue + fi + + echo " Downloading ${record_id}_${filename} (${content_length} bytes)..." + wget -q --tries=3 --waitretry=5 -O "${output}" "${url}" || { echo " WARNING: Failed to download ${url}"; rm -f "${output}"; } + sleep 1 +done + +echo "Done. RDA files saved to ${RDA_DIR}/files/" \ No newline at end of file diff --git a/test/functionality/util/project/plugin/random-r-code-generator.ts b/test/functionality/util/project/plugin/random-r-code-generator.ts new file mode 100644 index 00000000000..0219d7d2ba4 --- /dev/null +++ b/test/functionality/util/project/plugin/random-r-code-generator.ts @@ -0,0 +1,519 @@ +import { randomString } from '../../../../../src/util/random'; + +/** + * Pools of symbols used by {@link RandomRCodeGenerator.generateString} to build random R strings. + */ +export const validStringSymbols = [ + [ + 'a', 'b', 'c', 'x', 'y', 'z', + 'A', 'B', 'C', 'X', 'Y', 'Z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + + '.', ',', ';', ':', '!', '?', + '-', '_', '+', '*', '/', '=', + + '(', ')', '[', ']', '{', '}', + + '@', '€', '#', '§', '$', '%', '&', '^', '|', '`', '~', '<', '>', '°', + + 'ä', 'ö', 'ü', 'ß', + 'Ä', 'Ö', 'Ü', + 'é', 'è', 'ê', + 'í', 'ì', 'î', + ], + // eslint-disable-next-line no-useless-escape + [ '\n', '\t', '\v', '\b', '\r', '\f', '\a', '\\', '\'', '\"', ' ' ], + [ '😀', '💩' ] +]; + +/** + * Kinds of R objects {@link RandomRCodeGenerator} can 'generate'. + */ +export enum RObjectType { + Literal = 'literal', + Vector = 'vector', + List = 'list', + Map = 'map', + Matrix = 'matrix', + DataFrame = 'dataframe', + Environment = 'environment', + Function = 'function', + PairList = 'pairlist', + Call = 'call', + Symbol = 'symbol', + Language = 'language', + Expression = 'expression', + AnonymousFunction = 'anonymous-function', + Primitive = 'primitive', + Promise = 'promise', + Factor = 'factor', + S4 = 's4', +} + +/** + * {@link RObjectType}s for which generating an attribute is not possible. + */ +const typesWithoutAttributes = new Set(['symbol', 'promise']); + +/** + * Generates random R code (literals, vectors, lists, functions, environments, S4 objects, ...). + * @example + * ```ts + * const rnd = new SeededRandom(seedrandom('my-seed')); + * const rcg = new RandomRCodeGenerator(rnd); + * const { rCode, vars } = rcg.generateRCodeWithTypes([RObjectType.Vector, RObjectType.Function]); + * // rCode now contains R source defining var_0 (a vector) and var_1 (a function) + * ``` + */ +export class RandomRCodeGenerator { + private readonly rnd: SeededRandom; + + constructor(rnd: SeededRandom) { + this.rnd = rnd; + } + + /** + * Generates `numberOfObjects` random R variable definitions with the name scheme `var_0`, `var_1`, ..., + * Each variable a randomly chosen object type and (with some probability) a random attribute is assigned. + * @param numberOfObjects - How many top-level variables to generate. + * @param maxNestingLevel - Maximum recursion depth for nested object types (e.g. a list of vectors). + * @returns The generated R source code (`rCode`) and the list of variable names it defines (`vars`). + */ + generateRCode(numberOfObjects: number, maxNestingLevel: number) { + const codeMap = new Map(); + const vars: string[] = []; + + for(let i = 0; i < numberOfObjects; i++) { + const name = `var_${i}`; + + const rnd = this.rnd.int(100); + + let code = ''; + + if(rnd < 80){ + const operator = this.rnd.pick(['<-', '=', '<<-']); + const { value, type, len } = this.generateObject(0, maxNestingLevel); + code = `${name} ${operator} ${value}`; + + if(!(value === null || len === 0 || typesWithoutAttributes.has(type))) { + const attribute = this.rnd.pick([ + () => this.generateAttribute(name), + () => ({ value: `class(${name}) <- "foo"` }), + () => ({ value: '' }), + ])(); + code = `${code}\n${attribute.value}`; + } + } else { + code += this.generateS4(name); + } + + codeMap.set(name, code); + vars.push(name); + } + + const rCode = Array.from(codeMap.values()).join('\n'); + + return { + rCode, + vars + }; + } + + /** + * Generates R source for a single object of the given {@link RObjectType}. + * @param type - The kind of object to generate. + * @param nestingLevel - Current recursion depth (used to bound nested generation). + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R expression (`value`), its R type name (`type`), and its length/size (`len`). + */ + generateObjectOfType(type: RObjectType, nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + switch(type) { + case RObjectType.Literal: return this.generateLiteral(); + case RObjectType.Vector: return this.generateVector(nestingLevel + 1, maxNestingLevel); + case RObjectType.List: return this.generateList(nestingLevel + 1, maxNestingLevel); + case RObjectType.Map: return this.generateMap(nestingLevel + 1, maxNestingLevel); + case RObjectType.Matrix: return this.generateMatrix(maxNestingLevel); + case RObjectType.DataFrame: return this.generateDataFrame(); + case RObjectType.Environment: return this.generateEnvironmentExpr(); + case RObjectType.Function: return this.generateFunction(nestingLevel + 1, maxNestingLevel); + case RObjectType.PairList: return this.generatePairList(nestingLevel + 1, maxNestingLevel); + case RObjectType.Call: return this.generateCall(); + case RObjectType.Symbol: return this.generateSymbol(); + case RObjectType.Language: return this.generateLanguage(); + case RObjectType.Expression: return this.generateExpression(); + case RObjectType.AnonymousFunction: return this.generateAnonymousFunction(); + case RObjectType.Primitive: return this.generatePrimitive(); + case RObjectType.Promise: return this.generatePromise(); + case RObjectType.Factor: return this.generateFactor(nestingLevel + 1, maxNestingLevel); + case RObjectType.S4: return { value: this.generateS4(`tmp_${Date.now()}`), type: 's4', len: 1 }; + } + } + + /** + * Generates one variable definition per entry in `types`, in order. + * @param types - The {@link RObjectType} to generate for each variable, in order. + * @param maxNestingLevel - Maximum recursion depth for nested object types. + * @returns The generated R source code (`rCode`) and the list of variable names it defines (`vars`). + */ + generateRCodeWithTypes(types: RObjectType[], maxNestingLevel = 1) { + const codeMap = new Map(); + const vars: string[] = []; + + for(let i = 0; i < types.length; i++) { + const name = `var_${i}`; + const { value } = this.generateObjectOfType(types[i], 0, maxNestingLevel); + codeMap.set(name, `${name} <- ${value}`); + vars.push(name); + } + + return { + rCode: Array.from(codeMap.values()).join('\n'), + vars + }; + } + + /** + * Generates a fixed S4 class (`Employee`) and an instance of it assigned to `name`. + * @param name - The variable name the new S4 instance should be assigned to. + * @returns R source code. + */ + generateS4(name: string){ + return `setClass("Employee", slots=list(name="character", + age="numeric", + role="character")) + ${name} <- new("Employee", name = "Sanket", + age = 21, + role = "Software Developer")`; + } + + /** + * Generates a random object, picking its {@link RObjectType} uniformly at random. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type of the object (`type`), + * and the length of the object (`len`). + */ + generateObject(nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + if(nestingLevel >= maxNestingLevel){ + return this.generateLiteral(); + } + + const values = Object.values(RObjectType) as RObjectType[]; + const type = this.rnd.pick(values); + return this.generateObjectOfType(type, nestingLevel, maxNestingLevel); + } + + /** + * Generates a `factor(...)` wrapping a random vector. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type 'factor' (`type`), and the length of the vector (`len`). + */ + generateFactor(nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + const vector = this.generateVector(nestingLevel, maxNestingLevel); + return { value: `factor(${vector.value})`, type: 'factor', len: vector.len }; + } + + /** + * Generates an attribute to the given R object. + * @param name - Name of variable the attribute is added to. + * @returns The generated R source code (`value`) and the length of the object (`len`). + */ + generateAttribute(name: string): { value: string, len: number } { + const obj = this.generateLiteral(); + return { value: `attr(${name}, 'attr') <- ${obj.value}`, len: obj.len }; + } + + /** + * Generates a set promise. + * @returns The generated R source code (`value`), the type 'promise' (`type`) and a length of 1 (`len`). + */ + generatePromise(): { value: string, type: string, len: number } { + return { value: 'delayedAssign("x", 1L)', type: 'promise', len: 1 }; + } + + /** + * Generates a set primitive. + * @returns The generated R source code (`value`), the type 'primitive' (`type`) and a length of 1 (`len`). + */ + generatePrimitive(): { value: string, type: string, len: number } { + return { value: '.Primitive("sqrt")', type: 'primitive', len: 1 }; + } + + /** + * Generates a set anonymous function. + * @returns The generated R source code (`value`), the type 'function' (`type`) and a length of 1 (`len`). + */ + + generateAnonymousFunction(): { value: string, type: string, len: number } { + return { value: '(function(x, y){ z <- x^2 + y^2; x+y+z })(0:7, 1)', type: 'function', len: 1 }; + } + + /** + * Generates a set expression. + * @returns The generated R source code (`value`), the type 'expression' (`type`) and a length of 1 (`len`). + */ + generateExpression(): { value: string, type: string, len: number } { + return { value: 'expression(1 + 0:9)', type: 'expression', len: 1 }; + } + + /** + * Generates a set language object. + * @returns The generated R source code (`value`), the type 'language' (`type`) and a length of 1 (`len`). + */ + generateLanguage(): { value: string, type: string, len: number } { + return { value: 'quote(1+2)', type: 'language', len: 1 }; + } + + /** + * Generates a set symbol object. + * @returns The generated R source code (`value`), the type 'symbol' (`type`) and a length of 1 (`len`). + */ + generateSymbol(): { value: string, type: string, len: number } { + return { value: 'as.name("arrg")', type: 'symbol', len: 1 }; + } + + /** + * Generates a set call object. + * @returns The generated R source code (`value`), the type 'call' (`type`) and a length of 1 (`len`). + */ + generateCall(): { value: string, type: string, len: number } { + return { value: 'call("round",10.5)', type: 'call', len: 1 }; + } + + /** + * Generates a pairlist. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type 'pairlist' (`type`) and a length of the pairlist (`len`). + */ + generatePairList(nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + const len = this.rnd.int(10); + const elements = Array.from({ length: len }, () => + this.generateObject(nestingLevel + 1, maxNestingLevel).value + ); + return { value: `pairlist(${elements.join(', ')})`, type: 'pairlist', len: len }; + } + + /** + * Generates a vector. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type 'vector' (`type`) and the length of the vector (`len`). + */ + generateVector(nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + const len = this.rnd.int(10); + const elements = Array.from({ length: len }, () => + this.generateObject(nestingLevel + 1, maxNestingLevel).value + ); + return { value: `c(${elements.join(', ')})`, type: 'vector', len: len }; + } + + /** + * Generates a List. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @param length - (optional) Size of the generated list. + * @returns The generated R source code (`value`), the type 'list' (`type`) and the length of the list (`len`) + */ + generateList(nestingLevel: number, maxNestingLevel: number, length?: number): { value: string, type: string, len: number } { + const len = length || this.rnd.int(10); + const elements = Array.from({ length: len }, () => + this.generateObject(nestingLevel + 1, maxNestingLevel).value + ); + return { value: `list(${elements.join(', ')})`, type: 'list', len: len }; + } + + /** + * Generates a Map. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type 'map' (`type`) and a length of 1 (`len`). + */ + generateMap(nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + const len = this.rnd.int(10); + const elements = Array.from({ length: len }, (_, i) => { + const key = `key_${i}`; + const { value, type: _type } = this.generateObject(nestingLevel + 1, maxNestingLevel); + return `${key} = ${value}`; + }); + return { value: `list(${elements.join(', ')})`, type: 'map', len: len }; + } + + /** + * Generates a Matrix. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type 'matrix' (`type`) and the amount of elements in it (`len`). + */ + generateMatrix(maxNestingLevel: number): { value: string, type: string, len: number } { + const rows = this.rnd.int(3) + 1; + const cols = this.rnd.int(3) + 1; + const len = rows * cols; + const elements = Array.from({ length: len }, () => this.generateObject(0, maxNestingLevel).value); + const byRow = this.rnd.pick(['TRUE', 'FALSE']); + return { value: `matrix(c(${elements.join(', ')}), nrow = ${rows}, ncol = ${cols}, byrow = ${byRow})`, type: 'matrix', len }; + } + + /** + * Generates a data frame. + * @returns The generated R source code (`value`), the type 'dataframe' (`type`) and the amount of columns (`len`). + */ + generateDataFrame(): { value: string, type: string, len: number } { + const nRows = this.rnd.int(10); + const nCols = this.rnd.int(10); + const cols = Array.from({ length: nCols }, (_, i) => { + const type = this.rnd.pick(['integer', 'double', 'logical', 'character']); + const vals = Array.from({ length: nRows }, () => this.generateTypedLiteral(type).value); + return `col_${i} = c(${vals.join(', ')})`; + }); + return { value: `data.frame(${cols.join(', ')})`, type: 'dataframe', len: nCols }; + } + + /** + * Generates an environment expression. + * @returns The generated R source code (`value`), the type 'environment' (`type`) and a length of 1 (`len`). + */ + generateEnvironmentExpr(): { value: string, type: string, len: number } { + const value = this.rnd.pick([ + 'new.env()', + 'new.env(parent = emptyenv())', + 'globalenv()', + 'baseenv()', + 'emptyenv()', + ]); + return { value: value, type: 'environment', len: 1 }; + } + + /** + * Generates a function. + * The function is filled with random R objects. + * @param nestingLevel - Current recursion depth. + * @param maxNestingLevel - Maximum recursion depth allowed. + * @returns The generated R source code (`value`), the type 'function' (`type`) and the length of the body (`len`). + */ + generateFunction(nestingLevel: number, maxNestingLevel: number): { value: string, type: string, len: number } { + const args = Array.from({ length: this.rnd.int(3) }, (_, i) => `arg${i}`).join(', '); + const body = this.generateObject(nestingLevel + 1, maxNestingLevel); + return { value: `function(${args}) { ${body.value} }`, type: 'function', len: body.len }; + } + + /** + * Generates a single literal value of the given primitive R type. + * @param type - The primitive R type name to generate a literal for. + * @returns The generated R source code (`value`), the object's type (`type`) and the length of the object (`len`). + */ + generateTypedLiteral(type: string): { value: string, type: string, len: number } { + switch(type) { + case 'integer': { + const obj = this.rnd.int(1000); + return { value: `${obj}L`, type: 'integer', len: Math.ceil(Math.log10(obj + 1)) }; + } + case 'double': return this.generateDouble(); + case 'logical': return { value: this.rnd.pick(['TRUE', 'FALSE', 'NA']), type: 'logical', len: 1 }; + case 'character': return this.generateString(); + case 'complex': return { value: `${this.generateDouble().value}+${this.generateDouble().value}i`, type: 'complex', len: 1 }; + case 'raw': return { value: `as.raw(0x${this.rnd.int(256).toString(16).padStart(2, '0')})`, type: 'raw', len: 1 }; + default: return { value: 'NULL', type: 'NULL', len: 1 }; + } + } + + /** + * Generates a string. + * Uses {@link validStringSymbols} + * @returns The generated R source code (`value`), the type 'string' (`type`) and the length of the string (`len`). + */ + generateString(): { value: string, type: string, len: number } { + const len = this.rnd.int(50); + + const weights = [70, 20, 10]; + const total = weights.reduce((a, b) => a + b, 0); + + const pickIndex = () => { + let r = this.rnd.int(total); + for(let i = 0; i < weights.length; i++) { + r -= weights[i]; + if(r < 0) { + return i; + } + } + return weights.length - 1; + }; + + const elements = Array.from({ length: len }, () => + randomString(1, validStringSymbols[pickIndex() as number]) + .replaceAll('\\', '\\\\') + .replaceAll('"', String.raw`\"`) + ).join(''); + return { + value: `"${elements}"`, + type: 'character', + len: elements.length, + }; + } + + /** + * Generates a random literal. + * @returns The generated R source code (`value`), the object's type (`type`) and the length of the object (`len`). + */ + generateLiteral(): { value: string, type: string, len: number } { + return this.rnd.pick([ + () => ({ value: `${this.rnd.pick(['NA_integer_', 'NA_real_', 'NA_complex_', 'NA_character_', 'Inf', '-Inf', 'NaN'])}`, type: 'NaTypes', len: 1 }), + () => this.generateTypedLiteral('logical'), + () => this.generateTypedLiteral('integer'), + () => this.generateTypedLiteral('double'), + () => this.generateTypedLiteral('complex'), + () => this.generateTypedLiteral('character'), + () => this.generateTypedLiteral('raw'), + ])(); + } + + /** + * Generates a double. + * @returns The generated R source code (`value`), the type 'double' (`type`) and the length of the double (`len`). + */ + generateDouble(): { value: string, type: string, len: number } { + return this.rnd.pick([ + () => { + const value = (this.rnd.int(10000) / 100 - 50).toFixed(this.rnd.int(10)); + return { value: `${value}`, type: 'double', len: value.length }; + }, + () => { + const value = this.rnd.int(1e10); + return { value: `${value}`, type: 'double', len: Math.ceil(Math.log10(value + 1)) }; + }, + () => { + const value = `${(this.rnd.int(999))}.${this.rnd.int(999)}e${this.rnd.pick(['+', '-'])}${this.rnd.int(10)}`; + return { value: `${value}`, type: 'double', len: value.length }; + }, + ])(); + } +} + +/** + * Helper class to wrap around a `() => number` RNG + * for generating bounded random integers and picking random array elements. + */ +export class SeededRandom { + /** + * @param rng - A function returning a random float in `[0, 1)`. + */ + constructor(private readonly rng: () => number) {} + + /** + * Generates a random number. + * @param max - maximum size of the number. + * @returns a random integer in `[0, max)`. + */ + int(max: number) { + return Math.floor(this.rng() * max); + } + + /** + * Generates a random element from a given array. + * @param arr - the array to pick from. + * @returns a random element. + */ + pick(arr: T[]) { + return arr[this.int(arr.length)]; + } +} \ No newline at end of file