From ebcff1e218c8bbfcfb71e91454671cbc69fa7ba1 Mon Sep 17 00:00:00 2001 From: Lukas Rohde Date: Sat, 10 May 2025 13:03:57 +0200 Subject: [PATCH 1/4] feat(ast): add `mapAstInfo` for transforming metadata attached to nodes --- src/r-bridge/lang-4.x/ast/model/model.ts | 2 +- .../lang-4.x/ast/model/processing/decorate.ts | 94 ++++++++++++++++++- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/model/model.ts b/src/r-bridge/lang-4.x/ast/model/model.ts index 78e08db983..d9111b893a 100644 --- a/src/r-bridge/lang-4.x/ast/model/model.ts +++ b/src/r-bridge/lang-4.x/ast/model/model.ts @@ -31,7 +31,7 @@ export type NoInfo = object; * Will be used to reconstruct the source of the given element in the R-ast. * This will not be part of most comparisons as it is mainly of interest to the reconstruction of R code. */ -interface Source { +export interface Source { /** * The range is different from the assigned {@link Location} as it refers to the complete source range covered by the given * element. diff --git a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts index 26ac2dbcb2..35f08006f5 100644 --- a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts +++ b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts @@ -9,7 +9,7 @@ * @module */ -import type { NoInfo, RNode } from '../model'; +import type { NoInfo, RNode, Source } from '../model'; import { guard } from '../../../../../util/assert'; import type { SourceRange } from '../../../../../util/range'; import { BiMap } from '../../../../../util/collections/bimap'; @@ -21,11 +21,12 @@ import type { NodeId } from './node-id'; import type { RDelimiter } from '../nodes/info/r-delimiter'; import type { RBinaryOp } from '../nodes/r-binary-op'; import type { RPipe } from '../nodes/r-pipe'; -import type { RFunctionCall, RNamedFunctionCall, RUnnamedFunctionCall } from '../nodes/r-function-call'; +import type { RFunctionArgument, RFunctionCall, RNamedFunctionCall, RUnnamedFunctionCall } from '../nodes/r-function-call'; import { EmptyArgument } from '../nodes/r-function-call'; import type { RExpressionList } from '../nodes/r-expression-list'; import type { RParameter } from '../nodes/r-parameter'; -import type { RArgument } from '../nodes/r-argument'; +import type { RArgument, RUnnamedArgument } from '../nodes/r-argument'; +import type { RSymbol } from '../nodes/r-symbol'; /** * A function that given an RNode returns a (guaranteed) unique id for it @@ -463,3 +464,90 @@ function createFoldForFunctionArgument(info: FoldInfo) { return decorated; }; } + + +export function mapAstInfo(ast: RNode, down: Down, infoMapper: (node: RNode, down: Down) => NewInfo, downUpdater: (node: RNode, down: Down) => Down): RNode { + const fullInfoMapper = (node: RNode, down: Down): NewInfo & Source => { + const sourceInfo = { + ...(node.info.fullRange !== undefined ? { fullRange: node.info.fullRange } : {}), + ...(node.info.fullLexeme !== undefined ? { fullLexeme: node.info.fullLexeme } : {}), + ...(node.info.additionalTokens !== undefined ? { additionalTokens: node.info.additionalTokens } : {}), + ...(node.info.file !== undefined ? { file: node.info.file } : {}) + }; + const info = infoMapper(node, down); + return { ...sourceInfo, ...info }; + }; + + return foldAstStateful(ast, down, { + down: downUpdater, + foldNumber: (num, down) => ({ ...num, info: fullInfoMapper(num, down) } as RNode), + foldString: (str, down) => ({ ...str, info: fullInfoMapper(str, down) }), + foldLogical: (logical, down) => ({ ...logical, info: fullInfoMapper(logical, down) }), + foldSymbol: (symbol, down) => ({ ...symbol, info: fullInfoMapper(symbol, down) }), + foldAccess: (node, name, access, down) => ({ + ...node, + info: fullInfoMapper(node, down), + accessed: name, + access: access as [RUnnamedArgument] + }), + foldBinaryOp: (op, lhs, rhs, down) => ({ ...op, info: fullInfoMapper(op, down), lhs, rhs }), + foldPipe: (op, lhs, rhs, down) => ({ ...op, info: fullInfoMapper(op, down), lhs, rhs }), + foldUnaryOp: (op, operand, down) => ({ ...op, info: fullInfoMapper(op, down), operand }), + loop: { + foldFor: (loop, variable, vector, body, down) => ({ + ...loop, + info: fullInfoMapper(loop, down), + variable: variable as RSymbol, + vector, + body: body as RExpressionList + }), + foldWhile: (loop, condition, body, down) => ({ ...loop, info: fullInfoMapper(loop, down), condition, body: body as RExpressionList }), + foldRepeat: (loop, body, down) => ({ ...loop, info: fullInfoMapper(loop, down), body: body as RExpressionList }), + foldNext: (next, down) => ({ ...next, info: fullInfoMapper(next, down) }), + foldBreak: (next, down) => ({ ...next, info: fullInfoMapper(next, down) }), + }, + other: { + foldComment: (comment, down) => ({ ...comment, info: fullInfoMapper(comment, down) }), + foldLineDirective: (comment, down) => ({ ...comment, info: fullInfoMapper(comment, down) }), + }, + foldIfThenElse: (ifThenExpr, condition, then, otherwise, down ) => ({ ...ifThenExpr, info: fullInfoMapper(ifThenExpr, down), condition, then: then as RExpressionList, otherwise: otherwise as RExpressionList }), + foldExprList: (exprList, grouping, expressions, down) => ({ + ...exprList, + info: fullInfoMapper(exprList, down), + grouping: grouping as [start: RSymbol, end: RSymbol] | undefined, + children: expressions + }), + functions: { + foldFunctionDefinition: (definition, parameters, body, down) => ({ + ...definition, + info: fullInfoMapper(definition, down), + parameters: parameters as RParameter[], + body + }), + /** folds named and unnamed function calls */ + foldFunctionCall: (call, functionNameOrExpression, args, down) => { + const { functionName: _name, calledFunction: _fun, ...rest } = call; + return { + ...rest, + info: fullInfoMapper(call, down), + ...(call.named ? { functionName: functionNameOrExpression as RSymbol } : { calledFunction: functionNameOrExpression }), + arguments: args as readonly RFunctionArgument[] + } as RNamedFunctionCall | RUnnamedFunctionCall; + }, + /** The `name` is `undefined` if the argument is unnamed, the value, if we have something like `x=,...` */ + foldArgument: (argument, name, value, down) => ({ + ...argument, + info: fullInfoMapper(argument, down), + name: name as RSymbol | undefined, + value + }), + /** The `defaultValue` is `undefined` if the argument was not initialized with a default value */ + foldParameter: (parameter, name, defaultValue, down) => ({ + ...parameter, + info: fullInfoMapper(parameter, down), + name: name as RSymbol, + defaultValue + }), + } + }); +} \ No newline at end of file From 14913c48df2eedfee50d48c98a779db6d9b98b88 Mon Sep 17 00:00:00 2001 From: Lukas Rohde Date: Mon, 12 May 2025 12:15:21 +0200 Subject: [PATCH 2/4] refactor(ast): rework `mapAstInfo` to mutate the AST nodes in place --- .../lang-4.x/ast/model/processing/decorate.ts | 91 ++++++------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts index 35f08006f5..dafd57ff73 100644 --- a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts +++ b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts @@ -478,76 +478,43 @@ export function mapAstInfo(ast: RNode, down: Do return { ...sourceInfo, ...info }; }; + function updateInfo(n: RNode, down: Down): RNode { + (n.info as NewInfo) = fullInfoMapper(n, down); + return n as unknown as RNode; + } + return foldAstStateful(ast, down, { - down: downUpdater, - foldNumber: (num, down) => ({ ...num, info: fullInfoMapper(num, down) } as RNode), - foldString: (str, down) => ({ ...str, info: fullInfoMapper(str, down) }), - foldLogical: (logical, down) => ({ ...logical, info: fullInfoMapper(logical, down) }), - foldSymbol: (symbol, down) => ({ ...symbol, info: fullInfoMapper(symbol, down) }), - foldAccess: (node, name, access, down) => ({ - ...node, - info: fullInfoMapper(node, down), - accessed: name, - access: access as [RUnnamedArgument] - }), - foldBinaryOp: (op, lhs, rhs, down) => ({ ...op, info: fullInfoMapper(op, down), lhs, rhs }), - foldPipe: (op, lhs, rhs, down) => ({ ...op, info: fullInfoMapper(op, down), lhs, rhs }), - foldUnaryOp: (op, operand, down) => ({ ...op, info: fullInfoMapper(op, down), operand }), + down: downUpdater, + foldNumber: updateInfo, + foldString: updateInfo, + foldLogical: updateInfo, + foldSymbol: updateInfo, + foldAccess: (node, _name, _access, down) => updateInfo(node, down), + foldBinaryOp: (op, _lhs, _rhs, down) => updateInfo(op, down), + foldPipe: (op, _lhs, _rhs, down) => updateInfo(op, down), + foldUnaryOp: (op, _operand, down) => updateInfo(op, down), loop: { - foldFor: (loop, variable, vector, body, down) => ({ - ...loop, - info: fullInfoMapper(loop, down), - variable: variable as RSymbol, - vector, - body: body as RExpressionList - }), - foldWhile: (loop, condition, body, down) => ({ ...loop, info: fullInfoMapper(loop, down), condition, body: body as RExpressionList }), - foldRepeat: (loop, body, down) => ({ ...loop, info: fullInfoMapper(loop, down), body: body as RExpressionList }), - foldNext: (next, down) => ({ ...next, info: fullInfoMapper(next, down) }), - foldBreak: (next, down) => ({ ...next, info: fullInfoMapper(next, down) }), + foldFor: (loop, _variable, _vector, _body, down) => updateInfo(loop, down), + foldWhile: (loop, _condition, _body, down) => updateInfo(loop, down), + foldRepeat: (loop, _body, down) => updateInfo(loop, down), + foldNext: (next, down) => updateInfo(next, down), + foldBreak: (next, down) => updateInfo(next, down), }, other: { - foldComment: (comment, down) => ({ ...comment, info: fullInfoMapper(comment, down) }), - foldLineDirective: (comment, down) => ({ ...comment, info: fullInfoMapper(comment, down) }), + foldComment: (comment, down) => updateInfo(comment, down), + foldLineDirective: (comment, down) => updateInfo(comment, down), }, - foldIfThenElse: (ifThenExpr, condition, then, otherwise, down ) => ({ ...ifThenExpr, info: fullInfoMapper(ifThenExpr, down), condition, then: then as RExpressionList, otherwise: otherwise as RExpressionList }), - foldExprList: (exprList, grouping, expressions, down) => ({ - ...exprList, - info: fullInfoMapper(exprList, down), - grouping: grouping as [start: RSymbol, end: RSymbol] | undefined, - children: expressions - }), - functions: { - foldFunctionDefinition: (definition, parameters, body, down) => ({ - ...definition, - info: fullInfoMapper(definition, down), - parameters: parameters as RParameter[], - body - }), + foldIfThenElse: (ifThenExpr, _condition, _then, _otherwise, down ) => + updateInfo(ifThenExpr, down), + foldExprList: (exprList, _grouping, _expressions, down) => updateInfo(exprList, down), + functions: { + foldFunctionDefinition: (definition, _parameters, _body, down) => updateInfo(definition, down), /** folds named and unnamed function calls */ - foldFunctionCall: (call, functionNameOrExpression, args, down) => { - const { functionName: _name, calledFunction: _fun, ...rest } = call; - return { - ...rest, - info: fullInfoMapper(call, down), - ...(call.named ? { functionName: functionNameOrExpression as RSymbol } : { calledFunction: functionNameOrExpression }), - arguments: args as readonly RFunctionArgument[] - } as RNamedFunctionCall | RUnnamedFunctionCall; - }, + foldFunctionCall: (call, _functionNameOrExpression, _args, down) => updateInfo(call, down), /** The `name` is `undefined` if the argument is unnamed, the value, if we have something like `x=,...` */ - foldArgument: (argument, name, value, down) => ({ - ...argument, - info: fullInfoMapper(argument, down), - name: name as RSymbol | undefined, - value - }), + foldArgument: (argument, _name, _value, down) => updateInfo(argument, down), /** The `defaultValue` is `undefined` if the argument was not initialized with a default value */ - foldParameter: (parameter, name, defaultValue, down) => ({ - ...parameter, - info: fullInfoMapper(parameter, down), - name: name as RSymbol, - defaultValue - }), + foldParameter: (parameter, _name, _defaultValue, down) => updateInfo(parameter, down) } }); } \ No newline at end of file From 5339c1a5ea9aca082f84fc88c034848ba3b97b17 Mon Sep 17 00:00:00 2001 From: Lukas Rohde Date: Mon, 12 May 2025 12:18:55 +0200 Subject: [PATCH 3/4] refactor(ast): define default for `downUpdater` argument of `mapAstInfo` --- src/r-bridge/lang-4.x/ast/model/processing/decorate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts index dafd57ff73..14c6f324ce 100644 --- a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts +++ b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts @@ -466,7 +466,7 @@ function createFoldForFunctionArgument(info: FoldInfo) { } -export function mapAstInfo(ast: RNode, down: Down, infoMapper: (node: RNode, down: Down) => NewInfo, downUpdater: (node: RNode, down: Down) => Down): RNode { +export function mapAstInfo(ast: RNode, down: Down, infoMapper: (node: RNode, down: Down) => NewInfo, downUpdater: (node: RNode, down: Down) => Down = (_node, down) => down): RNode { const fullInfoMapper = (node: RNode, down: Down): NewInfo & Source => { const sourceInfo = { ...(node.info.fullRange !== undefined ? { fullRange: node.info.fullRange } : {}), From d00ccedf75c58e158d26570b8fb09a8da0380008 Mon Sep 17 00:00:00 2001 From: Lukas Rohde Date: Mon, 12 May 2025 12:41:42 +0200 Subject: [PATCH 4/4] feat(ast): add wrapper of `mapAstInfo` for normalized ASTs --- .../lang-4.x/ast/model/processing/decorate.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts index 14c6f324ce..b15e5da13c 100644 --- a/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts +++ b/src/r-bridge/lang-4.x/ast/model/processing/decorate.ts @@ -21,12 +21,11 @@ import type { NodeId } from './node-id'; import type { RDelimiter } from '../nodes/info/r-delimiter'; import type { RBinaryOp } from '../nodes/r-binary-op'; import type { RPipe } from '../nodes/r-pipe'; -import type { RFunctionArgument, RFunctionCall, RNamedFunctionCall, RUnnamedFunctionCall } from '../nodes/r-function-call'; +import type { RFunctionCall, RNamedFunctionCall, RUnnamedFunctionCall } from '../nodes/r-function-call'; import { EmptyArgument } from '../nodes/r-function-call'; import type { RExpressionList } from '../nodes/r-expression-list'; import type { RParameter } from '../nodes/r-parameter'; -import type { RArgument, RUnnamedArgument } from '../nodes/r-argument'; -import type { RSymbol } from '../nodes/r-symbol'; +import type { RArgument } from '../nodes/r-argument'; /** * A function that given an RNode returns a (guaranteed) unique id for it @@ -474,8 +473,8 @@ export function mapAstInfo(ast: RNode, down: Do ...(node.info.additionalTokens !== undefined ? { additionalTokens: node.info.additionalTokens } : {}), ...(node.info.file !== undefined ? { file: node.info.file } : {}) }; - const info = infoMapper(node, down); - return { ...sourceInfo, ...info }; + const mappedInfo = infoMapper(node, down); + return { ...sourceInfo, ...mappedInfo }; }; function updateInfo(n: RNode, down: Down): RNode { @@ -517,4 +516,20 @@ export function mapAstInfo(ast: RNode, down: Do foldParameter: (parameter, _name, _defaultValue, down) => updateInfo(parameter, down) } }); +} + +export function mapNormalizedAstInfo(normalizedAst: NormalizedAst, down: Down, infoMapper: (node: RNode, down: Down) => NewInfo, downUpdater: (node: RNode, down: Down) => Down = (_node, down) => down): NormalizedAst { + const parentInfoPreservingMapper = (node: RNode, down: Down): NewInfo & ParentInformation => { + const parentInfo = { + id: node.info.id, + parent: node.info.parent, + role: node.info.role, + nesting: node.info.nesting, + index: node.info.index + }; + const mappedInfo = infoMapper(node, down); + return { ...parentInfo, ...mappedInfo }; + }; + mapAstInfo(normalizedAst.ast, down, parentInfoPreservingMapper, downUpdater); + return normalizedAst as unknown as NormalizedAst; } \ No newline at end of file