From a08e74accb8474e7fd135b5d9487d77a95986b51 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:23:41 +0100 Subject: [PATCH 01/88] wip, doc: Comments for questions in reconstruct.ts --- src/reconstruct/reconstruct.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 600f6a34fb..9e6c9fb48f 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -4,6 +4,7 @@ * @module */ +//imports {note: as of current, do not change} import { NormalizedAst, NodeId, @@ -29,16 +30,23 @@ import { import { log, LogLevel } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' + +//question: of interface or why no linebreaks? type Selection = Set + interface PrettyPrintLine { line: string indent: number } + function plain(text: string): PrettyPrintLine[] { return [{ line: text, indent: 0 }] } + type Code = PrettyPrintLine[] +/*end question*/ +//export? just logger? export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -57,6 +65,7 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n)) +//look up exact function function indentBy(lines: Code, indent: number): Code { return lines.map(({ line, indent: i }) => ({ line, indent: i + indent })) } @@ -259,6 +268,7 @@ function reconstructParameters(parameters: RParameter[]): str } +//foldAccess?? Arrayzugriffe function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, node)) { return plain(getLexeme(node)) From e36762036838c41fd0aec77d3a79b45879b90d36 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 7 Nov 2023 17:01:37 +0100 Subject: [PATCH 02/88] wip, refactor: Comments to group parts of reconstruct.ts --- src/reconstruct/reconstruct.ts | 114 +++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 14 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 9e6c9fb48f..5f7a161207 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -31,27 +31,33 @@ import { log, LogLevel } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' -//question: of interface or why no linebreaks? +/* +--helper function-- +*/ type Selection = Set - interface PrettyPrintLine { line: string indent: number } - function plain(text: string): PrettyPrintLine[] { return [{ line: text, indent: 0 }] } - type Code = PrettyPrintLine[] -/*end question*/ -//export? just logger? -export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) +/* +--logger-- +*/ +export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) +/* +--helper function-- +*/ const getLexeme = (n: RNodeWithParent) => n.info.fullLexeme ?? n.lexeme ?? '' +/* +--reconstruct-- +*/ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionConfiguration): Code => { const selectionHasLeaf = configuration.selection.has(leaf.info.id) || configuration.autoSelectIf(leaf) if(selectionHasLeaf) { @@ -66,10 +72,16 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n)) //look up exact function +/* +--helper function-- +*/ function indentBy(lines: Code, indent: number): Code { return lines.map(({ line, indent: i }) => ({ line, indent: i + indent })) } +/* +--recunstruct-- +*/ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, exprList)) { return plain(getLexeme(exprList)) @@ -89,10 +101,16 @@ function reconstructExpressionList(exprList: RExpressionList, } } +/* +--helper function-- +*/ function isSelected(configuration: ReconstructionConfiguration, n: RNode) { return configuration.selection.has(n.info.id) || configuration.autoSelectIf(n) } +/* +--recunstruct-- +*/ function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: PrettyPrintLine[]) { return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), @@ -101,7 +119,9 @@ function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: Pr ] } - +/* +--recunstruct-- +*/ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: ReconstructionConfiguration) { if(configuration.selection.has(leaf.info.id)) { return foldToConst(leaf) @@ -113,6 +133,9 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: } } +/* +--recunstruct-- +*/ function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { return plain(getLexeme(n)) @@ -131,6 +154,9 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe' : n.operator, rhs) } +/* +--recunstruct-- +*/ function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { return plain(getLexeme(loop)) @@ -158,6 +184,9 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } } +/* +--recunstruct-- +*/ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { return plain(getLexeme(loop)) @@ -184,6 +213,9 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } } +/* +--helper function-- +*/ function removeExpressionListWrap(code: Code) { if(code.length > 0 && code[0].line === '{' && code[code.length - 1].line === '}') { return indentBy(code.slice(1, code.length - 1), -1) @@ -192,7 +224,9 @@ function removeExpressionListWrap(code: Code) { } } - +/* +--recunstruct-- +*/ function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse)) @@ -228,7 +262,9 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi } } - +/* +--recunstruct-- +*/ function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { return plain(getLexeme(loop)) @@ -256,6 +292,9 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co } } +/* +--recunstruct-- +*/ function reconstructParameters(parameters: RParameter[]): string[] { // const baseParameters = parameters.flatMap(p => plain(getLexeme(p))) return parameters.map(p => { @@ -269,6 +308,9 @@ function reconstructParameters(parameters: RParameter[]): str //foldAccess?? Arrayzugriffe +/* +--recunstruct-- +*/ function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, node)) { return plain(getLexeme(node)) @@ -285,6 +327,9 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, return plain(getLexeme(node)) } +/* +--recunstruct-- +*/ function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, argument)) { return plain(getLexeme(argument)) @@ -297,7 +342,9 @@ function reconstructArgument(argument: RArgument, name: Code } } - +/* +--recunstruct-- +*/ function reconstructParameter(parameter: RParameter, name: Code, value: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, parameter)) { return plain(getLexeme(parameter)) @@ -312,7 +359,9 @@ function reconstructParameter(parameter: RParameter, name: Co } } - +/* +--recunstruct-- +*/ function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { @@ -342,6 +391,9 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition): Code { guard(args.length === 2, () => `infix special call must have exactly two arguments, got: ${args.length} (${JSON.stringify(args)})`) guard(call.flavor === 'named', `infix special call must be named, got: ${call.flavor}`) @@ -365,6 +417,9 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`) } +/* +--recunstruct-- +*/ function reconstructFunctionCall(call: RFunctionCall, functionName: Code, args: (Code | undefined)[], configuration: ReconstructionConfiguration): Code { if(call.infixSpecial === true) { return reconstructSpecialInfixFunctionCall(args, call) @@ -393,22 +448,36 @@ function reconstructFunctionCall(call: RFunctionCall, functio } } +/* +--helper function-- +*/ /** The structure of the predicate that should be used to determine if a given normalized node should be included in the reconstructed code independent of if it is selected by the slice or not */ export type AutoSelectPredicate = (node: RNode) => boolean - +/* +--interface-- +*/ interface ReconstructionConfiguration extends MergeableRecord { selection: Selection /** if true, this will force the ast part to be reconstructed, this can be used, for example, to force include `library` statements */ autoSelectIf: AutoSelectPredicate } +/* +--helper function-- +*/ export function doNotAutoSelect(_node: RNode): boolean { return false } +/* +--helper function-- +*/ const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/ +/* +--helper function-- +*/ export function autoSelectLibrary(node: RNode): boolean { if(node.type !== RType.FunctionCall || node.flavor !== 'named') { return false @@ -417,6 +486,9 @@ export function autoSelectLibrary(node: RNode): boolean { } +/* +--reconstruct-- +*/ /** * The fold functions used to reconstruct the ast in {@link reconstructToCode}. */ @@ -464,21 +536,32 @@ const reconstructAstFolds: StatefulFoldFunctions `${getIndentString(indent)}${line}`).join(lf) } +/* +--interface-- +*/ export interface ReconstructionResult { code: string /** number of nodes that triggered the `autoSelectIf` predicate {@link reconstructToCode} */ autoSelected: number } +/* +--helper function-- +*/ function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { if(result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { // remove outer block @@ -488,6 +571,9 @@ function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSe } } +/* +--main function-- +*/ /** * Reconstructs parts of a normalized R ast into R code on an expression basis. * From 19041f353f66255cd52bb9cec518347dc00040ba Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 13 Nov 2023 15:31:06 +0100 Subject: [PATCH 03/88] wip, refactor: refactor of 'reconstructToCode' into main.ts and seperated its import in core\steps.ts --- src/core/steps.ts | 3 ++- src/reconstruct/main.ts | 43 ++++++++++++++++++++++++++++++++ src/reconstruct/reconstruct.ts | 45 +++------------------------------- 3 files changed, 49 insertions(+), 42 deletions(-) create mode 100644 src/reconstruct/main.ts diff --git a/src/core/steps.ts b/src/core/steps.ts index 160bda4856..4680c7f4f1 100644 --- a/src/core/steps.ts +++ b/src/core/steps.ts @@ -19,7 +19,8 @@ import { retrieveXmlFromRCode } from '../r-bridge' import { produceDataFlowGraph } from '../dataflow' -import { reconstructToCode, staticSlicing } from '../slicing' +import { staticSlicing } from '../slicing' +import { reconstructToCode } from '../reconstruct/main' import { internalPrinter, IStepPrinter, StepOutputFormat } from './print/print' import { normalizedAstToJson, normalizedAstToQuads } from './print/normalize-printer' import { guard } from '../util/assert' diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts new file mode 100644 index 0000000000..03dabb442e --- /dev/null +++ b/src/reconstruct/main.ts @@ -0,0 +1,43 @@ +import { Selection, AutoSelectPredicate, autoSelectLibrary, ReconstructionResult, reconstructLogger, reconstructAstFolds, removeOuterExpressionListIfApplicable } from './reconstruct'; + +import { NormalizedAst, RNode, ParentInformation, foldAstStateful } from '../r-bridge'; +import { LogLevel } from '../util/log'; + + +/* +--main function-- +*/ +/** + * Reconstructs parts of a normalized R ast into R code on an expression basis. + * + * @param ast - The {@link NormalizedAst|normalized ast} to be used as a basis for reconstruction + * @param selection - The selection of nodes to be reconstructed (probably the {@link NodeId|NodeIds} identified by the slicer) + * @param autoSelectIf - A predicate that can be used to force the reconstruction of a node (for example to reconstruct library call statements, see {@link autoSelectLibrary}, {@link doNotAutoSelect}) + * + * @returns The number of times `autoSelectIf` triggered, as well as the reconstructed code itself. + */ + +export function reconstructToCode(ast: NormalizedAst, selection: Selection, autoSelectIf: AutoSelectPredicate = autoSelectLibrary): ReconstructionResult { + if (reconstructLogger.settings.minLevel >= LogLevel.Trace) { + reconstructLogger.trace(`reconstruct ast with ids: ${JSON.stringify([...selection])}`) + } + + // we use a wrapper to count the number of times the autoSelectIf predicate triggered + let autoSelected = 0 + const autoSelectIfWrapper = (node: RNode) => { + const result = autoSelectIf(node) + if (result) { + autoSelected++ + } + return result + } + + // fold of the normalized ast + const result = foldAstStateful(ast.ast, { selection, autoSelectIf: autoSelectIfWrapper }, reconstructAstFolds) + + if (reconstructLogger.settings.minLevel >= LogLevel.Trace) { + reconstructLogger.trace('reconstructed ast before string conversion: ', JSON.stringify(result)) + } + + return removeOuterExpressionListIfApplicable(result, autoSelected) +} diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 5f7a161207..99dfebab12 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -6,7 +6,6 @@ //imports {note: as of current, do not change} import { - NormalizedAst, NodeId, ParentInformation, RAccess, @@ -24,17 +23,16 @@ import { RWhileLoop, RType, RPipe, - foldAstStateful, StatefulFoldFunctions } from '../r-bridge' -import { log, LogLevel } from '../util/log' +import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' /* --helper function-- */ -type Selection = Set +export type Selection = Set interface PrettyPrintLine { line: string indent: number @@ -493,7 +491,7 @@ export function autoSelectLibrary(node: RNode): boolean { * The fold functions used to reconstruct the ast in {@link reconstructToCode}. */ // escalates with undefined if all are undefined -const reconstructAstFolds: StatefulFoldFunctions = { +export const reconstructAstFolds: StatefulFoldFunctions = { // we just pass down the state information so everyone has them down: (_n, c) => c, foldNumber: reconstructAsLeaf, @@ -562,7 +560,7 @@ export interface ReconstructionResult { /* --helper function-- */ -function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { +export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { if(result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { // remove outer block return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), autoSelected } @@ -571,39 +569,4 @@ function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSe } } -/* ---main function-- -*/ -/** - * Reconstructs parts of a normalized R ast into R code on an expression basis. - * - * @param ast - The {@link NormalizedAst|normalized ast} to be used as a basis for reconstruction - * @param selection - The selection of nodes to be reconstructed (probably the {@link NodeId|NodeIds} identified by the slicer) - * @param autoSelectIf - A predicate that can be used to force the reconstruction of a node (for example to reconstruct library call statements, see {@link autoSelectLibrary}, {@link doNotAutoSelect}) - * - * @returns The number of times `autoSelectIf` triggered, as well as the reconstructed code itself. - */ -export function reconstructToCode(ast: NormalizedAst, selection: Selection, autoSelectIf: AutoSelectPredicate = autoSelectLibrary): ReconstructionResult { - if(reconstructLogger.settings.minLevel >= LogLevel.Trace) { - reconstructLogger.trace(`reconstruct ast with ids: ${JSON.stringify([...selection])}`) - } - - // we use a wrapper to count the number of times the autoSelectIf predicate triggered - let autoSelected = 0 - const autoSelectIfWrapper = (node: RNode) => { - const result = autoSelectIf(node) - if(result) { - autoSelected++ - } - return result - } - - // fold of the normalized ast - const result = foldAstStateful(ast.ast, { selection, autoSelectIf: autoSelectIfWrapper }, reconstructAstFolds) - if(reconstructLogger.settings.minLevel >= LogLevel.Trace) { - reconstructLogger.trace('reconstructed ast before string conversion: ', JSON.stringify(result)) - } - - return removeOuterExpressionListIfApplicable(result, autoSelected) -} From e5d9a30865456d71b4975164f9218b2615830f0e Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:04:43 +0100 Subject: [PATCH 04/88] typo: corrected typo of reconstruct in reconstruct.ts --- src/reconstruct/reconstruct.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 99dfebab12..f576fb8ae2 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -78,7 +78,7 @@ function indentBy(lines: Code, indent: number): Code { } /* ---recunstruct-- +--reconstruct-- */ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, exprList)) { @@ -107,7 +107,7 @@ function isSelected(configuration: ReconstructionConfiguration, n: RNode | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { @@ -153,7 +153,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { @@ -183,7 +183,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } /* ---recunstruct-- +--reconstruct-- */ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { @@ -223,7 +223,7 @@ function removeExpressionListWrap(code: Code) { } /* ---recunstruct-- +--reconstruct-- */ function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, ifThenElse)) { @@ -261,7 +261,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi } /* ---recunstruct-- +--reconstruct-- */ function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { @@ -291,7 +291,7 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co } /* ---recunstruct-- +--reconstruct-- */ function reconstructParameters(parameters: RParameter[]): string[] { // const baseParameters = parameters.flatMap(p => plain(getLexeme(p))) @@ -307,7 +307,7 @@ function reconstructParameters(parameters: RParameter[]): str //foldAccess?? Arrayzugriffe /* ---recunstruct-- +--reconstruct-- */ function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, node)) { @@ -326,7 +326,7 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, } /* ---recunstruct-- +--reconstruct-- */ function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, argument)) { @@ -341,7 +341,7 @@ function reconstructArgument(argument: RArgument, name: Code } /* ---recunstruct-- +--reconstruct-- */ function reconstructParameter(parameter: RParameter, name: Code, value: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, parameter)) { @@ -358,7 +358,7 @@ function reconstructParameter(parameter: RParameter, name: Co } /* ---recunstruct-- +--reconstruct-- */ function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition @@ -390,7 +390,7 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition): Code { guard(args.length === 2, () => `infix special call must have exactly two arguments, got: ${args.length} (${JSON.stringify(args)})`) @@ -416,7 +416,7 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R } /* ---recunstruct-- +--reconstruct-- */ function reconstructFunctionCall(call: RFunctionCall, functionName: Code, args: (Code | undefined)[], configuration: ReconstructionConfiguration): Code { if(call.infixSpecial === true) { From 5c90575920b4fdf43dd620d3f21a25e4f544ef85 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 13 Nov 2023 16:24:39 +0100 Subject: [PATCH 05/88] wip, refactor: refactor of 'helper functions', as declared in earlier commit, into new file of helper.ts. seperated import of AutoSelectPredicate in input.ts. --- src/core/input.ts | 3 +- src/reconstruct/helper.ts | 97 +++++++++++++++++++++++++++++ src/reconstruct/main.ts | 6 +- src/reconstruct/reconstruct.ts | 108 +++------------------------------ 4 files changed, 114 insertions(+), 100 deletions(-) create mode 100644 src/reconstruct/helper.ts diff --git a/src/core/input.ts b/src/core/input.ts index 59ac663dfd..f854e1a72d 100644 --- a/src/core/input.ts +++ b/src/core/input.ts @@ -1,7 +1,8 @@ import { MergeableRecord } from '../util/objects' import { IdGenerator, NoInfo, RParseRequest, RShell, XmlParserHooks } from '../r-bridge' import { DeepPartial } from 'ts-essentials' -import { AutoSelectPredicate, SlicingCriteria } from '../slicing' +import { SlicingCriteria } from '../slicing' +import { AutoSelectPredicate } from '../reconstruct/helper' import { STEPS_PER_SLICE, StepName, STEPS_PER_FILE } from './steps' /** diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts new file mode 100644 index 0000000000..af786db91d --- /dev/null +++ b/src/reconstruct/helper.ts @@ -0,0 +1,97 @@ +import { NodeId, ParentInformation, RNode, RType } from '../r-bridge'; +import { ReconstructionConfiguration } from './reconstruct'; + + +/* +--helper function-- +*/ +export type Selection = Set +export interface PrettyPrintLine { + line: string + indent: number +} +export function plain(text: string): PrettyPrintLine[] { + return [{ line: text, indent: 0 }] +} +export type Code = PrettyPrintLine[] + +//look up exact function +/* +--helper function-- +*/ +export function indentBy(lines: Code, indent: number): Code { + return lines.map(({ line, indent: i }) => ({ line, indent: i + indent })) +} + +/* +--helper function-- +*/ +export function isSelected(configuration: ReconstructionConfiguration, n: RNode) { + return configuration.selection.has(n.info.id) || configuration.autoSelectIf(n) +} + +/* +--helper function-- +*/ +export function removeExpressionListWrap(code: Code) { + if (code.length > 0 && code[0].line === '{' && code[code.length - 1].line === '}') { + return indentBy(code.slice(1, code.length - 1), -1) + } else { + return code + } +} + +/* +--helper function-- +*/ +/** The structure of the predicate that should be used to determine if a given normalized node should be included in the reconstructed code independent of if it is selected by the slice or not */ +export type AutoSelectPredicate = (node: RNode) => boolean + +/* +--helper function-- +*/ +export function doNotAutoSelect(_node: RNode): boolean { + return false +} + +/* +--helper function-- +*/ +export const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/ + +/* +--helper function-- +*/ +export function autoSelectLibrary(node: RNode): boolean { + if (node.type !== RType.FunctionCall || node.flavor !== 'named') { + return false + } + return libraryFunctionCall.test(node.functionName.content) +} + +/* +--helper function-- +*/ +export function getIndentString(indent: number): string { + return ' '.repeat(indent * 4) +} + +/* +--helper function-- +*/ +export function prettyPrintCodeToString(code: Code, lf = '\n'): string { + return code.map(({ line, indent }) => `${getIndentString(indent)}${line}`).join(lf) +} + +/* +--helper function-- +*/ +export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { + if (result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { + // remove outer block + return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), autoSelected } + } else { + return { code: prettyPrintCodeToString(result), autoSelected } + } +} + diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index 03dabb442e..a80181aad8 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,4 +1,8 @@ -import { Selection, AutoSelectPredicate, autoSelectLibrary, ReconstructionResult, reconstructLogger, reconstructAstFolds, removeOuterExpressionListIfApplicable } from './reconstruct'; +import { ReconstructionResult, reconstructLogger, reconstructAstFolds } from './reconstruct'; +import { removeOuterExpressionListIfApplicable } from './helper'; +import { autoSelectLibrary } from './helper'; +import { AutoSelectPredicate } from './helper'; +import { Selection } from './helper'; import { NormalizedAst, RNode, ParentInformation, foldAstStateful } from '../r-bridge'; import { LogLevel } from '../util/log'; diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index f576fb8ae2..0c7f3e953a 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -6,7 +6,6 @@ //imports {note: as of current, do not change} import { - NodeId, ParentInformation, RAccess, RArgument, @@ -16,7 +15,6 @@ import { RFunctionCall, RFunctionDefinition, RIfThenElse, - RNode, RNodeWithParent, RParameter, RRepeatLoop, @@ -28,20 +26,15 @@ import { import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' - -/* ---helper function-- -*/ -export type Selection = Set -interface PrettyPrintLine { - line: string - indent: number -} -function plain(text: string): PrettyPrintLine[] { - return [{ line: text, indent: 0 }] -} -type Code = PrettyPrintLine[] - +import { Selection } from './helper' +import { PrettyPrintLine } from './helper' +import { plain } from './helper' +import { Code } from './helper' +import { indentBy } from './helper' +import { isSelected } from './helper' +import { removeExpressionListWrap } from './helper' +import { AutoSelectPredicate } from './helper' +import { getIndentString } from './helper' /* --logger-- @@ -69,14 +62,6 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n)) -//look up exact function -/* ---helper function-- -*/ -function indentBy(lines: Code, indent: number): Code { - return lines.map(({ line, indent: i }) => ({ line, indent: i + indent })) -} - /* --reconstruct-- */ @@ -99,13 +84,6 @@ function reconstructExpressionList(exprList: RExpressionList, } } -/* ---helper function-- -*/ -function isSelected(configuration: ReconstructionConfiguration, n: RNode) { - return configuration.selection.has(n.info.id) || configuration.autoSelectIf(n) -} - /* --reconstruct-- */ @@ -211,17 +189,6 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } } -/* ---helper function-- -*/ -function removeExpressionListWrap(code: Code) { - if(code.length > 0 && code[0].line === '{' && code[code.length - 1].line === '}') { - return indentBy(code.slice(1, code.length - 1), -1) - } else { - return code - } -} - /* --reconstruct-- */ @@ -446,44 +413,15 @@ function reconstructFunctionCall(call: RFunctionCall, functio } } -/* ---helper function-- -*/ -/** The structure of the predicate that should be used to determine if a given normalized node should be included in the reconstructed code independent of if it is selected by the slice or not */ -export type AutoSelectPredicate = (node: RNode) => boolean - /* --interface-- */ -interface ReconstructionConfiguration extends MergeableRecord { +export interface ReconstructionConfiguration extends MergeableRecord { selection: Selection /** if true, this will force the ast part to be reconstructed, this can be used, for example, to force include `library` statements */ autoSelectIf: AutoSelectPredicate } -/* ---helper function-- -*/ -export function doNotAutoSelect(_node: RNode): boolean { - return false -} - -/* ---helper function-- -*/ -const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/ - -/* ---helper function-- -*/ -export function autoSelectLibrary(node: RNode): boolean { - if(node.type !== RType.FunctionCall || node.flavor !== 'named') { - return false - } - return libraryFunctionCall.test(node.functionName.content) -} - - /* --reconstruct-- */ @@ -534,20 +472,6 @@ export const reconstructAstFolds: StatefulFoldFunctions `${getIndentString(indent)}${line}`).join(lf) -} - /* --interface-- */ @@ -557,16 +481,4 @@ export interface ReconstructionResult { autoSelected: number } -/* ---helper function-- -*/ -export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { - if(result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { - // remove outer block - return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), autoSelected } - } else { - return { code: prettyPrintCodeToString(result), autoSelected } - } -} - From bda524a6c7b5d1d0fa7c2d3318643fa8095d5ece Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 24 Nov 2023 14:22:40 +0100 Subject: [PATCH 06/88] wip, feat: Added parts to PrettyPrintLine --- src/reconstruct/helper.ts | 65 +++++++++++++++++++++++++++++----- src/reconstruct/main.ts | 20 +++++------ src/reconstruct/reconstruct.ts | 40 ++++++++++----------- 3 files changed, 87 insertions(+), 38 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index af786db91d..9024839707 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,20 +1,69 @@ -import { NodeId, ParentInformation, RNode, RType } from '../r-bridge'; -import { ReconstructionConfiguration } from './reconstruct'; +import { NodeId, ParentInformation, RNode, RType } from '../r-bridge' +import { ReconstructionConfiguration } from './reconstruct' /* --helper function-- */ export type Selection = Set +interface PrettyPrintLinePart { + part: string + spaces: number +} export interface PrettyPrintLine { - line: string + line: string indent: number } -export function plain(text: string): PrettyPrintLine[] { - return [{ line: text, indent: 0 }] +export interface WrappedPrettyPrintLine { //change name to be more fitting + linePart: PrettyPrintLinePart[] + indent: number +} +export function plain(text: string,space: number): Code { + return [unwrapPrettyPrintLine({linePart: [{part: text,spaces: space}],indent: 0})] } export type Code = PrettyPrintLine[] +export function unwrapPrettyPrintLine(wrappedLine: WrappedPrettyPrintLine): PrettyPrintLine { + const text = '' + for(let index = 0; index < wrappedLine.linePart.length; index++) { + const element = wrappedLine.linePart[index] + for(let indent = 0; indent < wrappedLine.linePart[index].spaces; indent++) { + text === text + ' ' + } + text === text + wrappedLine.linePart[index].part + } + return {line: text,indent: 0} +} + +export function wrappPrettyPrintLine(code: Code): WrappedPrettyPrintLine[] { + let result:WrappedPrettyPrintLine[] = [] + for(let line = 0; line < code.length; line++) { + const wrappedLine: WrappedPrettyPrintLine = {linePart: [], indent: code[line].indent} + for(let i = 0; i < code[line].line.length; i++) { + let space = 0 + let linePart = '' + let newPart = true + if(code[line].line[i] === ' ') { + if(newPart) { + space = space + 1 + } + else { + const part: PrettyPrintLinePart = {part: linePart,spaces: space} + wrappedLine.linePart = wrappedLine.linePart.concat(part) + space = 0 + newPart = true + } + } + else { + newPart = false + linePart = linePart + code[line].line[i] + } + } + result = result.concat(wrappedLine) + } + return result +} + //look up exact function /* --helper function-- @@ -34,7 +83,7 @@ export function isSelected(configuration: ReconstructionConfiguration, n: RNode< --helper function-- */ export function removeExpressionListWrap(code: Code) { - if (code.length > 0 && code[0].line === '{' && code[code.length - 1].line === '}') { + if(code.length > 0 && code[0].line === '{' && code[code.length - 1].line === '}') { return indentBy(code.slice(1, code.length - 1), -1) } else { return code @@ -63,7 +112,7 @@ export const libraryFunctionCall = /^(library|require|((require|load|attach)Name --helper function-- */ export function autoSelectLibrary(node: RNode): boolean { - if (node.type !== RType.FunctionCall || node.flavor !== 'named') { + if(node.type !== RType.FunctionCall || node.flavor !== 'named') { return false } return libraryFunctionCall.test(node.functionName.content) @@ -87,7 +136,7 @@ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { --helper function-- */ export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { - if (result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { + if(result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { // remove outer block return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), autoSelected } } else { diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index a80181aad8..6059cb20ae 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,11 +1,11 @@ -import { ReconstructionResult, reconstructLogger, reconstructAstFolds } from './reconstruct'; -import { removeOuterExpressionListIfApplicable } from './helper'; -import { autoSelectLibrary } from './helper'; -import { AutoSelectPredicate } from './helper'; -import { Selection } from './helper'; +import { ReconstructionResult, reconstructLogger, reconstructAstFolds } from './reconstruct' +import { removeOuterExpressionListIfApplicable } from './helper' +import { autoSelectLibrary } from './helper' +import { AutoSelectPredicate } from './helper' +import { Selection } from './helper' -import { NormalizedAst, RNode, ParentInformation, foldAstStateful } from '../r-bridge'; -import { LogLevel } from '../util/log'; +import { NormalizedAst, RNode, ParentInformation, foldAstStateful } from '../r-bridge' +import { LogLevel } from '../util/log' /* @@ -22,7 +22,7 @@ import { LogLevel } from '../util/log'; */ export function reconstructToCode(ast: NormalizedAst, selection: Selection, autoSelectIf: AutoSelectPredicate = autoSelectLibrary): ReconstructionResult { - if (reconstructLogger.settings.minLevel >= LogLevel.Trace) { + if(reconstructLogger.settings.minLevel >= LogLevel.Trace) { reconstructLogger.trace(`reconstruct ast with ids: ${JSON.stringify([...selection])}`) } @@ -30,7 +30,7 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel let autoSelected = 0 const autoSelectIfWrapper = (node: RNode) => { const result = autoSelectIf(node) - if (result) { + if(result) { autoSelected++ } return result @@ -39,7 +39,7 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel // fold of the normalized ast const result = foldAstStateful(ast.ast, { selection, autoSelectIf: autoSelectIfWrapper }, reconstructAstFolds) - if (reconstructLogger.settings.minLevel >= LogLevel.Trace) { + if(reconstructLogger.settings.minLevel >= LogLevel.Trace) { reconstructLogger.trace('reconstructed ast before string conversion: ', JSON.stringify(result)) } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 0c7f3e953a..d58d0386dd 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -60,14 +60,14 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC // return selectionHasLeaf ? wouldBe : [] } -const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n)) +const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start.column : 0) /* --reconstruct-- */ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, exprList)) { - return plain(getLexeme(exprList)) + return plain(getLexeme(exprList), exprList.location? exprList.location.start.column : 0) } const subExpressions = expressions.filter(e => e.length > 0) @@ -114,7 +114,7 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: */ function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { - return plain(getLexeme(n)) + return plain(getLexeme(n),n.location? n.location.start.column : 0) } if(lhs.length === 0 && rhs.length === 0) { @@ -124,7 +124,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe' : n.operator, rhs) @@ -135,7 +135,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { - return plain(getLexeme(loop)) + return plain(getLexeme(loop), loop.location? loop.location.start.column : 0) } if(body.length === 0 && variable.length === 0 && vector.length === 0) { return [] @@ -165,7 +165,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v */ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { - return plain(getLexeme(loop)) + return plain(getLexeme(loop), loop.location? loop.location.start.column : 0) } else if(body.length === 0) { return [] } else { @@ -194,7 +194,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, */ function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, ifThenElse)) { - return plain(getLexeme(ifThenElse)) + return plain(getLexeme(ifThenElse), ifThenElse.location? ifThenElse.location.start.column : 0) } otherwise ??= [] if(condition.length === 0 && when.length === 0 && otherwise.length === 0) { @@ -232,7 +232,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi */ function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { - return plain(getLexeme(loop)) + return plain(getLexeme(loop), loop.location? loop.location.start.column : 0) } if(body.length === 0 && condition.length === 0) { return [] @@ -278,7 +278,7 @@ function reconstructParameters(parameters: RParameter[]): str */ function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, node)) { - return plain(getLexeme(node)) + return plain(getLexeme(node), node.location? node.location.start.column : 0) } if(accessed.length === 0) { @@ -289,7 +289,7 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, } } - return plain(getLexeme(node)) + return plain(getLexeme(node), node.location? node.location.start.column : 0) } /* @@ -297,11 +297,11 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, */ function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, argument)) { - return plain(getLexeme(argument)) + return plain(getLexeme(argument), argument.location? argument.location.start.column : 0) } if(argument.name !== undefined && name !== undefined && name.length > 0) { - return plain(`${getLexeme(argument.name)}=${argument.value ? getLexeme(argument.value) : ''}`) + return plain(`${getLexeme(argument.name)}=${argument.value ? getLexeme(argument.value) : ''}`, argument.location? argument.location.start.column : 0) } else { return value ?? [] } @@ -312,13 +312,13 @@ function reconstructArgument(argument: RArgument, name: Code */ function reconstructParameter(parameter: RParameter, name: Code, value: Code | undefined, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, parameter)) { - return plain(getLexeme(parameter)) + return plain(getLexeme(parameter), parameter.location? parameter.location.start.column : 0) } if(parameter.defaultValue !== undefined && name.length > 0) { - return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`) + return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`, parameter.location? parameter.location.start.column : 0) } else if(parameter.defaultValue !== undefined && name.length === 0) { - return plain(getLexeme(parameter.defaultValue)) + return plain(getLexeme(parameter.defaultValue), parameter.location? parameter.location.start.column : 0) } else { return name } @@ -374,12 +374,12 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R const lhsText = lhs.map(l => `${getIndentString(l.indent)}${l.line}`).join('\n') if(rhs !== undefined && rhs.length > 0) { const rhsText = rhs.map(l => `${getIndentString(l.indent)}${l.line}`).join('\n') - return plain(`${lhsText} ${call.functionName.content} ${rhsText}`) + return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location? call.location.start.column : 0) } else { - return plain(lhsText) + return plain(lhsText, call.location? call.location.start.column : 0) } } - return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`) + return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`, call.location? call.location.start.column : 0) } /* @@ -390,7 +390,7 @@ function reconstructFunctionCall(call: RFunctionCall, functio return reconstructSpecialInfixFunctionCall(args, call) } if(call.flavor === 'named' && isSelected(configuration, call)) { - return plain(getLexeme(call)) + return plain(getLexeme(call), call.location? call.location.start.column : 0) } const filteredArgs = args.filter(a => a !== undefined && a.length > 0) if(functionName.length === 0 && filteredArgs.length === 0) { @@ -409,7 +409,7 @@ function reconstructFunctionCall(call: RFunctionCall, functio } return [{ line: functionName[0].line, indent: functionName[0].indent }] } else { - return plain(getLexeme(call)) + return plain(getLexeme(call), call.location? call.location.start.column : 0) } } From 5995277eb0e557bd1114ea7244df3b49ea18f894 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:09:05 +0100 Subject: [PATCH 07/88] feat(reconstruct): lineParts now work with locations *WIP* plain and merge have been implemented, however functionality could not be tested due to single error from reconstructRawBinaryOperator returning wrong type --- src/reconstruct/helper.ts | 96 ++++++++++++------------ src/reconstruct/reconstruct.ts | 130 ++++++++++++++++++--------------- 2 files changed, 119 insertions(+), 107 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 9024839707..fd1cadd543 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,4 +1,5 @@ import { NodeId, ParentInformation, RNode, RType } from '../r-bridge' +import { SourcePosition } from '../util/range' import { ReconstructionConfiguration } from './reconstruct' @@ -8,68 +9,69 @@ import { ReconstructionConfiguration } from './reconstruct' export type Selection = Set interface PrettyPrintLinePart { part: string - spaces: number + loc: SourcePosition } export interface PrettyPrintLine { - line: string - indent: number -} -export interface WrappedPrettyPrintLine { //change name to be more fitting linePart: PrettyPrintLinePart[] - indent: number -} -export function plain(text: string,space: number): Code { - return [unwrapPrettyPrintLine({linePart: [{part: text,spaces: space}],indent: 0})] + indent: number } -export type Code = PrettyPrintLine[] -export function unwrapPrettyPrintLine(wrappedLine: WrappedPrettyPrintLine): PrettyPrintLine { - const text = '' - for(let index = 0; index < wrappedLine.linePart.length; index++) { - const element = wrappedLine.linePart[index] - for(let indent = 0; indent < wrappedLine.linePart[index].spaces; indent++) { - text === text + ' ' +/** + * Splits text on '\n' to create lineParts and encapsulates them in the Code type + * @param text lexeme which needs to be converted to Code + * @param location of the lexeme within the R Code + * @returns text and location converted into the Code type + */ +export function plain(text: string, location: SourcePosition): Code { + let part = "" + let printLine: PrettyPrintLine = {linePart: [], indent: 0} + for (let character = 0; character < text.length; character++) { + const element = text[character] + if(element === '\n') { + printLine.linePart.concat({part: part, loc: location}) + } + else { + part = part.concat(element) } - text === text + wrappedLine.linePart[index].part } - return {line: text,indent: 0} + return [printLine] } -export function wrappPrettyPrintLine(code: Code): WrappedPrettyPrintLine[] { - let result:WrappedPrettyPrintLine[] = [] - for(let line = 0; line < code.length; line++) { - const wrappedLine: WrappedPrettyPrintLine = {linePart: [], indent: code[line].indent} - for(let i = 0; i < code[line].line.length; i++) { - let space = 0 - let linePart = '' - let newPart = true - if(code[line].line[i] === ' ') { - if(newPart) { - space = space + 1 - } - else { - const part: PrettyPrintLinePart = {part: linePart,spaces: space} - wrappedLine.linePart = wrappedLine.linePart.concat(part) - space = 0 - newPart = true - } - } - else { - newPart = false - linePart = linePart + code[line].line[i] - } - } - result = result.concat(wrappedLine) +/** + * this function will merge up to n code pieces into a singular code piece, garanting that there are no duplicate lines and all lines are in order + * @param snipbits list of code snipbits that need to be merged + * @returns the snipbits array as a single merged code element + */ +export function merge(snipbits: Code[]): Code { + let buckets: PrettyPrintLine[] = [] + let result:Code = [] + + //seperate and group lineParts by lines + snipbits.forEach(code => { + code.forEach(line => { + line.linePart.forEach(part => { + const line = part.loc.line + buckets[line].linePart.concat(part) + }) + }) + }); + + //sort buckets by column and stich lines into single code piece + for (const line in buckets) { + buckets[line].linePart.sort((a, b) => a.loc.column - b.loc.column) + result.concat(buckets[line]) } + return result } +export type Code = PrettyPrintLine[] //look up exact function /* --helper function-- */ export function indentBy(lines: Code, indent: number): Code { - return lines.map(({ line, indent: i }) => ({ line, indent: i + indent })) + return lines.map(({ linePart, indent: i }) => ({ linePart, indent: i + indent })) } /* @@ -83,7 +85,7 @@ export function isSelected(configuration: ReconstructionConfiguration, n: RNode< --helper function-- */ export function removeExpressionListWrap(code: Code) { - if(code.length > 0 && code[0].line === '{' && code[code.length - 1].line === '}') { + if(code.length > 0 && code[0].linePart[0].part === '{' && code[code.length - 1].linePart[code[code.length - 1].linePart.length - 1].part === '}') { return indentBy(code.slice(1, code.length - 1), -1) } else { return code @@ -129,14 +131,14 @@ export function getIndentString(indent: number): string { --helper function-- */ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { - return code.map(({ line, indent }) => `${getIndentString(indent)}${line}`).join(lf) + return code.map(({ linePart, indent }) => `${getIndentString(indent)}${linePart}`).join(lf) } /* --helper function-- */ export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { - if(result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { + if(result.length > 1 && result[0].linePart[0].part === '{' && result[result.length - 1].linePart[result[result.length - 1].linePart.length - 1].part === '}') { // remove outer block return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), autoSelected } } else { diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index d58d0386dd..c0f41ed8c1 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -60,14 +60,15 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC // return selectionHasLeaf ? wouldBe : [] } -const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start.column : 0) +const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start : {line: 0, column: 0}) /* --reconstruct-- */ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { + let position = exprList.location? exprList.location.start : {line: 0, column: 0} if(isSelected(configuration, exprList)) { - return plain(getLexeme(exprList), exprList.location? exprList.location.start.column : 0) + return plain(getLexeme(exprList), position) } const subExpressions = expressions.filter(e => e.length > 0) @@ -77,9 +78,9 @@ function reconstructExpressionList(exprList: RExpressionList, return subExpressions[0] } else { return [ - { line: '{', indent: 0 }, + { linePart: [{part: '{', loc: position}] , indent: 0 }, ...indentBy(subExpressions.flat(), 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: position}], indent: 0 } ] } } @@ -90,7 +91,7 @@ function reconstructExpressionList(exprList: RExpressionList, function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: PrettyPrintLine[]) { return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), - { line: `${lhs[lhs.length - 1].line} ${n} ${rhs[0].line}`, indent: 0 }, + { line: `${lhs[lhs.length - 1].linePart[lhs.length - 1]} ${n} ${rhs[0].linePart[0]}`, indent: 0 }, ...indentBy(rhs.slice(1, rhs.length), 1) ] } @@ -114,7 +115,7 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: */ function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { - return plain(getLexeme(n),n.location? n.location.start.column : 0) + return plain(getLexeme(n), n.location.start) } if(lhs.length === 0 && rhs.length === 0) { @@ -124,7 +125,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe' : n.operator, rhs) @@ -135,25 +136,25 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), loop.location? loop.location.start.column : 0) + return plain(getLexeme(loop), loop.location.start) } if(body.length === 0 && variable.length === 0 && vector.length === 0) { return [] } else { if(body.length <= 1) { // 'inline' - return [{ line: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].line}`, indent: 0 }] - } else if(body[0].line === '{' && body[body.length - 1].line === '}') { + return [{ linePart: [{part:`for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].linePart}`,loc: loop.location.start}], indent: 0 }] + } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ - { line: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) {`, indent: 0 }, + { linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) {`, loc: loop.location.start}], indent: 0 }, ...body.slice(1, body.length - 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: loop.location.start}], indent: 0 } ] } else { // unknown return [ - { line: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, indent: 0 }, + { linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: loop.location.start}], indent: 0 }, ...indentBy(body, 1) ] } @@ -165,24 +166,24 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v */ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), loop.location? loop.location.start.column : 0) + return plain(getLexeme(loop), loop.location.start) } else if(body.length === 0) { return [] } else { if(body.length <= 1) { // 'inline' - return [{ line: `repeat ${body.length === 0 ? '{}' : body[0].line}`, indent: 0 }] - } else if(body[0].line === '{' && body[body.length - 1].line === '}') { + return [{ linePart: [{part: `repeat ${body.length === 0 ? '{}' : body[0].linePart[0]}`,loc: loop.location.start}], indent: 0 }] + } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ - { line: 'repeat {', indent: 0 }, + { linePart: [{part: 'repeat {', loc: loop.location.start}], indent: 0 }, ...body.slice(1, body.length - 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: loop.location.start}], indent: 0 } ] } else { // unknown return [ - { line: 'repeat', indent: 0 }, + { linePart: [{part: 'repeat', loc: loop.location.start}], indent: 0 }, ...indentBy(body, 1) ] } @@ -193,8 +194,11 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, --reconstruct-- */ function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { + const startPos = ifThenElse.location.start + const endPos = ifThenElse.location.end + const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : {line: 0, column: 0} if(isSelected(configuration, ifThenElse)) { - return plain(getLexeme(ifThenElse), ifThenElse.location? ifThenElse.location.start.column : 0) + return plain(getLexeme(ifThenElse), startPos) } otherwise ??= [] if(condition.length === 0 && when.length === 0 && otherwise.length === 0) { @@ -202,27 +206,27 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi } if(otherwise.length === 0 && when.length === 0) { return [ - { line: `if(${getLexeme(ifThenElse.condition)}) { }`, indent: 0 } + { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) { }`, loc: startPos}], indent: 0 } ] } else if(otherwise.length === 0) { return [ - { line: `if(${getLexeme(ifThenElse.condition)}) {`, indent: 0 }, + { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos}], indent: 0 }, ...indentBy(removeExpressionListWrap(when), 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: endPos}], indent: 0 } ] } else if(when.length === 0) { return [ - { line: `if(${getLexeme(ifThenElse.condition)}) { } else {`, indent: 0 }, + { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) { } else {`, loc: startPos}], indent: 0 }, ...indentBy(removeExpressionListWrap(otherwise), 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: startPos}], indent: 0 } ] } else { return [ - { line: `if(${getLexeme(ifThenElse.condition)}) {`, indent: 0 }, + { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos}], indent: 0 }, ...indentBy(removeExpressionListWrap(when), 1), - { line: '} else {', indent: 0 }, + { linePart: [{part: '} else {', loc: conditionPos}], indent: 0 }, ...indentBy(removeExpressionListWrap(otherwise), 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: endPos}], indent: 0 } ] } } @@ -231,26 +235,27 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi --reconstruct-- */ function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { + const start = loop.location.start if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), loop.location? loop.location.start.column : 0) + return plain(getLexeme(loop), start) } if(body.length === 0 && condition.length === 0) { return [] } else { if(body.length <= 1) { // 'inline' - return [{ line: `while(${getLexeme(loop.condition)}) ${body.length === 0 ? '{}' : body[0].line}`, indent: 0 }] - } else if(body[0].line === '{' && body[body.length - 1].line === '}') { + return [{ linePart: [{part: `while(${getLexeme(loop.condition)}) ${body.length === 0 ? '{}' : body[0].linePart[0]}`, loc: start}], indent: 0 }] + } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ - { line: `while(${getLexeme(loop.condition)}) {`, indent: 0 }, + { linePart: [{part: `while(${getLexeme(loop.condition)}) {`, loc: start}], indent: 0 }, ...body.slice(1, body.length - 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: loop.location.end}], indent: 0 } ] } else { // unknown return [ - { line: `while(${getLexeme(loop.condition)})`, indent: 0 }, + { linePart: [{part: `while(${getLexeme(loop.condition)})`, loc: start}], indent: 0 }, ...indentBy(body, 1) ] } @@ -277,8 +282,9 @@ function reconstructParameters(parameters: RParameter[]): str --reconstruct-- */ function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { + const start = node.location.start if(isSelected(configuration, node)) { - return plain(getLexeme(node), node.location? node.location.start.column : 0) + return plain(getLexeme(node), start) } if(accessed.length === 0) { @@ -289,19 +295,20 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, } } - return plain(getLexeme(node), node.location? node.location.start.column : 0) + return plain(getLexeme(node), start) } /* --reconstruct-- */ function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined, configuration: ReconstructionConfiguration): Code { + const start = argument.location.start if(isSelected(configuration, argument)) { - return plain(getLexeme(argument), argument.location? argument.location.start.column : 0) + return plain(getLexeme(argument), start) } if(argument.name !== undefined && name !== undefined && name.length > 0) { - return plain(`${getLexeme(argument.name)}=${argument.value ? getLexeme(argument.value) : ''}`, argument.location? argument.location.start.column : 0) + return plain(`${getLexeme(argument.name)}=${argument.value ? getLexeme(argument.value) : ''}`, start) } else { return value ?? [] } @@ -311,14 +318,15 @@ function reconstructArgument(argument: RArgument, name: Code --reconstruct-- */ function reconstructParameter(parameter: RParameter, name: Code, value: Code | undefined, configuration: ReconstructionConfiguration): Code { + const start = parameter.location.start if(isSelected(configuration, parameter)) { - return plain(getLexeme(parameter), parameter.location? parameter.location.start.column : 0) + return plain(getLexeme(parameter), start) } if(parameter.defaultValue !== undefined && name.length > 0) { - return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`, parameter.location? parameter.location.start.column : 0) + return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`, start) } else if(parameter.defaultValue !== undefined && name.length === 0) { - return plain(getLexeme(parameter.defaultValue), parameter.location? parameter.location.start.column : 0) + return plain(getLexeme(parameter.defaultValue), start) } else { return name } @@ -333,24 +341,26 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition we add the braces just to be sure return [ - { line: `function(${parameters}) {`, indent: 0 }, + { linePart: [{part: `function(${parameters}) {`, loc: startPos}], indent: 0 }, ...indentBy(body, 1), - { line: '}', indent: 0 } + { linePart: [{part: '}', loc: endPos}], indent: 0 } ] } @@ -371,15 +381,15 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R // else if (rhs === undefined || rhs.length === 0) { // if rhs is undefined we still have to keep both now, but reconstruct manually :/ if(lhs !== undefined && lhs.length > 0) { - const lhsText = lhs.map(l => `${getIndentString(l.indent)}${l.line}`).join('\n') + const lhsText = lhs.map(l => `${getIndentString(l.indent)}${l.linePart}`).join('\n') if(rhs !== undefined && rhs.length > 0) { - const rhsText = rhs.map(l => `${getIndentString(l.indent)}${l.line}`).join('\n') - return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location? call.location.start.column : 0) + const rhsText = rhs.map(l => `${getIndentString(l.indent)}${l.linePart}`).join('\n') + return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location.start) } else { - return plain(lhsText, call.location? call.location.start.column : 0) + return plain(lhsText, call.location.start) } } - return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`, call.location? call.location.start.column : 0) + return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`, call.location.start) } /* @@ -390,7 +400,7 @@ function reconstructFunctionCall(call: RFunctionCall, functio return reconstructSpecialInfixFunctionCall(args, call) } if(call.flavor === 'named' && isSelected(configuration, call)) { - return plain(getLexeme(call), call.location? call.location.start.column : 0) + return plain(getLexeme(call), call.location.start) } const filteredArgs = args.filter(a => a !== undefined && a.length > 0) if(functionName.length === 0 && filteredArgs.length === 0) { @@ -399,17 +409,17 @@ function reconstructFunctionCall(call: RFunctionCall, functio if(args.length === 0) { guard(functionName.length === 1, `without args, we need the function name to be present! got: ${JSON.stringify(functionName)}`) - if(call.flavor === 'unnamed' && !functionName[0].line.endsWith(')')) { - functionName[0].line = `(${functionName[0].line})` + if(call.flavor === 'unnamed' && !functionName[0].linePart[functionName[0].linePart.length - 1].part.endsWith(')')) { + functionName[0].linePart[0].part = `(${functionName[0].linePart[0].part})` } - if(!functionName[0].line.endsWith('()')) { + if(!functionName[0].linePart[functionName[0].linePart.length - 1].part.endsWith('()')) { // add empty call braces if not present - functionName[0].line += '()' + functionName[0].linePart[functionName[0].linePart.length - 1].part += '()' } - return [{ line: functionName[0].line, indent: functionName[0].indent }] + return [{ linePart: functionName[0].linePart, indent: functionName[0].indent }] } else { - return plain(getLexeme(call), call.location? call.location.start.column : 0) + return plain(getLexeme(call), call.location.start) } } From 030edc91752838e62d883ee0967728d528f54b17 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:07:27 +0100 Subject: [PATCH 08/88] fix(reconstruct): fixed problems the linter gave --- src/reconstruct/helper.ts | 29 ++++++++++++----------------- src/reconstruct/reconstruct.ts | 4 ++-- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index fd1cadd543..7d813590ff 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -8,24 +8,21 @@ import { ReconstructionConfiguration } from './reconstruct' */ export type Selection = Set interface PrettyPrintLinePart { - part: string - loc: SourcePosition + part: string + loc: SourcePosition } export interface PrettyPrintLine { linePart: PrettyPrintLinePart[] - indent: number + indent: number } /** * Splits text on '\n' to create lineParts and encapsulates them in the Code type - * @param text lexeme which needs to be converted to Code - * @param location of the lexeme within the R Code - * @returns text and location converted into the Code type */ export function plain(text: string, location: SourcePosition): Code { - let part = "" - let printLine: PrettyPrintLine = {linePart: [], indent: 0} - for (let character = 0; character < text.length; character++) { + let part = '' + const printLine: PrettyPrintLine = {linePart: [], indent: 0} + for(let character = 0; character < text.length; character++) { const element = text[character] if(element === '\n') { printLine.linePart.concat({part: part, loc: location}) @@ -39,12 +36,10 @@ export function plain(text: string, location: SourcePosition): Code { /** * this function will merge up to n code pieces into a singular code piece, garanting that there are no duplicate lines and all lines are in order - * @param snipbits list of code snipbits that need to be merged - * @returns the snipbits array as a single merged code element */ export function merge(snipbits: Code[]): Code { - let buckets: PrettyPrintLine[] = [] - let result:Code = [] + const buckets: PrettyPrintLine[] = [] + const result:Code = [] //seperate and group lineParts by lines snipbits.forEach(code => { @@ -54,12 +49,12 @@ export function merge(snipbits: Code[]): Code { buckets[line].linePart.concat(part) }) }) - }); + }) //sort buckets by column and stich lines into single code piece - for (const line in buckets) { - buckets[line].linePart.sort((a, b) => a.loc.column - b.loc.column) - result.concat(buckets[line]) + for (const line of buckets) { + line.linePart.sort((a, b) => a.loc.column - b.loc.column) + result.concat(line) } return result diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index c0f41ed8c1..14229fa06c 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -66,7 +66,7 @@ const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location --reconstruct-- */ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { - let position = exprList.location? exprList.location.start : {line: 0, column: 0} + const position = exprList.location? exprList.location.start : {line: 0, column: 0} if(isSelected(configuration, exprList)) { return plain(getLexeme(exprList), position) } @@ -143,7 +143,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } else { if(body.length <= 1) { // 'inline' - return [{ linePart: [{part:`for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].linePart}`,loc: loop.location.start}], indent: 0 }] + return [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].linePart}`,loc: loop.location.start}], indent: 0 }] } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ From 03cbf9e76b1ea809b8b71b987118419c1dc49891 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 1 Dec 2023 12:48:23 +0100 Subject: [PATCH 09/88] wip(reconstruct): cleaned up based on suggestions --- src/reconstruct/helper.ts | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 7d813590ff..fe74a2e970 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -6,6 +6,7 @@ import { ReconstructionConfiguration } from './reconstruct' /* --helper function-- */ +export type Code = PrettyPrintLine[] export type Selection = Set interface PrettyPrintLinePart { part: string @@ -25,7 +26,8 @@ export function plain(text: string, location: SourcePosition): Code { for(let character = 0; character < text.length; character++) { const element = text[character] if(element === '\n') { - printLine.linePart.concat({part: part, loc: location}) + printLine.linePart.push({part: part, loc: location}) + location.line += 1 } else { part = part.concat(element) @@ -42,14 +44,13 @@ export function merge(snipbits: Code[]): Code { const result:Code = [] //seperate and group lineParts by lines - snipbits.forEach(code => { - code.forEach(line => { - line.linePart.forEach(part => { - const line = part.loc.line - buckets[line].linePart.concat(part) - }) - }) - }) + for (const code of snipbits) { + for (const line of code) { + for (const part of line.linePart) { + buckets[part.loc.line].linePart.push(part) + } + } + } //sort buckets by column and stich lines into single code piece for (const line of buckets) { @@ -59,9 +60,7 @@ export function merge(snipbits: Code[]): Code { return result } -export type Code = PrettyPrintLine[] -//look up exact function /* --helper function-- */ From 329a0af96845fe4a57efa4f149364076c087dd9f Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:14:31 +0100 Subject: [PATCH 10/88] fix(reconstruct): fixed linter recommendations in helper.ts --- src/reconstruct/helper.ts | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index fe74a2e970..cb4dac12f6 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -18,19 +18,18 @@ export interface PrettyPrintLine { } /** - * Splits text on '\n' to create lineParts and encapsulates them in the Code type + * Splits text on linebreak to create lineParts and encapsulates them in the Code type */ export function plain(text: string, location: SourcePosition): Code { let part = '' const printLine: PrettyPrintLine = {linePart: [], indent: 0} - for(let character = 0; character < text.length; character++) { - const element = text[character] - if(element === '\n') { + for(const character of text) { + if(character === '\n') { printLine.linePart.push({part: part, loc: location}) location.line += 1 } else { - part = part.concat(element) + part = part.concat(character) } } return [printLine] @@ -44,23 +43,34 @@ export function merge(snipbits: Code[]): Code { const result:Code = [] //seperate and group lineParts by lines - for (const code of snipbits) { - for (const line of code) { - for (const part of line.linePart) { + for(const code of snipbits) { + for(const line of code) { + for(const part of line.linePart) { buckets[part.loc.line].linePart.push(part) } } } //sort buckets by column and stich lines into single code piece - for (const line of buckets) { + for(const line of buckets) { line.linePart.sort((a, b) => a.loc.column - b.loc.column) - result.concat(line) + result.push(line) } return result } +function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { + const result = '' + for(const part of line) { + for(let I = 0; I < part.loc.column; I++) { + result.concat(' ') + } + result.concat(part.part) + } + return result +} + /* --helper function-- */ @@ -125,7 +135,7 @@ export function getIndentString(indent: number): string { --helper function-- */ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { - return code.map(({ linePart, indent }) => `${getIndentString(indent)}${linePart}`).join(lf) + return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart)}`).join(lf) } /* From 428d7bfb0661dfe647fce4be6d190e1f62efb198 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 1 Dec 2023 17:03:50 +0100 Subject: [PATCH 11/88] feat: code update during the meeting --- src/reconstruct/helper.ts | 22 +++++++++------------- src/reconstruct/reconstruct.ts | 18 +++++++++--------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index cb4dac12f6..0453c4831d 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -21,16 +21,12 @@ export interface PrettyPrintLine { * Splits text on linebreak to create lineParts and encapsulates them in the Code type */ export function plain(text: string, location: SourcePosition): Code { - let part = '' const printLine: PrettyPrintLine = {linePart: [], indent: 0} - for(const character of text) { - if(character === '\n') { - printLine.linePart.push({part: part, loc: location}) - location.line += 1 - } - else { - part = part.concat(character) - } + const split = text.split('\n') + let locationLine = location.line + + for(const line of split) { + printLine.linePart.push({part: line, loc: { column: location.column, line: locationLine++ }}) } return [printLine] } @@ -42,7 +38,7 @@ export function merge(snipbits: Code[]): Code { const buckets: PrettyPrintLine[] = [] const result:Code = [] - //seperate and group lineParts by lines + //separate and group lineParts by lines for(const code of snipbits) { for(const line of code) { for(const part of line.linePart) { @@ -60,11 +56,11 @@ export function merge(snipbits: Code[]): Code { return result } -function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { - const result = '' +export function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { + const result = '' for(const part of line) { for(let I = 0; I < part.loc.column; I++) { - result.concat(' ') + result.concat(' ') } result.concat(part.part) } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 14229fa06c..d3d17ade53 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -26,7 +26,7 @@ import { import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' -import { Selection } from './helper' +import { Selection, prettyPrintPartToString } from './helper' import { PrettyPrintLine } from './helper' import { plain } from './helper' import { Code } from './helper' @@ -88,10 +88,10 @@ function reconstructExpressionList(exprList: RExpressionList, /* --reconstruct-- */ -function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: PrettyPrintLine[]) { +function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: PrettyPrintLine[]): Code { return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), - { line: `${lhs[lhs.length - 1].linePart[lhs.length - 1]} ${n} ${rhs[0].linePart[0]}`, indent: 0 }, + { linePart: [{ part: `${lhs[lhs.length - 1].linePart[lhs.length - 1].part} ${n} ${rhs[0].linePart[0].part}`, loc: lhs[lhs.length - 1].linePart[lhs.length - 1].loc }], indent: 0 }, ...indentBy(rhs.slice(1, rhs.length), 1) ] } @@ -143,7 +143,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } else { if(body.length <= 1) { // 'inline' - return [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].linePart}`,loc: loop.location.start}], indent: 0 }] + return [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].linePart[0].part}`,loc: loop.location.start}], indent: 0 }] } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ @@ -172,7 +172,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } else { if(body.length <= 1) { // 'inline' - return [{ linePart: [{part: `repeat ${body.length === 0 ? '{}' : body[0].linePart[0]}`,loc: loop.location.start}], indent: 0 }] + return [{ linePart: [{part: `repeat ${body.length === 0 ? '{}' : body[0].linePart[0].part}`,loc: loop.location.start}], indent: 0 }] } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ @@ -244,7 +244,7 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co } else { if(body.length <= 1) { // 'inline' - return [{ linePart: [{part: `while(${getLexeme(loop.condition)}) ${body.length === 0 ? '{}' : body[0].linePart[0]}`, loc: start}], indent: 0 }] + return [{ linePart: [{part: `while(${getLexeme(loop.condition)}) ${body.length === 0 ? '{}' : body[0].linePart[0].part}`, loc: start}], indent: 0 }] } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { // 'block' return [ @@ -345,7 +345,7 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition 0) { - const lhsText = lhs.map(l => `${getIndentString(l.indent)}${l.linePart}`).join('\n') + const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart)}`).join('\n') if(rhs !== undefined && rhs.length > 0) { - const rhsText = rhs.map(l => `${getIndentString(l.indent)}${l.linePart}`).join('\n') + const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart)}`).join('\n') return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location.start) } else { return plain(lhsText, call.location.start) From 82552b7fd28ab5089b69daffe1b30e113b9c1c53 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 8 Dec 2023 14:09:56 +0100 Subject: [PATCH 12/88] fix(reconstruct): pretty-print-to-string use the result :) --- src/reconstruct/helper.ts | 8 +++----- test/functionality/slicing/reconstruct/simple-tests.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 0453c4831d..9b6f66d64d 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -57,12 +57,10 @@ export function merge(snipbits: Code[]): Code { } export function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { - const result = '' + let result = '' for(const part of line) { - for(let I = 0; I < part.loc.column; I++) { - result.concat(' ') - } - result.concat(part.part) + result += ' '.repeat(part.loc.column) + result = result.concat(part.part) } return result } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index dacf8dc35f..06e839d55c 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -65,7 +65,7 @@ describe('Simple', withShell(shell => { describe('for', () => { const largeFor = ` - for (i in 1:20) { + for (i in 1:20) { y <- 9 x <- 5 12 -> x From f1adaa5e25462d86343bba79ef0d49bd958d6060 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 8 Dec 2023 14:51:50 +0100 Subject: [PATCH 13/88] refactor(doc): fix typo (theis->this) --- test/functionality/_helper/shell.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index fd952ceab4..2012a864c0 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -182,7 +182,7 @@ function printIdMapping(ids: NodeId[], map: DecoratedAstMap): string { } /** - * Please note, that theis executes the reconstruction step separately, as it predefines the result of the slice with the given ids. + * Please note, that this executes the reconstruction step separately, as it predefines the result of the slice with the given ids. */ export function assertReconstructed(name: string, shell: RShell, input: string, ids: NodeId | NodeId[], expected: string, userConfig?: Partial, getId: IdGenerator = deterministicCountingIdGenerator(0)): Mocha.Test { const selectedIds = Array.isArray(ids) ? ids : [ids] From 36a1df4d11f1654051aecf03800b97b22b4afc65 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 8 Dec 2023 14:56:30 +0100 Subject: [PATCH 14/88] feat(delims): limit subtype --- src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts b/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts index 4e9d077c97..164eaf0d39 100644 --- a/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts +++ b/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts @@ -10,5 +10,5 @@ import { MergeableRecord } from '../../../../../../util/objects' export interface RDelimiter extends MergeableRecord, Location { readonly type: RType.Delimiter; readonly lexeme: string; - readonly subtype: RawRType; + readonly subtype: RawRType.BraceLeft | RawRType.BraceRight | RawRType.ParenLeft | RawRType.ParenRight; } From 0446887484719d249baa50f67ac62db01ca15af0 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 8 Dec 2023 14:59:07 +0100 Subject: [PATCH 15/88] refactor(delimiter): improve RDelimiter node type --- src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts | 4 ++-- .../ast/parser/xml/internal/structure/single-element.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts b/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts index 164eaf0d39..93a16f15dc 100644 --- a/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts +++ b/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts @@ -2,7 +2,7 @@ import { RawRType, RType } from '../../type' import { Location } from '../../model' import { MergeableRecord } from '../../../../../../util/objects' - +export type RDelimiterNode = RawRType.BraceLeft | RawRType.BraceRight | RawRType.ParenLeft | RawRType.ParenRight | RawRType.Semicolon /** * Combines '`{`', '`}`', '`(`', '`)`', and other delimiters used by R, they are ignored for most analysis * but helpful during reconstruction. @@ -10,5 +10,5 @@ import { MergeableRecord } from '../../../../../../util/objects' export interface RDelimiter extends MergeableRecord, Location { readonly type: RType.Delimiter; readonly lexeme: string; - readonly subtype: RawRType.BraceLeft | RawRType.BraceRight | RawRType.ParenLeft | RawRType.ParenRight; + readonly subtype: RDelimiterNode; } diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts index b0385a08dc..72497f262d 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts @@ -8,7 +8,7 @@ import { RNode, RawRType, RType } from '../../../../model' import { normalizeComment } from '../other' import { normalizeBreak, normalizeNext } from '../loops' import { normalizeLineDirective } from '../other/line-directive' -import { RDelimiter } from '../../../../model/nodes/info' +import { RDelimiter, RDelimiterNode } from '../../../../model/nodes/info' function normalizeDelimiter(data: ParserData, elem: NamedXmlBasedJson): RDelimiter { const { @@ -19,7 +19,7 @@ function normalizeDelimiter(data: ParserData, elem: NamedXmlBasedJson): RDelimit type: RType.Delimiter, location, lexeme: content, - subtype: elem.name + subtype: elem.name as RDelimiterNode } } From 998a6d3c3ef70d48d86bdf2d0084ba5cfdf04993 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:34:33 +0100 Subject: [PATCH 16/88] feat(test): added first tests for plain and merge of reconstruct --- src/reconstruct/helper.ts | 9 ++- .../slicing/reconstruct/function-tests.ts | 73 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 test/functionality/slicing/reconstruct/function-tests.ts diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 9b6f66d64d..572f26a60b 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -42,13 +42,19 @@ export function merge(snipbits: Code[]): Code { for(const code of snipbits) { for(const line of code) { for(const part of line.linePart) { - buckets[part.loc.line].linePart.push(part) + const lineNumber = part.loc.line + if(buckets[lineNumber] === undefined) { //may be necessary as empty elements count as undefined and we don't want to reassign filled buckets + buckets[lineNumber] = {linePart: [], indent: line.indent} + } + buckets[lineNumber].linePart.push(part) } } } //sort buckets by column and stich lines into single code piece for(const line of buckets) { + continue + } line.linePart.sort((a, b) => a.loc.column - b.loc.column) result.push(line) } @@ -129,6 +135,7 @@ export function getIndentString(indent: number): string { --helper function-- */ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { + code = merge([code]) return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart)}`).join(lf) } diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts new file mode 100644 index 0000000000..7298a4ed07 --- /dev/null +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -0,0 +1,73 @@ +import { assert } from 'chai' +import { plain, Code, merge, prettyPrintCodeToString } from '../../../../src/reconstruct/helper' + +describe('Functions_Reconstruct', () => { + describe('plain', () => { + function positive(input: string, line: number, column: number, expected: Code) { + it(`${input} for ${line}:${column}`, () => { + const result:Code = plain(input, {line,column}) + assert.deepStrictEqual(result,expected) + }) + } + + for(const testCase of [ + {input: 'Hello', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}}],indent: 0}]}, + {input: 'Hello World', line: 4, column: 3, expected: [{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]}, + {input: 'Hello\nWorld', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 2, column: 1}}],indent: 0}]}, + {input: 'Hello\nWorld', line: 3, column: 4, expected: [{linePart: [{part: 'Hello', loc: {line: 3, column: 4}},{part: 'World', loc: {line: 4, column: 4}}],indent: 0}]}, + {input: 'Hello\nWorld\n24', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 2, column: 1}},{part: '24', loc: {line: 3, column: 1}}],indent: 0}]} + ]) { + positive(testCase.input, testCase.line, testCase.column, testCase.expected) + } + }) + describe.only('merge', () => { + function positive(snipbits: Code[],expected: Code) { + it(prettyPrintCodeToString(expected),() => { + const result:Code = merge(snipbits) + //console.log(JSON.stringify(result)) + assert.deepStrictEqual(result, expected) + }) + } + describe('single line merge', () => { + for(const testCase of [ + {snipbit: [[{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]], expected: [{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]}, + {snipbit: [[{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 1, column: 1}}],indent: 0}]], expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 1, column: 1}}],indent: 0}]} + ]) { + positive(testCase.snipbit, testCase.expected) + } + }) + describe('two lines merge', () => { + const testCases = [ + //Case 1 (in order) + { + snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}],[{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + }, + { + snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + }, + { + snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 0}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 1, column: 7}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}, {part: '24', loc: {line: 1, column: 7}}], indent: 0}] + }, + //Case 2 (out of order) + { + snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}],[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + }, + { + snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 0}}, {part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + }, + { + snipbit: [[{linePart: [{part: '24', loc: {line: 1, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}, {part: '24', loc: {line: 1, column: 7}}], indent: 0}] + } + ] + for(const testCase of testCases) { + positive(testCase.snipbit, testCase.expected) + } + }) + }) +}) \ No newline at end of file From 2e2472eb72514b32f354ecaebb389d78fb1671b3 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 13 Dec 2023 16:43:24 +0100 Subject: [PATCH 17/88] feat-fix(reconstruct): changes that got lost --- src/reconstruct/helper.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 572f26a60b..f17c7761af 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -53,6 +53,7 @@ export function merge(snipbits: Code[]): Code { //sort buckets by column and stich lines into single code piece for(const line of buckets) { + if(line === undefined){ //appers to be necessary as 'buckets' may be sparce (empty elements count as undefined) continue } line.linePart.sort((a, b) => a.loc.column - b.loc.column) From 0cd9ab26ce58cd9847068807ad24846a0fdfcbeb Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:38:17 +0100 Subject: [PATCH 18/88] feat(test): added more cases to merge Last Test Case ignores if merge sorts stable or not Maybe investigate stable behavior of merge later --- .../slicing/reconstruct/function-tests.ts | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 7298a4ed07..54fd77c77a 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -69,5 +69,78 @@ describe('Functions_Reconstruct', () => { positive(testCase.snipbit, testCase.expected) } }) + describe('three lines merge', () => { + const testCases = [ + //Case 1 (in order) 123 + { + snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 7}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + }, + //Case 2 (2nd Line out of order) 213 + { + snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 7}}, {part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + }, + //Case 3 (3rd Line out of order) 231 + { + snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + }, + //Case 4 (reverse order) 321 + { + snipbit: [[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}],[{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + }, + //Case 5 () 312 + { + snipbit: [[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}], [{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + }, + //Case 6 () 132 + { + snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}], [{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}], [{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}]], + expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + } + ] + for(const testCase of testCases) { + positive(testCase.snipbit, testCase.expected) + } + }) + describe('random lines merge', () => { + function makeTestCase(lines: number): Code[] { + const testCase = [] as Code[] + const partPool = ['Hello', 'World', 'FlowR', 'Is', 'Incredible'] + for(let i = 0; i < lines; i++) { + const line: Code = [{linePart: [{part: partPool[Math.random() % 5], loc: {line: Math.random() % lines, column: Math.random() % 15}}], indent: 0}] + testCase.push(line) + } + return testCase + } + function checkTestCase(code: Code): boolean { + let currentLoc = {line: 0, column: 0} + for(let line = 0; line < code.length; line++) { + for(let part = 0; part < code[line].linePart.length; part++) { + const nextLoc = code[line].linePart[part].loc + if(currentLoc.line <= nextLoc.line && currentLoc.column <= nextLoc.column) { + currentLoc = nextLoc + } + else { + return false + } + } + } + return true + } + function positive(lines: number) { + const snipbit = makeTestCase(lines) + it(`case with ${lines} lines`, () => { + const merged = merge(snipbit) + assert.isTrue(checkTestCase(merged), JSON.stringify(merged)) + }) + } + for(let i = 0; i < 15; i++) { + positive(i) + } + }) }) }) \ No newline at end of file From 7b9cb5bd1ff188e88770481b8c6ebf6f9c595aae Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 15 Dec 2023 13:02:43 +0100 Subject: [PATCH 19/88] refactor(reconstruct, test): minor meeting improvements --- .../slicing/reconstruct/function-tests.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 54fd77c77a..ed546d8cab 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -1,7 +1,7 @@ import { assert } from 'chai' import { plain, Code, merge, prettyPrintCodeToString } from '../../../../src/reconstruct/helper' -describe('Functions_Reconstruct', () => { +describe('Functions Reconstruct', () => { describe('plain', () => { function positive(input: string, line: number, column: number, expected: Code) { it(`${input} for ${line}:${column}`, () => { @@ -15,12 +15,12 @@ describe('Functions_Reconstruct', () => { {input: 'Hello World', line: 4, column: 3, expected: [{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]}, {input: 'Hello\nWorld', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 2, column: 1}}],indent: 0}]}, {input: 'Hello\nWorld', line: 3, column: 4, expected: [{linePart: [{part: 'Hello', loc: {line: 3, column: 4}},{part: 'World', loc: {line: 4, column: 4}}],indent: 0}]}, - {input: 'Hello\nWorld\n24', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 2, column: 1}},{part: '24', loc: {line: 3, column: 1}}],indent: 0}]} + {input: 'Hello\n World\n24', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: ' World', loc: {line: 2, column: 1}},{part: '24', loc: {line: 3, column: 1}}],indent: 0}]} ]) { positive(testCase.input, testCase.line, testCase.column, testCase.expected) } }) - describe.only('merge', () => { + describe('merge', () => { function positive(snipbits: Code[],expected: Code) { it(prettyPrintCodeToString(expected),() => { const result:Code = merge(snipbits) @@ -118,9 +118,9 @@ describe('Functions_Reconstruct', () => { } function checkTestCase(code: Code): boolean { let currentLoc = {line: 0, column: 0} - for(let line = 0; line < code.length; line++) { - for(let part = 0; part < code[line].linePart.length; part++) { - const nextLoc = code[line].linePart[part].loc + for(const line of code) { + for(const part of line.linePart) { + const nextLoc = part.loc if(currentLoc.line <= nextLoc.line && currentLoc.column <= nextLoc.column) { currentLoc = nextLoc } From 698febf76464a4a7a44d97007bec521383ea90b5 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:49:22 +0100 Subject: [PATCH 20/88] feat-fix(reconstruct): fixed error with columns BinaryOp now uses lhs.location if possible as node.location is the location of the operator not the beginning of the statement --- src/reconstruct/helper.ts | 9 ++++++-- src/reconstruct/main.ts | 1 + src/reconstruct/reconstruct.ts | 11 +++++++--- .../slicing/reconstruct/function-tests.ts | 21 +++++++++++++++++-- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index f17c7761af..768f7028e5 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -8,7 +8,7 @@ import { ReconstructionConfiguration } from './reconstruct' */ export type Code = PrettyPrintLine[] export type Selection = Set -interface PrettyPrintLinePart { +export interface PrettyPrintLinePart { part: string loc: SourcePosition } @@ -28,6 +28,7 @@ export function plain(text: string, location: SourcePosition): Code { for(const line of split) { printLine.linePart.push({part: line, loc: { column: location.column, line: locationLine++ }}) } + //console.log(JSON.stringify(printLine)) return [printLine] } @@ -64,9 +65,13 @@ export function merge(snipbits: Code[]): Code { } export function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { + line.sort((a, b) => a.loc.column - b.loc.column) let result = '' for(const part of line) { - result += ' '.repeat(part.loc.column) + const currLength = result.length + //console.log(part.loc.column - currLength) + //we have to 0 any negative values as they can happen??? + result += ' '.repeat(Math.max(part.loc.column - currLength - 1, 0)) result = result.concat(part.part) } return result diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index 6059cb20ae..fc7d29b69e 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -39,6 +39,7 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel // fold of the normalized ast const result = foldAstStateful(ast.ast, { selection, autoSelectIf: autoSelectIfWrapper }, reconstructAstFolds) + //console.log(JSON.stringify(result)) if(reconstructLogger.settings.minLevel >= LogLevel.Trace) { reconstructLogger.trace('reconstructed ast before string conversion: ', JSON.stringify(result)) } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index d3d17ade53..61ce475550 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -115,7 +115,9 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: */ function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { - return plain(getLexeme(n), n.location.start) + //console.log(`lhs at ${JSON.stringify(n.lhs.location?.start)}, ${JSON.stringify(n.lhs.location?.end)}`) + //console.log(`${getLexeme(n)} at ${JSON.stringify(n.location.start)}, ${JSON.stringify(n.location.end)}`) + return plain(getLexeme(n), n.lhs.location? n.lhs.location.start : n.location.start) } if(lhs.length === 0 && rhs.length === 0) { @@ -124,8 +126,11 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe' : n.operator, rhs) diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index ed546d8cab..50917c1154 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -1,5 +1,5 @@ import { assert } from 'chai' -import { plain, Code, merge, prettyPrintCodeToString } from '../../../../src/reconstruct/helper' +import { plain, Code, merge, prettyPrintCodeToString, PrettyPrintLinePart, prettyPrintPartToString } from '../../../../src/reconstruct/helper' describe('Functions Reconstruct', () => { describe('plain', () => { @@ -138,9 +138,26 @@ describe('Functions Reconstruct', () => { assert.isTrue(checkTestCase(merged), JSON.stringify(merged)) }) } - for(let i = 0; i < 15; i++) { + for(let i = 0; i < 20; i++) { positive(i) } }) }) + describe('printLinePart', () => { + function positive(input: PrettyPrintLinePart[], expected: string, msg: string) { + it(`Convert ${JSON.stringify(input)} to string`, () => { + const out = prettyPrintPartToString(input) + assert.strictEqual(out, expected, msg) + }) + } + for(const testCase of [ + {input: [{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello',msg: 'No Spaces anywhere'}, + {input: [{part: 'Hello World', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get preserved'}, + {input: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 0, column: 7}}],expected: 'Hello World',msg: 'Spaces get added within the string'}, + {input: [{part: 'Hello', loc: {line: 0, column: 7}}],expected: ' Hello',msg: 'Spaces get added at the beginning'}, + {input: [{part: 'World', loc: {line: 0, column: 7}},{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order'}, + ]) { + positive(testCase.input, testCase.expected, testCase.msg) + } + }) }) \ No newline at end of file From 624acf203e4f2c43a878c0c124aa849fca4ccc9b Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 22 Dec 2023 14:10:13 +0100 Subject: [PATCH 21/88] refactor(merge): fix dirty merge --- src/reconstruct/helper.ts | 2 +- src/reconstruct/reconstruct.ts | 18 ++++++------------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 768f7028e5..6c97fd1955 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -69,7 +69,7 @@ export function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { let result = '' for(const part of line) { const currLength = result.length - //console.log(part.loc.column - currLength) + console.log(part.loc.column - currLength) //we have to 0 any negative values as they can happen??? result += ' '.repeat(Math.max(part.loc.column - currLength - 1, 0)) result = result.concat(part.part) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index e07e15e0b2..6aa1bd43c5 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -26,18 +26,12 @@ import { import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' +import { Selection, prettyPrintPartToString } from './helper' +import { PrettyPrintLine, plain, Code, indentBy, isSelected, removeExpressionListWrap, AutoSelectPredicate, getIndentString } from './helper' -// -type Selection = Set -interface PrettyPrintLine { - line: string - indent: number -} -function plain(text: string): PrettyPrintLine[] { - return [{ line: text, indent: 0 }] -} -type Code = PrettyPrintLine[] - +/* +--logger-- +*/ export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) /* @@ -127,7 +121,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe Date: Fri, 22 Dec 2023 15:05:34 +0100 Subject: [PATCH 22/88] feat(reconstruct): work on more sensible reconstructions in for, repeat, exprlist --- src/reconstruct/helper.ts | 24 ++++-- src/reconstruct/main.ts | 4 +- src/reconstruct/reconstruct.ts | 85 +++++++++---------- test/functionality/_helper/shell.ts | 2 +- .../slicing/reconstruct/simple-tests.ts | 11 +-- 5 files changed, 65 insertions(+), 61 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 6c97fd1955..78951111bb 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,6 +1,7 @@ import { NodeId, ParentInformation, RNode, RType } from '../r-bridge' import { SourcePosition } from '../util/range' import { ReconstructionConfiguration } from './reconstruct' +import {jsonReplacer} from "../util/json"; /* @@ -54,7 +55,7 @@ export function merge(snipbits: Code[]): Code { //sort buckets by column and stich lines into single code piece for(const line of buckets) { - if(line === undefined){ //appers to be necessary as 'buckets' may be sparce (empty elements count as undefined) + if(line === undefined){ //appears to be necessary as 'buckets' may be sparse (empty elements count as undefined) continue } line.linePart.sort((a, b) => a.loc.column - b.loc.column) @@ -65,13 +66,16 @@ export function merge(snipbits: Code[]): Code { } export function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { + if(line.length === 0) { + return '' + } line.sort((a, b) => a.loc.column - b.loc.column) + let columnOffset = line[0].loc.column let result = '' for(const part of line) { - const currLength = result.length - console.log(part.loc.column - currLength) + const currLength = result.length + columnOffset //we have to 0 any negative values as they can happen??? - result += ' '.repeat(Math.max(part.loc.column - currLength - 1, 0)) + result += ' '.repeat(Math.max(part.loc.column - currLength, 0)) result = result.concat(part.part) } return result @@ -148,12 +152,16 @@ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { /* --helper function-- */ -export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], autoSelected: number) { - if(result.length > 1 && result[0].linePart[0].part === '{' && result[result.length - 1].linePart[result[result.length - 1].linePart.length - 1].part === '}') { +export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[]): Code { + const first = result[0]?.linePart + if(result.length === 1 && first[0].part === '{' && first[result[0].linePart.length - 1].part === '}') { + // we are in a single line + return [{ linePart: first.slice(1, first.length - 1), indent: result[0].indent }] + } else if(result.length > 1 && first[0].part === '{' && result[result.length - 1].linePart[result[result.length - 1].linePart.length - 1].part === '}') { // remove outer block - return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), autoSelected } + return indentBy(result.slice(1, result.length - 1), -1) } else { - return { code: prettyPrintCodeToString(result), autoSelected } + return result } } diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index fc7d29b69e..61eddd4c24 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,5 +1,5 @@ import { ReconstructionResult, reconstructLogger, reconstructAstFolds } from './reconstruct' -import { removeOuterExpressionListIfApplicable } from './helper' +import {prettyPrintCodeToString, removeOuterExpressionListIfApplicable} from './helper' import { autoSelectLibrary } from './helper' import { AutoSelectPredicate } from './helper' import { Selection } from './helper' @@ -44,5 +44,5 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel reconstructLogger.trace('reconstructed ast before string conversion: ', JSON.stringify(result)) } - return removeOuterExpressionListIfApplicable(result, autoSelected) + return { code: prettyPrintCodeToString(removeOuterExpressionListIfApplicable(result)), autoSelected } } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 6aa1bd43c5..207886d143 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -26,8 +26,20 @@ import { import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' import { MergeableRecord } from '../util/objects' -import { Selection, prettyPrintPartToString } from './helper' -import { PrettyPrintLine, plain, Code, indentBy, isSelected, removeExpressionListWrap, AutoSelectPredicate, getIndentString } from './helper' +import { + Selection, + prettyPrintPartToString, + PrettyPrintLine, + plain, + Code, + indentBy, + isSelected, + removeExpressionListWrap, + AutoSelectPredicate, + getIndentString, merge, removeOuterExpressionListIfApplicable +} from './helper' +import {SourcePosition, SourceRange} from "../util/range"; +import {jsonReplacer} from "../util/json"; /* --logger-- @@ -59,22 +71,20 @@ const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location --reconstruct-- */ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { - const position = exprList.location? exprList.location.start : {line: 0, column: 0} if(isSelected(configuration, exprList)) { - return plain(getLexeme(exprList), position) + const positionStart = exprList.location? exprList.location.start : {line: 0, column: 0} + return plain(getLexeme(exprList), positionStart) } const subExpressions = expressions.filter(e => e.length > 0) if(subExpressions.length === 0) { return [] - } else if(subExpressions.length === 1) { - return subExpressions[0] } else { - return [ - { linePart: [{part: '{', loc: position}] , indent: 0 }, - ...indentBy(subExpressions.flat(), 1), - { linePart: [{part: '}', loc: position}], indent: 0 } - ] + const additionalTokens = reconstructAdditionalTokens(exprList); + return merge([ + ...subExpressions, + ...additionalTokens + ]) } } @@ -138,27 +148,23 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } if(body.length === 0 && variable.length === 0 && vector.length === 0) { return [] + } else if(body.length !== 0 && (variable.length !== 0 || vector.length !== 0)) { + const additionalTokens = reconstructAdditionalTokens(loop); + return merge([ + [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: loop.location.start}], indent: 0 }], + body, + ...additionalTokens + ]) } else { - if(body.length <= 1) { - // 'inline' - return [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].linePart[0].part}`,loc: loop.location.start}], indent: 0 }] - } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { - // 'block' - return [ - { linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) {`, loc: loop.location.start}], indent: 0 }, - ...body.slice(1, body.length - 1), - { linePart: [{part: '}', loc: loop.location.start}], indent: 0 } - ] - } else { - // unknown - return [ - { linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: loop.location.start}], indent: 0 }, - ...indentBy(body, 1) - ] - } + return body } } +function reconstructAdditionalTokens(loop: RNodeWithParent): Code[] { + return loop.info.additionalTokens?.filter(t => t.lexeme && t.location) + .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] +} + /* --reconstruct-- */ @@ -168,23 +174,12 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } else if(body.length === 0) { return [] } else { - if(body.length <= 1) { - // 'inline' - return [{ linePart: [{part: `repeat ${body.length === 0 ? '{}' : body[0].linePart[0].part}`,loc: loop.location.start}], indent: 0 }] - } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { - // 'block' - return [ - { linePart: [{part: 'repeat {', loc: loop.location.start}], indent: 0 }, - ...body.slice(1, body.length - 1), - { linePart: [{part: '}', loc: loop.location.start}], indent: 0 } - ] - } else { - // unknown - return [ - { linePart: [{part: 'repeat', loc: loop.location.start}], indent: 0 }, - ...indentBy(body, 1) - ] - } + const additionalTokens = reconstructAdditionalTokens(loop); + return merge([ + [{ linePart: [{part: 'repeat', loc: loop.location.start}], indent: 0 }], + body, + ...additionalTokens + ]) } } diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index 9d7731f0cf..b0c785f822 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -194,7 +194,7 @@ function printIdMapping(ids: NodeId[], map: DecoratedAstMap): string { /** * Please note, that this executes the reconstruction step separately, as it predefines the result of the slice with the given ids. */ -export function assertReconstructed(name: string, shell: RShell, input: string, ids: NodeId | NodeId[], expected: string, userConfig?: Partial, getId: IdGenerator = deterministicCountingIdGenerator(0)): Mocha.Test { +export function assertReconstructed(name: string, shell: RShell, input: string, ids: NodeId | readonly NodeId[], expected: string, userConfig?: Partial, getId: IdGenerator = deterministicCountingIdGenerator(0)): Mocha.Test { const selectedIds = Array.isArray(ids) ? ids : [ids] return it(name, async function() { await ensureConfig(shell, this, userConfig) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 06e839d55c..9dcad7aecb 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -8,7 +8,7 @@ describe('Simple', withShell(shell => { '{ x <- 5 }', '{ x <- 5; y <- 9 }' ]) { - assertReconstructed(code, shell, code, '0', 'x <- 5') + assertReconstructed(code, shell, code, '0', code.includes(';') ? 'x <- 5;' : 'x <- 5') } }) describe('Nested Assignments', () => { @@ -17,8 +17,9 @@ describe('Simple', withShell(shell => { ['y <- x <- 42', '1', 'x <- 42' ], ['y <- x <- 42', '0', 'y <- x <- 42' ], // we are not smart enough right now to see, that the write is constant. - ['for (i in 1:20) { x <- 5 }', '4', 'for(i in 1:20) x <- 5' ] - ]) { + ['for(i in 1:20) { x <- 5 }', '4', 'x <- 5' ], + ['for(i in 1:20) { x <- 5 }', ['0', '4'], 'for(i in 1:20) { x <- 5 }' ] + ] as const) { assertReconstructed(code, shell, code, id, expected) } }) @@ -35,8 +36,8 @@ describe('Simple', withShell(shell => { describe('Loops', () => { describe('repeat', () => { const pool: [string, string | string[], string][] = [ - ['repeat { x }', '0', 'repeat x'], - ['repeat { x <- 5; y <- 9 }', '0', 'repeat x <- 5'], + ['repeat { x }', '0', 'repeat { x }'], + ['repeat { x <- 5; y <- 9 }', '0', 'repeat { x <- 5; }'], ['repeat { x <- 5; y <- 9 }', ['0', '1', '4'], 'repeat {\n x <- 5\n 9\n}'] ] for(const [code, id, expected] of pool) { From 2897b9b5e1e2f48c0b9c3fd2bdc28a6e2ef9e8a8 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:59:42 +0100 Subject: [PATCH 23/88] test-fix(test): fixed for-loop reconstruction --- src/reconstruct/helper.ts | 7 ++-- src/reconstruct/reconstruct.ts | 33 +++++++++++-------- .../slicing/reconstruct/function-tests.ts | 2 +- .../slicing/reconstruct/simple-tests.ts | 23 ++++++++----- 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 78951111bb..44fa983d10 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,8 +1,6 @@ import { NodeId, ParentInformation, RNode, RType } from '../r-bridge' import { SourcePosition } from '../util/range' import { ReconstructionConfiguration } from './reconstruct' -import {jsonReplacer} from "../util/json"; - /* --helper function-- @@ -65,12 +63,11 @@ export function merge(snipbits: Code[]): Code { return result } -export function prettyPrintPartToString(line: PrettyPrintLinePart[]): string { +export function prettyPrintPartToString(line: PrettyPrintLinePart[],columnOffset: number): string { if(line.length === 0) { return '' } line.sort((a, b) => a.loc.column - b.loc.column) - let columnOffset = line[0].loc.column let result = '' for(const part of line) { const currLength = result.length + columnOffset @@ -146,7 +143,7 @@ export function getIndentString(indent: number): string { */ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge([code]) - return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart)}`).join(lf) + return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc.column)}`).join(lf) } /* diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 207886d143..27ee72ca05 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -36,10 +36,8 @@ import { isSelected, removeExpressionListWrap, AutoSelectPredicate, - getIndentString, merge, removeOuterExpressionListIfApplicable -} from './helper' -import {SourcePosition, SourceRange} from "../util/range"; -import {jsonReplacer} from "../util/json"; + getIndentString, merge } from './helper' +import { SourceRange} from '../util/range' /* --logger-- @@ -80,7 +78,7 @@ function reconstructExpressionList(exprList: RExpressionList, if(subExpressions.length === 0) { return [] } else { - const additionalTokens = reconstructAdditionalTokens(exprList); + const additionalTokens = reconstructAdditionalTokens(exprList) return merge([ ...subExpressions, ...additionalTokens @@ -143,21 +141,30 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { - if(isSelected(configuration, loop)) { + const start = loop.info.fullRange?.start //may be unnesseccary + const additionalTokens = reconstructAdditionalTokens(loop) + const out = merge([ + [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: start ? start :loop.location.start}], indent: 0 }], + body, + ...additionalTokens + ]) + return out + /*if(isSelected(configuration, loop)) { return plain(getLexeme(loop), loop.location.start) } if(body.length === 0 && variable.length === 0 && vector.length === 0) { return [] } else if(body.length !== 0 && (variable.length !== 0 || vector.length !== 0)) { - const additionalTokens = reconstructAdditionalTokens(loop); - return merge([ - [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: loop.location.start}], indent: 0 }], + const additionalTokens = reconstructAdditionalTokens(loop) + const out = merge([ + [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: start ? start :loop.location.start}], indent: 0 }], body, ...additionalTokens ]) + return out } else { return body - } + }*/ } function reconstructAdditionalTokens(loop: RNodeWithParent): Code[] { @@ -174,7 +181,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } else if(body.length === 0) { return [] } else { - const additionalTokens = reconstructAdditionalTokens(loop); + const additionalTokens = reconstructAdditionalTokens(loop) return merge([ [{ linePart: [{part: 'repeat', loc: loop.location.start}], indent: 0 }], body, @@ -374,9 +381,9 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R // else if (rhs === undefined || rhs.length === 0) { // if rhs is undefined we still have to keep both now, but reconstruct manually :/ if(lhs !== undefined && lhs.length > 0) { - const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart)}`).join('\n') + const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, lhs[0].linePart[0].loc.column)}`).join('\n') if(rhs !== undefined && rhs.length > 0) { - const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart)}`).join('\n') + const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, rhs[0].linePart[0].loc.column)}`).join('\n') return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location.start) } else { return plain(lhsText, call.location.start) diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 50917c1154..8ece46d671 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -146,7 +146,7 @@ describe('Functions Reconstruct', () => { describe('printLinePart', () => { function positive(input: PrettyPrintLinePart[], expected: string, msg: string) { it(`Convert ${JSON.stringify(input)} to string`, () => { - const out = prettyPrintPartToString(input) + const out = prettyPrintPartToString(input, input[0].loc.column) assert.strictEqual(out, expected, msg) }) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 9dcad7aecb..91d471c784 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -1,6 +1,6 @@ import { assertReconstructed, withShell } from '../../_helper/shell' -describe('Simple', withShell(shell => { +describe.only('Simple', withShell(shell => { describe('Constant assignments', () => { for(const code of [ 'x <- 5', @@ -38,13 +38,15 @@ describe('Simple', withShell(shell => { const pool: [string, string | string[], string][] = [ ['repeat { x }', '0', 'repeat { x }'], ['repeat { x <- 5; y <- 9 }', '0', 'repeat { x <- 5; }'], - ['repeat { x <- 5; y <- 9 }', ['0', '1', '4'], 'repeat {\n x <- 5\n 9\n}'] + ['repeat { x <- 5; y <- 9 }', ['0', '1', '4'], 'repeat { x <- 5; 9 }'] ] for(const [code, id, expected] of pool) { assertReconstructed(code, shell, code, id, expected) } }) + //output consistend "while(...) {" + //may want to look into reconstruct for while describe('while', () => { const pool: [string, string | string[], string][] = [ ['while(TRUE) { x }', '1', 'while(TRUE) x'], @@ -73,16 +75,19 @@ describe('Simple', withShell(shell => { } ` const pool: [string, string | string[], string][] = [ - [largeFor, '0', 'for(i in 1:20) {}'], - [largeFor, '4', 'for(i in 1:20) y <- 9'], - [largeFor, ['0', '4'], 'for(i in 1:20) y <- 9'], + [largeFor, '0', 'for(i in 1:20) {}'], //reconstruct for(i in 1:20) ? + [largeFor, '4', 'for(i in 1:20) {\n y <- 9\n}'], + //more spaces after for + [largeFor, ['0', '4'], 'for(i in 1:20) {\n y <- 9\n}'], + //more spaces after for [largeFor, ['0', '4', '7'], `for(i in 1:20) { - y <- 9 - x <- 5 + y <- 9 + x <- 5 }`], + //more spaces after for [largeFor, ['0', '4', '10'], `for(i in 1:20) { - y <- 9 - 12 -> x + y <- 9 + 12 -> x }`], ] From 19ae981f27c39d4c1794d856c2857924f8d4647b Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 19 Jan 2024 14:21:37 +0100 Subject: [PATCH 24/88] wip: first stage of adding `(`, `in`, and `)` to the output --- .../lang-4.x/ast/parser/xml/internal/loops/for.ts | 10 ++++++---- src/reconstruct/reconstruct.ts | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts index 8cca4abb9d..4d934c31d1 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts @@ -8,6 +8,7 @@ import { normalizeBasedOnType, splitComments, tryNormalizeSingleNode } from '../ import { RawRType, RComment, RForLoop, RNode, RSymbol, RType } from '../../../../model' import { executeHook, executeUnknownHook } from '../../hooks' import { normalizeComment } from '../other' +import { RDelimiter } from '../../../../model/nodes/info'; export function tryNormalizeFor( data: ParserData, @@ -31,7 +32,7 @@ export function tryNormalizeFor( ({ forToken, condition: head, body } = executeHook(data.hooks.loops.onForLoop.before, data, { forToken, condition: head, body })) - const { variable: parsedVariable, vector: parsedVector, comments } = + const { variable: parsedVariable, vector: parsedVector, additionalTokens: comments } = normalizeForHead(newParseData, head.content) const parseBody = tryNormalizeSingleNode(newParseData, body) @@ -70,7 +71,7 @@ export function tryNormalizeFor( return executeHook(data.hooks.loops.onForLoop.after, data, result) } -function normalizeForHead(data: ParserData, forCondition: XmlBasedJson): { variable: RSymbol | undefined, vector: RNode | undefined, comments: RComment[] } { +function normalizeForHead(data: ParserData, forCondition: XmlBasedJson): { variable: RSymbol | undefined, vector: RNode | undefined, additionalTokens: (RNode | RDelimiter)[] } { // must have a child which is `in`, a variable on the left, and a vector on the right const children: NamedXmlBasedJson[] = getKeysGuarded(forCondition, data.config.childrenName).map(content => ({ name: getTokenType(data.config.tokenMap, content), @@ -86,7 +87,8 @@ function normalizeForHead(data: ParserData, forCondition: XmlBasedJson): { varia const vector = normalizeBasedOnType(data, [others[inPosition + 1]]) guard(vector.length === 1 && vector[0].type !== RType.Delimiter, () => `for loop vector should have been parsed to a single element but was ${JSON.stringify(vector)}`) - const parsedComments = comments.map(c => normalizeComment(data, c.content)) + const parsedComments: (RNode | RDelimiter)[] = comments.map(c => normalizeComment(data, c.content)) + parsedComments.push(...normalizeBasedOnType(data, [others[0]])) // e.g., push '(' - return { variable, vector: vector[0], comments: parsedComments } + return { variable, vector: vector[0], additionalTokens: parsedComments } } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 27ee72ca05..c6214be15e 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -75,10 +75,12 @@ function reconstructExpressionList(exprList: RExpressionList, } const subExpressions = expressions.filter(e => e.length > 0) + if(subExpressions.length === 0) { return [] } else { const additionalTokens = reconstructAdditionalTokens(exprList) + return merge([ ...subExpressions, ...additionalTokens From 089eb6aa27c365d4a7dc5b7afdafec6f41bc811a Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:05:09 +0100 Subject: [PATCH 25/88] test-fix(reconstruct): while Loop Reconstruction changed while loop reconstruct to always construct from the ground up. Shifts everything by one space for unknown reason. --- src/reconstruct/reconstruct.ts | 28 ++++++------------- .../slicing/reconstruct/simple-tests.ts | 25 +++++++++-------- 2 files changed, 22 insertions(+), 31 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 27ee72ca05..0ea75cd94d 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -140,7 +140,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { +function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code): Code { const start = loop.info.fullRange?.start //may be unnesseccary const additionalTokens = reconstructAdditionalTokens(loop) const out = merge([ @@ -149,22 +149,6 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v ...additionalTokens ]) return out - /*if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), loop.location.start) - } - if(body.length === 0 && variable.length === 0 && vector.length === 0) { - return [] - } else if(body.length !== 0 && (variable.length !== 0 || vector.length !== 0)) { - const additionalTokens = reconstructAdditionalTokens(loop) - const out = merge([ - [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: start ? start :loop.location.start}], indent: 0 }], - body, - ...additionalTokens - ]) - return out - } else { - return body - }*/ } function reconstructAdditionalTokens(loop: RNodeWithParent): Code[] { @@ -239,7 +223,13 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co if(isSelected(configuration, loop)) { return plain(getLexeme(loop), start) } - if(body.length === 0 && condition.length === 0) { + const additionalTokens = reconstructAdditionalTokens(loop) + const constructedLoop: Code = [{linePart: [{part: '', loc: {line: 0, column: 0}}], indent: 0}] + constructedLoop[0].linePart.push({part: `while(${getLexeme(loop.condition)})`, loc: loop.location.start}) + constructedLoop.push(...body) + return merge([constructedLoop, ...additionalTokens]) + + /*if(body.length === 0 && condition.length === 0) { return [] } else { if(body.length <= 1) { @@ -259,7 +249,7 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co ...indentBy(body, 1) ] } - } + }*/ } /* diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 91d471c784..ce08049501 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -17,7 +17,7 @@ describe.only('Simple', withShell(shell => { ['y <- x <- 42', '1', 'x <- 42' ], ['y <- x <- 42', '0', 'y <- x <- 42' ], // we are not smart enough right now to see, that the write is constant. - ['for(i in 1:20) { x <- 5 }', '4', 'x <- 5' ], + ['for(i in 1:20) { x <- 5 }', '4', 'x <- 5' ], //somehow this now fails ['for(i in 1:20) { x <- 5 }', ['0', '4'], 'for(i in 1:20) { x <- 5 }' ] ] as const) { assertReconstructed(code, shell, code, id, expected) @@ -49,17 +49,18 @@ describe.only('Simple', withShell(shell => { //may want to look into reconstruct for while describe('while', () => { const pool: [string, string | string[], string][] = [ - ['while(TRUE) { x }', '1', 'while(TRUE) x'], - ['while(TRUE) { x <- 5 }', '1', 'while(TRUE) x <- 5'], - ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) x <- 5'], - ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) x <- 5'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) x <- 5'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) y <- 9'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], - ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], - ['while(x + 2 > 3) { x <- 0 }', ['5'], 'while(x + 2 > 3) x <- 0'], - ['while(x + 2 > 3) { x <- 0 }', ['0', '5'], 'while(x + 2 > 3) x <- 0'] + ['while(TRUE) { x }', '1', 'while(TRUE) { x }'], + ['while(TRUE) { x <- 5 }', '1', 'while(TRUE) { x <- 5 }'], + ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) {x <- 5; }'], + ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], //empty body may need to be seperate case + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) {x <- 5; }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) {x <- 5; }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { ; y <- 9}'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) { x <- 5; y <- 9 }',], + ['while(TRUE) {\n x <- 5\n y <- 9\n}', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], + ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], //empty body may need to be seperate case + ['while(x + 2 > 3) { x <- 0 }', ['5'], 'while(x + 2 > 3) { x <- 0 }'], + ['while(x + 2 > 3) { x <- 0 }', ['0', '5'], 'while(x + 2 > 3) { x <- 0 }'] ] for(const [code, id, expected] of pool) { assertReconstructed(code, shell, code, id, expected) From a172d2b6aeb96a17263bf59c0d2039a6e4a5562f Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 24 Jan 2024 13:26:33 +0100 Subject: [PATCH 26/88] lint: linter fixes --- src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts index 4d934c31d1..d4f3aae127 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts @@ -8,7 +8,7 @@ import { normalizeBasedOnType, splitComments, tryNormalizeSingleNode } from '../ import { RawRType, RComment, RForLoop, RNode, RSymbol, RType } from '../../../../model' import { executeHook, executeUnknownHook } from '../../hooks' import { normalizeComment } from '../other' -import { RDelimiter } from '../../../../model/nodes/info'; +import { RDelimiter } from '../../../../model/nodes/info' export function tryNormalizeFor( data: ParserData, From ef2d55b2b0e26e8f23aa1ab89110f26685cc5524 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 25 Jan 2024 22:04:56 +0100 Subject: [PATCH 27/88] feat-fix: fixed linting recomendations --- src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts | 2 +- test/functionality/_helper/shell.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts index d4f3aae127..ea815b6230 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts @@ -5,7 +5,7 @@ import { guard } from '../../../../../../../util/assert' import { ParserData } from '../../data' import { tryNormalizeSymbol } from '../values' import { normalizeBasedOnType, splitComments, tryNormalizeSingleNode } from '../structure' -import { RawRType, RComment, RForLoop, RNode, RSymbol, RType } from '../../../../model' +import { RawRType, RForLoop, RNode, RSymbol, RType } from '../../../../model' import { executeHook, executeUnknownHook } from '../../hooks' import { normalizeComment } from '../other' import { RDelimiter } from '../../../../model/nodes/info' diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index b0c785f822..e375ba738a 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -195,7 +195,7 @@ function printIdMapping(ids: NodeId[], map: DecoratedAstMap): string { * Please note, that this executes the reconstruction step separately, as it predefines the result of the slice with the given ids. */ export function assertReconstructed(name: string, shell: RShell, input: string, ids: NodeId | readonly NodeId[], expected: string, userConfig?: Partial, getId: IdGenerator = deterministicCountingIdGenerator(0)): Mocha.Test { - const selectedIds = Array.isArray(ids) ? ids : [ids] + const selectedIds :NodeId[] = (Array.isArray(ids) ? ids : [ids]) as NodeId[] return it(name, async function() { await ensureConfig(shell, this, userConfig) From 037d4ef46909a247181306e297f8bb4a38ad856c Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:46:47 +0100 Subject: [PATCH 28/88] wip(reconstruct): attemt to implement (, in, ) --- src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts | 2 +- src/reconstruct/reconstruct.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts index ea815b6230..4546fba167 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts @@ -88,7 +88,7 @@ function normalizeForHead(data: ParserData, forCondition: XmlBasedJson): { varia const vector = normalizeBasedOnType(data, [others[inPosition + 1]]) guard(vector.length === 1 && vector[0].type !== RType.Delimiter, () => `for loop vector should have been parsed to a single element but was ${JSON.stringify(vector)}`) const parsedComments: (RNode | RDelimiter)[] = comments.map(c => normalizeComment(data, c.content)) - parsedComments.push(...normalizeBasedOnType(data, [others[0]])) // e.g., push '(' + parsedComments.push(...normalizeBasedOnType(data, others.filter(token => token.name === RawRType.ForIn || token.name === RawRType.ParenLeft || token.name === RawRType.ParenRight))) // e.g., push '(' return { variable, vector: vector[0], additionalTokens: parsedComments } } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 071f11a372..b3b1284811 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -146,7 +146,8 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v const start = loop.info.fullRange?.start //may be unnesseccary const additionalTokens = reconstructAdditionalTokens(loop) const out = merge([ - [{ linePart: [{part: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: start ? start :loop.location.start}], indent: 0 }], + [{ linePart: [{part: 'for', loc: start ? start :loop.location.start}], indent: 0 }], + [{ linePart: [{part: `${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: loop.variable.location.start}], indent: 0}], body, ...additionalTokens ]) From d48274572497e0b0d0beaffe0ee70691eb9eaf29 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 26 Jan 2024 14:08:07 +0100 Subject: [PATCH 29/88] add a handler for `ForIn` as part of normalize delimiter --- .../lang-4.x/ast/parser/xml/internal/structure/single-element.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts index 72497f262d..725dd3148f 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts @@ -37,6 +37,7 @@ export function tryNormalizeSingleNode(data: ParserData, elem: NamedXmlBasedJson case RawRType.ParenRight: case RawRType.BraceLeft: case RawRType.BraceRight: + case RawRType.ForIn: return normalizeDelimiter(data, elem) case RawRType.Comment: return normalizeComment(data, elem.content) From d2d3d87a797258805e653df86e76a91bf88017e2 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Sun, 28 Jan 2024 10:40:35 +0100 Subject: [PATCH 30/88] feat(wip): might have broken reconstruction!!! we attemt to fit in the loop.vector however flowr does not produce any output. May be due to internet problems? --- src/reconstruct/reconstruct.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index b3b1284811..1b265dea39 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -145,9 +145,12 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code): Code { const start = loop.info.fullRange?.start //may be unnesseccary const additionalTokens = reconstructAdditionalTokens(loop) + const vectorLocation = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc + const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) const out = merge([ [{ linePart: [{part: 'for', loc: start ? start :loop.location.start}], indent: 0 }], - [{ linePart: [{part: `${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, loc: loop.variable.location.start}], indent: 0}], + variable, + reconstructedVector, body, ...additionalTokens ]) From 858a72651362d13c261834566ae0aed6bf62352f Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:16:51 +0100 Subject: [PATCH 31/88] feat-fix(reconstruct): for-loop finished some pretty printing still occurs do to problems with vector and empty body --- src/reconstruct/reconstruct.ts | 20 ++++++++++++++----- .../slicing/reconstruct/simple-tests.ts | 14 ++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 1b265dea39..b46a9c4163 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -37,7 +37,7 @@ import { removeExpressionListWrap, AutoSelectPredicate, getIndentString, merge } from './helper' -import { SourceRange} from '../util/range' +import { SourcePosition, SourceRange} from '../util/range' /* --logger-- @@ -145,16 +145,26 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code): Code { const start = loop.info.fullRange?.start //may be unnesseccary const additionalTokens = reconstructAdditionalTokens(loop) - const vectorLocation = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc + const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc + vectorLocation.column -= 1 //somehow the vector is consitently one space to late const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) const out = merge([ [{ linePart: [{part: 'for', loc: start ? start :loop.location.start}], indent: 0 }], - variable, + [{ linePart: [{part: getLexeme(loop.variable), loc: loop.variable.location.start}], indent: 0}], reconstructedVector, - body, ...additionalTokens ]) - return out + //if body empty + if(body.length < 1) { + // puts {} with one space separation after for(...) + const hBody = out[out.length - 1].linePart + const bodyLoc = hBody[hBody.length - 1].loc + out.push({ linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 2}}], indent: 0}) + return out + } + //normal reconstruct + out.push(...body) + return merge([out]) } function reconstructAdditionalTokens(loop: RNodeWithParent): Code[] { diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index ce08049501..18010e979e 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -52,13 +52,13 @@ describe.only('Simple', withShell(shell => { ['while(TRUE) { x }', '1', 'while(TRUE) { x }'], ['while(TRUE) { x <- 5 }', '1', 'while(TRUE) { x <- 5 }'], ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) {x <- 5; }'], - ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], //empty body may need to be seperate case + ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) {x <- 5; }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) {x <- 5; }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { ; y <- 9}'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) { x <- 5; y <- 9 }',], ['while(TRUE) {\n x <- 5\n y <- 9\n}', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], - ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], //empty body may need to be seperate case + ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], ['while(x + 2 > 3) { x <- 0 }', ['5'], 'while(x + 2 > 3) { x <- 0 }'], ['while(x + 2 > 3) { x <- 0 }', ['0', '5'], 'while(x + 2 > 3) { x <- 0 }'] ] @@ -76,17 +76,17 @@ describe.only('Simple', withShell(shell => { } ` const pool: [string, string | string[], string][] = [ - [largeFor, '0', 'for(i in 1:20) {}'], //reconstruct for(i in 1:20) ? - [largeFor, '4', 'for(i in 1:20) {\n y <- 9\n}'], + [largeFor, '0', 'for (i in 1:20) {}'], + [largeFor, '4', 'for (i in 1:20) {\n y <- 9\n}'], //more spaces after for - [largeFor, ['0', '4'], 'for(i in 1:20) {\n y <- 9\n}'], + [largeFor, ['0', '4'], 'for (i in 1:20) {\n y <- 9\n}'], //more spaces after for - [largeFor, ['0', '4', '7'], `for(i in 1:20) { + [largeFor, ['0', '4', '7'], `for (i in 1:20) { y <- 9 x <- 5 }`], //more spaces after for - [largeFor, ['0', '4', '10'], `for(i in 1:20) { + [largeFor, ['0', '4', '10'], `for (i in 1:20) { y <- 9 12 -> x }`], From 744e160d06c7776c784cf908db8fea2afe9ec587 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 30 Jan 2024 15:55:15 +0100 Subject: [PATCH 32/88] feat-fix(reconstruct): while-loop exept empty body --- src/reconstruct/reconstruct.ts | 21 ++++++++++++++----- .../slicing/reconstruct/simple-tests.ts | 8 +++---- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index b46a9c4163..2f207ff809 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -142,8 +142,11 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code): Code { +function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = loop.info.fullRange?.start //may be unnesseccary + if(isSelected(configuration, loop)) { + return plain(getLexeme(loop), start ? start :loop.location.start) + } const additionalTokens = reconstructAdditionalTokens(loop) const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc vectorLocation.column -= 1 //somehow the vector is consitently one space to late @@ -240,10 +243,18 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co return plain(getLexeme(loop), start) } const additionalTokens = reconstructAdditionalTokens(loop) - const constructedLoop: Code = [{linePart: [{part: '', loc: {line: 0, column: 0}}], indent: 0}] - constructedLoop[0].linePart.push({part: `while(${getLexeme(loop.condition)})`, loc: loop.location.start}) - constructedLoop.push(...body) - return merge([constructedLoop, ...additionalTokens]) + const out = merge([ + [{ linePart: [{part: `while(${getLexeme(loop.condition)})`, loc: start ? start :loop.location.start}], indent: 0 }], + ...additionalTokens + ]) + if(body.length < 1) { + //we need to get the location of the body but we have no idea what the location might be + out.push({linePart: [{part: '{}', loc: loop.location.end}], indent: 0}) + } + else { + out.push(...body) + } + return merge([out]) /*if(body.length === 0 && condition.length === 0) { return [] diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 18010e979e..c0adc2efd6 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -51,11 +51,11 @@ describe.only('Simple', withShell(shell => { const pool: [string, string | string[], string][] = [ ['while(TRUE) { x }', '1', 'while(TRUE) { x }'], ['while(TRUE) { x <- 5 }', '1', 'while(TRUE) { x <- 5 }'], - ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) {x <- 5; }'], + ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) { x <- 5; }'], ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) {x <- 5; }'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) {x <- 5; }'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { ; y <- 9}'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) { x <- 5; }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) { x <- 5; }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { ; y <- 9 }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) { x <- 5; y <- 9 }',], ['while(TRUE) {\n x <- 5\n y <- 9\n}', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], From 08141bdfb33fd55624c3b5ab22341d2d8ba067c3 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 31 Jan 2024 12:13:58 +0100 Subject: [PATCH 33/88] feat-fix(reconstruct): fixed while-loop --- src/reconstruct/reconstruct.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 2f207ff809..023be5cc38 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -248,8 +248,10 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co ...additionalTokens ]) if(body.length < 1) { + const hBody = out[out.length - 1].linePart + const bodyLoc = {line: hBody[hBody.length - 1].loc.line, column: hBody[hBody.length - 1].loc.column + hBody[hBody.length - 1].part.length} //we need to get the location of the body but we have no idea what the location might be - out.push({linePart: [{part: '{}', loc: loop.location.end}], indent: 0}) + out.push({linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 1}}], indent: 0}) } else { out.push(...body) From 22911ad8a66898bdb938e87750a8b1d2d11157c8 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 31 Jan 2024 13:02:33 +0100 Subject: [PATCH 34/88] feat-fix(reconstruct): cleaned up comments --- src/reconstruct/helper.ts | 34 ----------- src/reconstruct/main.ts | 3 - src/reconstruct/reconstruct.ts | 102 +++------------------------------ 3 files changed, 9 insertions(+), 130 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 44fa983d10..79afc3415c 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -2,9 +2,6 @@ import { NodeId, ParentInformation, RNode, RType } from '../r-bridge' import { SourcePosition } from '../util/range' import { ReconstructionConfiguration } from './reconstruct' -/* ---helper function-- -*/ export type Code = PrettyPrintLine[] export type Selection = Set export interface PrettyPrintLinePart { @@ -27,7 +24,6 @@ export function plain(text: string, location: SourcePosition): Code { for(const line of split) { printLine.linePart.push({part: line, loc: { column: location.column, line: locationLine++ }}) } - //console.log(JSON.stringify(printLine)) return [printLine] } @@ -78,23 +74,14 @@ export function prettyPrintPartToString(line: PrettyPrintLinePart[],columnOffset return result } -/* ---helper function-- -*/ export function indentBy(lines: Code, indent: number): Code { return lines.map(({ linePart, indent: i }) => ({ linePart, indent: i + indent })) } -/* ---helper function-- -*/ export function isSelected(configuration: ReconstructionConfiguration, n: RNode) { return configuration.selection.has(n.info.id) || configuration.autoSelectIf(n) } -/* ---helper function-- -*/ export function removeExpressionListWrap(code: Code) { if(code.length > 0 && code[0].linePart[0].part === '{' && code[code.length - 1].linePart[code[code.length - 1].linePart.length - 1].part === '}') { return indentBy(code.slice(1, code.length - 1), -1) @@ -103,27 +90,15 @@ export function removeExpressionListWrap(code: Code) { } } -/* ---helper function-- -*/ /** The structure of the predicate that should be used to determine if a given normalized node should be included in the reconstructed code independent of if it is selected by the slice or not */ export type AutoSelectPredicate = (node: RNode) => boolean -/* ---helper function-- -*/ export function doNotAutoSelect(_node: RNode): boolean { return false } -/* ---helper function-- -*/ export const libraryFunctionCall = /^(library|require|((require|load|attach)Namespace))$/ -/* ---helper function-- -*/ export function autoSelectLibrary(node: RNode): boolean { if(node.type !== RType.FunctionCall || node.flavor !== 'named') { return false @@ -131,24 +106,15 @@ export function autoSelectLibrary(node: RNode): boolean { return libraryFunctionCall.test(node.functionName.content) } -/* ---helper function-- -*/ export function getIndentString(indent: number): string { return ' '.repeat(indent * 4) } -/* ---helper function-- -*/ export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge([code]) return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc.column)}`).join(lf) } -/* ---helper function-- -*/ export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[]): Code { const first = result[0]?.linePart if(result.length === 1 && first[0].part === '{' && first[result[0].linePart.length - 1].part === '}') { diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index 61eddd4c24..6f70f806b4 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -8,9 +8,6 @@ import { NormalizedAst, RNode, ParentInformation, foldAstStateful } from '../r-b import { LogLevel } from '../util/log' -/* ---main function-- -*/ /** * Reconstructs parts of a normalized R ast into R code on an expression basis. * diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 023be5cc38..e17a649bcd 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -4,7 +4,6 @@ * @module */ -//imports {note: as of current, do not change} import { ParentInformation, RAccess, @@ -39,19 +38,11 @@ import { getIndentString, merge } from './helper' import { SourcePosition, SourceRange} from '../util/range' -/* ---logger-- -*/ + export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) -/* ---helper function-- -*/ const getLexeme = (n: RNodeWithParent) => n.info.fullLexeme ?? n.lexeme ?? '' -/* ---reconstruct-- -*/ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionConfiguration): Code => { const selectionHasLeaf = configuration.selection.has(leaf.info.id) || configuration.autoSelectIf(leaf) if(selectionHasLeaf) { @@ -65,9 +56,6 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start : {line: 0, column: 0}) -/* ---reconstruct-- -*/ function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, exprList)) { const positionStart = exprList.location? exprList.location.start : {line: 0, column: 0} @@ -88,9 +76,6 @@ function reconstructExpressionList(exprList: RExpressionList, } } -/* ---reconstruct-- -*/ function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: PrettyPrintLine[]): Code { return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), @@ -99,9 +84,6 @@ function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: Pr ] } -/* ---reconstruct-- -*/ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: ReconstructionConfiguration) { if(configuration.selection.has(leaf.info.id)) { return foldToConst(leaf) @@ -113,13 +95,8 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: } } -/* ---reconstruct-- -*/ function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { - //console.log(`lhs at ${JSON.stringify(n.lhs.location?.start)}, ${JSON.stringify(n.lhs.location?.end)}`) - //console.log(`${getLexeme(n)} at ${JSON.stringify(n.location.start)}, ${JSON.stringify(n.location.end)}`) return plain(getLexeme(n), n.lhs.location? n.lhs.location.start : n.location.start) } @@ -130,8 +107,6 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe | RPipe' : n.operator, rhs) } -/* ---reconstruct-- -*/ function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = loop.info.fullRange?.start //may be unnesseccary if(isSelected(configuration, loop)) { + console.log('loop selected') return plain(getLexeme(loop), start ? start :loop.location.start) } + if(isSelected(configuration, loop.body)) { + console.log('body selected') + return merge([body]) + } const additionalTokens = reconstructAdditionalTokens(loop) const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc vectorLocation.column -= 1 //somehow the vector is consitently one space to late @@ -159,12 +136,14 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v ]) //if body empty if(body.length < 1) { + console.log('no body') // puts {} with one space separation after for(...) const hBody = out[out.length - 1].linePart const bodyLoc = hBody[hBody.length - 1].loc out.push({ linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 2}}], indent: 0}) return out } + console.log('normal') //normal reconstruct out.push(...body) return merge([out]) @@ -175,9 +154,6 @@ function reconstructAdditionalTokens(loop: RNodeWithParent): Code[] { .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] } -/* ---reconstruct-- -*/ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, loop)) { return plain(getLexeme(loop), loop.location.start) @@ -193,9 +169,6 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } } -/* ---reconstruct-- -*/ function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = ifThenElse.location.start const endPos = ifThenElse.location.end @@ -234,9 +207,6 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi } } -/* ---reconstruct-- -*/ function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = loop.location.start if(isSelected(configuration, loop)) { @@ -248,42 +218,17 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co ...additionalTokens ]) if(body.length < 1) { + //this puts {} one space after while(...) const hBody = out[out.length - 1].linePart const bodyLoc = {line: hBody[hBody.length - 1].loc.line, column: hBody[hBody.length - 1].loc.column + hBody[hBody.length - 1].part.length} - //we need to get the location of the body but we have no idea what the location might be out.push({linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 1}}], indent: 0}) } else { out.push(...body) } return merge([out]) - - /*if(body.length === 0 && condition.length === 0) { - return [] - } else { - if(body.length <= 1) { - // 'inline' - return [{ linePart: [{part: `while(${getLexeme(loop.condition)}) ${body.length === 0 ? '{}' : body[0].linePart[0].part}`, loc: start}], indent: 0 }] - } else if(body[0].linePart[0].part === '{' && body[body.length - 1].linePart[body[body.length - 1].linePart.length - 1].part === '}') { - // 'block' - return [ - { linePart: [{part: `while(${getLexeme(loop.condition)}) {`, loc: start}], indent: 0 }, - ...body.slice(1, body.length - 1), - { linePart: [{part: '}', loc: loop.location.end}], indent: 0 } - ] - } else { - // unknown - return [ - { linePart: [{part: `while(${getLexeme(loop.condition)})`, loc: start}], indent: 0 }, - ...indentBy(body, 1) - ] - } - }*/ } -/* ---reconstruct-- -*/ function reconstructParameters(parameters: RParameter[]): string[] { // const baseParameters = parameters.flatMap(p => plain(getLexeme(p))) return parameters.map(p => { @@ -295,11 +240,6 @@ function reconstructParameters(parameters: RParameter[]): str }) } - -//foldAccess?? Arrayzugriffe -/* ---reconstruct-- -*/ function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { const start = node.location.start if(isSelected(configuration, node)) { @@ -317,9 +257,6 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, return plain(getLexeme(node), start) } -/* ---reconstruct-- -*/ function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined, configuration: ReconstructionConfiguration): Code { const start = argument.location.start if(isSelected(configuration, argument)) { @@ -333,9 +270,6 @@ function reconstructArgument(argument: RArgument, name: Code } } -/* ---reconstruct-- -*/ function reconstructParameter(parameter: RParameter, name: Code, value: Code | undefined, configuration: ReconstructionConfiguration): Code { const start = parameter.location.start if(isSelected(configuration, parameter)) { @@ -351,9 +285,6 @@ function reconstructParameter(parameter: RParameter, name: Co } } -/* ---reconstruct-- -*/ function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { @@ -385,9 +316,6 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition): Code { guard(args.length === 2, () => `infix special call must have exactly two arguments, got: ${args.length} (${JSON.stringify(args)})`) guard(call.flavor === 'named', `infix special call must be named, got: ${call.flavor}`) @@ -411,9 +339,6 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`, call.location.start) } -/* ---reconstruct-- -*/ function reconstructFunctionCall(call: RFunctionCall, functionName: Code, args: (Code | undefined)[], configuration: ReconstructionConfiguration): Code { if(call.infixSpecial === true) { return reconstructSpecialInfixFunctionCall(args, call) @@ -442,18 +367,12 @@ function reconstructFunctionCall(call: RFunctionCall, functio } } -/* ---interface-- -*/ export interface ReconstructionConfiguration extends MergeableRecord { selection: Selection /** if true, this will force the ast part to be reconstructed, this can be used, for example, to force include `library` statements */ autoSelectIf: AutoSelectPredicate } -/* ---reconstruct-- -*/ /** * The fold functions used to reconstruct the ast in {@link reconstructToCode}. */ @@ -501,9 +420,6 @@ export const reconstructAstFolds: StatefulFoldFunctions Date: Wed, 31 Jan 2024 13:31:10 +0100 Subject: [PATCH 35/88] test-fix(reconstruct): fixed nested assignments --- src/reconstruct/reconstruct.ts | 4 ---- test/functionality/slicing/reconstruct/simple-tests.ts | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index e17a649bcd..83c5bc8cb0 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -117,11 +117,9 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = loop.info.fullRange?.start //may be unnesseccary if(isSelected(configuration, loop)) { - console.log('loop selected') return plain(getLexeme(loop), start ? start :loop.location.start) } if(isSelected(configuration, loop.body)) { - console.log('body selected') return merge([body]) } const additionalTokens = reconstructAdditionalTokens(loop) @@ -136,14 +134,12 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v ]) //if body empty if(body.length < 1) { - console.log('no body') // puts {} with one space separation after for(...) const hBody = out[out.length - 1].linePart const bodyLoc = hBody[hBody.length - 1].loc out.push({ linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 2}}], indent: 0}) return out } - console.log('normal') //normal reconstruct out.push(...body) return merge([out]) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index c0adc2efd6..fef477c72a 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -1,6 +1,6 @@ import { assertReconstructed, withShell } from '../../_helper/shell' -describe.only('Simple', withShell(shell => { +describe('Simple', withShell(shell => { describe('Constant assignments', () => { for(const code of [ 'x <- 5', @@ -17,7 +17,7 @@ describe.only('Simple', withShell(shell => { ['y <- x <- 42', '1', 'x <- 42' ], ['y <- x <- 42', '0', 'y <- x <- 42' ], // we are not smart enough right now to see, that the write is constant. - ['for(i in 1:20) { x <- 5 }', '4', 'x <- 5' ], //somehow this now fails + ['for(i in 1:20) { x <- 5 }', '7', 'x <- 5' ], ['for(i in 1:20) { x <- 5 }', ['0', '4'], 'for(i in 1:20) { x <- 5 }' ] ] as const) { assertReconstructed(code, shell, code, id, expected) From 47a7d9317b36fddd0892c76f9db9d90887872a83 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 9 Feb 2024 13:52:40 +0100 Subject: [PATCH 36/88] feat-fix(reconstruct): started fixing function def --- src/reconstruct/reconstruct.ts | 10 ++++++++++ .../slicing/static-program-slices/calls-tests.ts | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 83c5bc8cb0..010fd044ae 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -289,6 +289,16 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition { +describe.only('Calls', withShell(shell => { describe('Simple Calls', () => { const code = `i <- 4 a <- function(x) { x } @@ -12,7 +12,7 @@ a(i)` a <- function(x) { x <- 2; 1 } a(i)` assertSliced('Function call with constant function', shell, constFunction, ['3:1'], `i <- 4 -a <- function(x) { 1 } +a <- function(x) { 1 } a(i)`) assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x) { }') assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2') @@ -24,7 +24,7 @@ foo(x, 3) x. <- function (x) { x } foo(x, x.(y)) foo(x, x.(3)) - `, ['4@foo'], `x. <- function(x) { x } + `, ['4@foo'], `x. <- function (x) { x } foo(x, x.(3))`) assertSliced('Using ...', shell, ` f1 <- function (a,b) { c } @@ -33,8 +33,8 @@ x <- 3 c <- 4 y <- 3 f2(1,x) - `, ['7@f2'], `f1 <- function(a, b) { c } -f2 <- function(...) { f1(...) } + `, ['7@f2'], `f1 <- function (a, b) { c } +f2 <- function (...) { f1(...) } x <- 3 c <- 4 f2(1,x)`) From 659574c80a3c3f37928854664cd9ed2b49d708fa Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:28:13 +0100 Subject: [PATCH 37/88] test(reconstruct): testing function definition --- src/reconstruct/reconstruct.ts | 22 ++++++++++++++----- test/functionality/_helper/shell.ts | 4 ++-- .../slicing/reconstruct/simple-tests.ts | 14 +++++++++--- .../static-program-slices/calls-tests.ts | 12 +++++++--- 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 010fd044ae..6b3e15ce72 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -20,7 +20,7 @@ import { RWhileLoop, RType, RPipe, - StatefulFoldFunctions + StatefulFoldFunctions, } from '../r-bridge' import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' @@ -284,21 +284,33 @@ function reconstructParameter(parameter: RParameter, name: Co function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { - return body + return merge([body]) } - const parameters = reconstructParameters(definition.parameters).join(', ') + + if(isSelected(configuration, definition.body)) { + return merge([body]) + } + const startPos = definition.location.start const endPos = definition.location.end + + if(isSelected(configuration, definition)) { + return plain(getLexeme(definition), startPos) + } + + const parameters = reconstructParameters(definition.parameters).join(', ') const additionalTokens = reconstructAdditionalTokens(definition) - const reconstructedBody = body.length === 0 ? [{linePart: [{part: '', loc: startPos}], indent: 0}] : body.slice(1, body.length - 1) + const reconstructedBody = reconstructExpressionList(definition.body, [plain(getLexeme(definition.body), startPos)], configuration) + //body.length === 0 ? [{linePart: [{part: '', loc: startPos}], indent: 0}] : body.slice(1, body.length - 1) const parameterLoc = definition.parameters.length === 0 ? startPos : definition.parameters[definition.parameters.length - 1].location.start const out = merge([[{linePart: [{part: getLexeme(definition), loc: startPos}], indent: 0}], reconstructedBody, - [{linePart: [{part: parameters, loc: parameterLoc}], indent: 0}], + plain(parameters, parameterLoc), ...additionalTokens]) return out + if(body.length <= 1) { // 'inline' const bodyStr = body.length === 0 ? '' : `${body[0].linePart[0].part} ` /* add suffix space */ diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index e375ba738a..4d2123df1c 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -225,10 +225,10 @@ export function assertSliced(name: string, shell: RShell, input: string, criteri try { assert.strictEqual( result.reconstruct.code, expected, - `got: ${result.reconstruct.code}, vs. expected: ${expected}, for input ${input} (slice: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` + //`got: ${result.reconstruct.code}, vs. expected: ${expected}, for input ${input} (slice: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` ) } catch(e) { - console.error('vis-got:\n', graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap)) + //console.error('vis-got:\n', graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap)) throw e } }) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index fef477c72a..e35faf763c 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -78,14 +78,11 @@ describe('Simple', withShell(shell => { const pool: [string, string | string[], string][] = [ [largeFor, '0', 'for (i in 1:20) {}'], [largeFor, '4', 'for (i in 1:20) {\n y <- 9\n}'], - //more spaces after for [largeFor, ['0', '4'], 'for (i in 1:20) {\n y <- 9\n}'], - //more spaces after for [largeFor, ['0', '4', '7'], `for (i in 1:20) { y <- 9 x <- 5 }`], - //more spaces after for [largeFor, ['0', '4', '10'], `for (i in 1:20) { y <- 9 12 -> x @@ -96,6 +93,17 @@ describe('Simple', withShell(shell => { assertReconstructed(`${JSON.stringify(id)}: ${code}`, shell, code, id, expected) } }) + + describe.only('function definition', () => { + const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ + {name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }'}, + {name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2'}, + {name: 'multi-line function', case: 'a <- function (x) { x <- 2;\n x + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\n x + 4 }'} + ] + for(const test of testCases) { + assertReconstructed(test.name, shell, test.case, test.argument, test.expected) + } + }) }) describe('Failures in practice', () => { assertReconstructed('Reconstruct expression list in call', shell, ` diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 5718c3da76..12a8e171b7 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -1,6 +1,6 @@ import { assertSliced, withShell } from '../../_helper/shell' -describe.only('Calls', withShell(shell => { +describe('Calls', withShell(shell => { describe('Simple Calls', () => { const code = `i <- 4 a <- function(x) { x } @@ -12,7 +12,7 @@ a(i)` a <- function(x) { x <- 2; 1 } a(i)` assertSliced('Function call with constant function', shell, constFunction, ['3:1'], `i <- 4 -a <- function(x) { 1 } +a <- function(x) { 1 } a(i)`) assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x) { }') assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2') @@ -302,7 +302,13 @@ a()(y) cat(x)`, ['5@x'], `x <- 2 cat(x)`) - assertSliced('Must work with nested globals and maybe assignments', shell, `a <- function() { function(b) { if(runif() > .5) { x <<- b } } } + assertSliced('Must work with nested globals and maybe assignments', shell, `a <- function() { + function(b) { + if(runif() > .5) { + x <<- b + } + } + } y <- 5 x <- 2 a()(y) From 2bdd8138e174937ec68597bf56407b161be0c65e Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 9 Feb 2024 21:55:53 +0100 Subject: [PATCH 38/88] test-fix(reconstruct): debugging for function def --- src/reconstruct/reconstruct.ts | 13 +++++++++++-- .../slicing/reconstruct/simple-tests.ts | 3 ++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 6b3e15ce72..25f02e54e6 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -284,27 +284,36 @@ function reconstructParameter(parameter: RParameter, name: Co function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { + console.log('function without parameter unselected') return merge([body]) } if(isSelected(configuration, definition.body)) { - return merge([body]) + console.log('fbody selected') + const out = merge([body]) + const h = out[out.length - 1].linePart + if(h[h.length - 1].part === ';') { + out.pop() + } + return out } const startPos = definition.location.start const endPos = definition.location.end if(isSelected(configuration, definition)) { + console.log('definition selected') return plain(getLexeme(definition), startPos) } + console.log('normal function reconstruct') const parameters = reconstructParameters(definition.parameters).join(', ') const additionalTokens = reconstructAdditionalTokens(definition) const reconstructedBody = reconstructExpressionList(definition.body, [plain(getLexeme(definition.body), startPos)], configuration) //body.length === 0 ? [{linePart: [{part: '', loc: startPos}], indent: 0}] : body.slice(1, body.length - 1) const parameterLoc = definition.parameters.length === 0 ? startPos : definition.parameters[definition.parameters.length - 1].location.start - const out = merge([[{linePart: [{part: getLexeme(definition), loc: startPos}], indent: 0}], + const out = merge([[{ linePart: [{part: `function(${parameters})`, loc: startPos}], indent: 0 }], reconstructedBody, plain(parameters, parameterLoc), ...additionalTokens]) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index e35faf763c..3701a3c41f 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -98,7 +98,8 @@ describe('Simple', withShell(shell => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ {name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }'}, {name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2'}, - {name: 'multi-line function', case: 'a <- function (x) { x <- 2;\n x + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\n x + 4 }'} + {name: 'multi-line function', case: 'a <- function (x) { x <- 2;\n x + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\n x + 4 }'}, + {name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2'} ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 010db509dca2cd0c0d08a14c1b75f5d2d3d87838 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 23 Feb 2024 12:19:21 +0100 Subject: [PATCH 39/88] test(slicing): mermaid link shown again --- test/functionality/_helper/shell.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index 4d2123df1c..e375ba738a 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -225,10 +225,10 @@ export function assertSliced(name: string, shell: RShell, input: string, criteri try { assert.strictEqual( result.reconstruct.code, expected, - //`got: ${result.reconstruct.code}, vs. expected: ${expected}, for input ${input} (slice: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` + `got: ${result.reconstruct.code}, vs. expected: ${expected}, for input ${input} (slice: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` ) } catch(e) { - //console.error('vis-got:\n', graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap)) + console.error('vis-got:\n', graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap)) throw e } }) From 5345f1958f8ac2097127765b3b67d8ab2108a2b3 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 23 Feb 2024 13:03:31 +0100 Subject: [PATCH 40/88] minor patches to get the tests running --- src/reconstruct/reconstruct.ts | 1 + test/functionality/_helper/shell.ts | 4 ++-- test/functionality/slicing/reconstruct/simple-tests.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 25f02e54e6..e037d1e906 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -37,6 +37,7 @@ import { AutoSelectPredicate, getIndentString, merge } from './helper' import { SourcePosition, SourceRange} from '../util/range' +import { jsonReplacer } from '../util/json'; export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) diff --git a/test/functionality/_helper/shell.ts b/test/functionality/_helper/shell.ts index e375ba738a..e04e228dd1 100644 --- a/test/functionality/_helper/shell.ts +++ b/test/functionality/_helper/shell.ts @@ -38,8 +38,8 @@ export const testWithShell = (msg: string, fn: (shell: RShell, test: Mocha.Conte function installWarning(pkg: string) { const banner = '-'.repeat(142) console.error(`${banner} -Test's have to install package ${pkg}. -This slows them down significantly! +Test's have to install package ${pkg}. +This slows them down significantly! Please see https://github.com/Code-Inspect/flowr/wiki/Linting-and-Testing#oh-no-the-tests-are-slow for more information. ${banner}`) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 3701a3c41f..5a1c0afc5e 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -98,8 +98,8 @@ describe('Simple', withShell(shell => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ {name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }'}, {name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2'}, - {name: 'multi-line function', case: 'a <- function (x) { x <- 2;\n x + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\n x + 4 }'}, - {name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2'} + {name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }'}, + {name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2;'} ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 20ec3a8501aca56f250b694599fc075c403c915e Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 23 Feb 2024 13:09:20 +0100 Subject: [PATCH 41/88] removing only marker --- test/functionality/slicing/reconstruct/simple-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 5a1c0afc5e..76e7ed4863 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -94,7 +94,7 @@ describe('Simple', withShell(shell => { } }) - describe.only('function definition', () => { + describe('function definition', () => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ {name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }'}, {name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2'}, From 6ffcf9e7f493aa8b2c7d69f986ec0bc50f0110bf Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:11:14 +0100 Subject: [PATCH 42/88] tests-fix(reconstruct): adjusted linePart tests --- package-lock.json | 13 ++++++------- .../slicing/reconstruct/function-tests.ts | 19 ++++++++++--------- .../slicing/reconstruct/simple-tests.ts | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae98c55fba..f00dc336cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.3.14", "license": "ISC", "dependencies": { - "@types/node": "^20.11.3", "@types/semver": "^7.5.6", "@types/tar": "^6.1.10", "@xmldom/xmldom": "^0.8.10", @@ -5697,9 +5696,9 @@ } }, "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz", + "integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ==", "dev": true }, "node_modules/is-arguments": { @@ -9517,9 +9516,9 @@ } }, "node_modules/socks/node_modules/ip": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", - "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==", "dev": true }, "node_modules/source-map": { diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 8ece46d671..a7c5885698 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -143,21 +143,22 @@ describe('Functions Reconstruct', () => { } }) }) - describe('printLinePart', () => { - function positive(input: PrettyPrintLinePart[], expected: string, msg: string) { + describe.only('printLinePart', () => { + function positive(input: PrettyPrintLinePart[], expected: string, msg: string, columnOffset: number) { it(`Convert ${JSON.stringify(input)} to string`, () => { - const out = prettyPrintPartToString(input, input[0].loc.column) + const out = prettyPrintPartToString(input, columnOffset) assert.strictEqual(out, expected, msg) }) } for(const testCase of [ - {input: [{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello',msg: 'No Spaces anywhere'}, - {input: [{part: 'Hello World', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get preserved'}, - {input: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 0, column: 7}}],expected: 'Hello World',msg: 'Spaces get added within the string'}, - {input: [{part: 'Hello', loc: {line: 0, column: 7}}],expected: ' Hello',msg: 'Spaces get added at the beginning'}, - {input: [{part: 'World', loc: {line: 0, column: 7}},{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order'}, + {input: [{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0}, + {input: [{part: 'Hello World', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0}, + {input: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 0, column: 6}}],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0}, + //This test case will always fail as we correct single line offsets + {input: [{part: 'Hello', loc: {line: 0, column: 6}}],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0}, + {input: [{part: 'World', loc: {line: 0, column: 6}},{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0}, ]) { - positive(testCase.input, testCase.expected, testCase.msg) + positive(testCase.input, testCase.expected, testCase.msg, testCase.columnOffset) } }) }) \ No newline at end of file diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 3701a3c41f..de5ffb5c7a 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -94,7 +94,7 @@ describe('Simple', withShell(shell => { } }) - describe.only('function definition', () => { + describe('function definition', () => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ {name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }'}, {name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2'}, From a8229ba0f38d4c50590d28165d56b466ab6ca1cf Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:13:03 +0100 Subject: [PATCH 43/88] test-fix(reconstruct): removed .only from test --- test/functionality/slicing/reconstruct/function-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index a7c5885698..db892db848 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -143,7 +143,7 @@ describe('Functions Reconstruct', () => { } }) }) - describe.only('printLinePart', () => { + describe('printLinePart', () => { function positive(input: PrettyPrintLinePart[], expected: string, msg: string, columnOffset: number) { it(`Convert ${JSON.stringify(input)} to string`, () => { const out = prettyPrintPartToString(input, columnOffset) From 5425223bae0c0144a836d40c20333dd77f11051a Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:18:03 +0100 Subject: [PATCH 44/88] test(reconstruct): removed unneccessary comment --- test/functionality/slicing/reconstruct/function-tests.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index db892db848..102864e08d 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -154,7 +154,6 @@ describe('Functions Reconstruct', () => { {input: [{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0}, {input: [{part: 'Hello World', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0}, {input: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 0, column: 6}}],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0}, - //This test case will always fail as we correct single line offsets {input: [{part: 'Hello', loc: {line: 0, column: 6}}],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0}, {input: [{part: 'World', loc: {line: 0, column: 6}},{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0}, ]) { From 227348bf3ac3809cd494a878b367cd3b768a2879 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 23 Feb 2024 13:23:31 +0100 Subject: [PATCH 45/88] feat-fix(reconstruct): removed unused import --- src/reconstruct/reconstruct.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index e037d1e906..25f02e54e6 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -37,7 +37,6 @@ import { AutoSelectPredicate, getIndentString, merge } from './helper' import { SourcePosition, SourceRange} from '../util/range' -import { jsonReplacer } from '../util/json'; export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) From 5ae5b74bd97ffe0bb636d6050028080de0a8c12d Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 23 Feb 2024 15:53:19 +0100 Subject: [PATCH 46/88] lint-fix, refactor(reconstruct): some love? --- src/core/input.ts | 3 +- src/core/steps.ts | 3 +- .../ast/parser/xml/internal/loops/for.ts | 6 +- .../xml/internal/structure/single-element.ts | 2 +- src/reconstruct/helper.ts | 7 +- src/reconstruct/main.ts | 10 +-- src/reconstruct/reconstruct.ts | 65 ++++++------------- .../slicing/reconstruct/function-tests.ts | 6 +- 8 files changed, 41 insertions(+), 61 deletions(-) diff --git a/src/core/input.ts b/src/core/input.ts index 139e7ceba5..1da46c7cc3 100644 --- a/src/core/input.ts +++ b/src/core/input.ts @@ -1,8 +1,9 @@ import type { MergeableRecord } from '../util/objects' import type { IdGenerator, NoInfo, RParseRequest, RShell, XmlParserHooks } from '../r-bridge' import type { DeepPartial } from 'ts-essentials' -import type { AutoSelectPredicate, SlicingCriteria } from '../slicing' +import type { SlicingCriteria } from '../slicing' import type { STEPS_PER_SLICE, StepName, STEPS_PER_FILE } from './steps' +import type { AutoSelectPredicate } from '../reconstruct/helper' /** * We split the types, as if you are only interested in what can be done per-file, you do not need a slicing criterion. diff --git a/src/core/steps.ts b/src/core/steps.ts index 8125bb56b1..54c854b0a7 100644 --- a/src/core/steps.ts +++ b/src/core/steps.ts @@ -16,7 +16,6 @@ import type { MergeableRecord } from '../util/objects' import { retrieveCsvFromRCode } from '../r-bridge' import { produceDataFlowGraph } from '../dataflow' -import { reconstructToCode, staticSlicing } from '../slicing' import type { IStepPrinter} from './print/print' import { internalPrinter, StepOutputFormat } from './print/print' import { @@ -36,6 +35,8 @@ import { import type {DataflowInformation} from '../dataflow/internal/info' import type {runAbstractInterpretation} from '../abstract-interpretation/processor' import {normalize} from '../r-bridge/lang-4.x/ast/parser/csv/parser' +import {staticSlicing} from '../slicing' +import {reconstructToCode} from '../reconstruct/main' /** * This represents close a function that we know completely nothing about. diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts index 675ddce99c..db17e9e748 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts @@ -1,16 +1,16 @@ import type { NamedXmlBasedJson, XmlBasedJson} from '../../input-format' -import {childrenKey} from '../../input-format' -import { getKeysGuarded, XmlParseError } from '../../input-format' +import { getKeysGuarded, XmlParseError, childrenKey } from '../../input-format' import { ensureExpressionList, getTokenType, retrieveMetaStructure } from '../meta' import { guard } from '../../../../../../../util/assert' import type { ParserData } from '../../data' import { tryNormalizeSymbol } from '../values' import { normalizeBasedOnType, splitComments, tryNormalizeSingleNode } from '../structure' -import type { RComment, RForLoop, RNode, RSymbol} from '../../../../model' +import type { RForLoop, RNode, RSymbol} from '../../../../model' import { RawRType, RType } from '../../../../model' import { executeHook, executeUnknownHook } from '../../hooks' import { normalizeComment } from '../other' import {parseLog} from '../../../csv/parser' +import type {RDelimiter} from '../../../../model/nodes/info' export function tryNormalizeFor( data: ParserData, diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts index 72a20018ca..8985c3043d 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts @@ -10,7 +10,7 @@ import { RawRType, RType } from '../../../../model' import { normalizeComment } from '../other' import { normalizeBreak, normalizeNext } from '../loops' import { normalizeLineDirective } from '../other/line-directive' -import type { RDelimiter } from '../../../../model/nodes/info' +import type {RDelimiter, RDelimiterNode} from '../../../../model/nodes/info' function normalizeDelimiter(data: ParserData, elem: NamedXmlBasedJson): RDelimiter { const { diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 79afc3415c..acd2eaad1b 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,6 +1,7 @@ -import { NodeId, ParentInformation, RNode, RType } from '../r-bridge' -import { SourcePosition } from '../util/range' -import { ReconstructionConfiguration } from './reconstruct' +import type { NodeId, ParentInformation, RNode} from '../r-bridge' +import { RType } from '../r-bridge' +import type { SourcePosition } from '../util/range' +import type { ReconstructionConfiguration } from './reconstruct' export type Code = PrettyPrintLine[] export type Selection = Set diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index 6f70f806b4..c4d92fc3f3 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,10 +1,12 @@ -import { ReconstructionResult, reconstructLogger, reconstructAstFolds } from './reconstruct' +import type { ReconstructionResult} from './reconstruct' +import { reconstructLogger, reconstructAstFolds } from './reconstruct' import {prettyPrintCodeToString, removeOuterExpressionListIfApplicable} from './helper' import { autoSelectLibrary } from './helper' -import { AutoSelectPredicate } from './helper' -import { Selection } from './helper' +import type { AutoSelectPredicate } from './helper' +import type { Selection } from './helper' -import { NormalizedAst, RNode, ParentInformation, foldAstStateful } from '../r-bridge' +import type { NormalizedAst, RNode, ParentInformation} from '../r-bridge' +import { foldAstStateful } from '../r-bridge' import { LogLevel } from '../util/log' diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 8260eee25e..309f492a8e 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -5,8 +5,6 @@ */ import type { - NormalizedAst, - NodeId, ParentInformation, RAccess, RArgument, @@ -21,23 +19,26 @@ import type { RRepeatLoop, RWhileLoop, RPipe, - StatefulFoldFunctions, -} from '../r-bridge' + StatefulFoldFunctions} from '../r-bridge' +import { RType } from '../r-bridge' import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' -import { MergeableRecord } from '../util/objects' -import { +import type { MergeableRecord } from '../util/objects' +import type { Selection, - prettyPrintPartToString, PrettyPrintLine, - plain, Code, + AutoSelectPredicate} from './helper' +import { + prettyPrintPartToString, + plain, indentBy, isSelected, removeExpressionListWrap, - AutoSelectPredicate, - getIndentString, merge } from './helper' -import { SourcePosition, SourceRange} from '../util/range' + getIndentString, + merge +} from './helper' +import type { SourcePosition, SourceRange} from '../util/range' export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -97,7 +98,7 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { - return plain(getLexeme(n), n.lhs.location? n.lhs.location.start : n.location.start) + return plain(getLexeme(n), n.lhs.location?.start ?? n.location.start) } if(lhs.length === 0 && rhs.length === 0) { @@ -108,7 +109,7 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe' : n.operator, rhs) @@ -117,17 +118,17 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = loop.info.fullRange?.start //may be unnesseccary if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), start ? start :loop.location.start) + return plain(getLexeme(loop), start ?? loop.location.start) } if(isSelected(configuration, loop.body)) { return merge([body]) } const additionalTokens = reconstructAdditionalTokens(loop) const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc - vectorLocation.column -= 1 //somehow the vector is consitently one space to late + vectorLocation.column -= 1 //somehow the vector is consistently one space to late const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) const out = merge([ - [{ linePart: [{part: 'for', loc: start ? start :loop.location.start}], indent: 0 }], + [{ linePart: [{part: 'for', loc: start ?? loop.location.start}], indent: 0 }], [{ linePart: [{part: getLexeme(loop.variable), loc: loop.variable.location.start}], indent: 0}], reconstructedVector, ...additionalTokens @@ -210,7 +211,7 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co } const additionalTokens = reconstructAdditionalTokens(loop) const out = merge([ - [{ linePart: [{part: `while(${getLexeme(loop.condition)})`, loc: start ? start :loop.location.start}], indent: 0 }], + [{ linePart: [{part: `while(${getLexeme(loop.condition)})`, loc: start ?? loop.location.start}], indent: 0 }], ...additionalTokens ]) if(body.length < 1) { @@ -218,8 +219,7 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co const hBody = out[out.length - 1].linePart const bodyLoc = {line: hBody[hBody.length - 1].loc.line, column: hBody[hBody.length - 1].loc.column + hBody[hBody.length - 1].part.length} out.push({linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 1}}], indent: 0}) - } - else { + } else { out.push(...body) } return merge([out]) @@ -299,7 +299,6 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition we add the braces just to be sure - return [ - { linePart: [{part: `function(${parameters}) {`, loc: startPos}], indent: 0 }, - ...indentBy(body, 1), - { linePart: [{part: '}', loc: endPos}], indent: 0 } - ] - } - } function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: RFunctionCall): Code { diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 102864e08d..86d0020f23 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -1,5 +1,6 @@ import { assert } from 'chai' -import { plain, Code, merge, prettyPrintCodeToString, PrettyPrintLinePart, prettyPrintPartToString } from '../../../../src/reconstruct/helper' +import type { Code, PrettyPrintLinePart} from '../../../../src/reconstruct/helper' +import { plain, merge, prettyPrintCodeToString, prettyPrintPartToString } from '../../../../src/reconstruct/helper' describe('Functions Reconstruct', () => { describe('plain', () => { @@ -123,8 +124,7 @@ describe('Functions Reconstruct', () => { const nextLoc = part.loc if(currentLoc.line <= nextLoc.line && currentLoc.column <= nextLoc.column) { currentLoc = nextLoc - } - else { + } else { return false } } From df6e39fb82e26445455fb9a6a8f3a71b8b4d7ad9 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 23 Feb 2024 16:02:45 +0100 Subject: [PATCH 47/88] refactor: remove console logging and stuff --- src/reconstruct/reconstruct.ts | 4 ---- test/functionality/_helper/shell.ts | 14 ++++---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 309f492a8e..bd92365c4c 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -284,12 +284,10 @@ function reconstructParameter(parameter: RParameter, name: Co function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { - console.log('function without parameter unselected') return merge([body]) } if(isSelected(configuration, definition.body)) { - console.log('fbody selected') const out = merge([body]) const h = out[out.length - 1].linePart if(h[h.length - 1].part === ';') { @@ -301,11 +299,9 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` - ) - } catch(e) { - console.error('vis-got:\n', graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap)) - throw e - } + assert.strictEqual( + result.reconstruct.code, expected, + `got: ${result.reconstruct.code}, vs. expected: ${expected}, for input ${input} (slice: ${printIdMapping(result.slice.decodedCriteria.map(({ id }) => id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` + ) }) } From 2ef889249646923d806672c9a4080a78009d7dc5 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 27 Feb 2024 19:57:23 +0100 Subject: [PATCH 48/88] feat-fix: fixed linting problems --- src/core/steps.ts | 2 - .../ast/parser/xml/internal/loops/for.ts | 4 +- .../xml/internal/structure/single-element.ts | 2 +- src/reconstruct/helper.ts | 8 +- src/reconstruct/main.ts | 6 +- src/reconstruct/reconstruct.ts | 52 ++++++------ .../slicing/reconstruct/function-tests.ts | 80 +++++++++---------- .../slicing/reconstruct/simple-tests.ts | 8 +- 8 files changed, 81 insertions(+), 81 deletions(-) diff --git a/src/core/steps.ts b/src/core/steps.ts index c209d6893b..09f857db4a 100644 --- a/src/core/steps.ts +++ b/src/core/steps.ts @@ -34,8 +34,6 @@ import { dataflowGraphToMermaidUrl, dataflowGraphToQuads } from './print/dataflow-printer' -import type {DataflowInformation} from '../dataflow/internal/info' -import type {runAbstractInterpretation} from '../abstract-interpretation/processor' import { normalize } from '../r-bridge/lang-4.x/ast/parser/json/parser' /** diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts index a7cffffdc8..9b588c7b9a 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/loops/for.ts @@ -6,8 +6,8 @@ import { guard } from '../../../../../../../util/assert' import type { ParserData } from '../../data' import { tryNormalizeSymbol } from '../values' import { normalizeBasedOnType, splitComments, tryNormalizeSingleNode } from '../structure' -import type {RDelimiter} from '../../../../model/nodes/info' -import type { RComment, RForLoop, RNode, RSymbol } from '../../../../model' +import type { RDelimiter } from '../../../../model/nodes/info' +import type { RForLoop, RNode, RSymbol } from '../../../../model' import { RawRType, RType } from '../../../../model' import { executeHook, executeUnknownHook } from '../../hooks' import { normalizeComment } from '../other' diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts index 2082917f72..fcd9157cc1 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/single-element.ts @@ -10,7 +10,7 @@ import { RawRType, RType } from '../../../../model' import { normalizeComment } from '../other' import { normalizeBreak, normalizeNext } from '../loops' import { normalizeLineDirective } from '../other/line-directive' -import type {RDelimiter, RDelimiterNode} from '../../../../model/nodes/info' +import type { RDelimiter, RDelimiterNode } from '../../../../model/nodes/info' function normalizeDelimiter(data: ParserData, elem: NamedXmlBasedJson): RDelimiter { const { diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index acd2eaad1b..ea6f2d1793 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,4 +1,4 @@ -import type { NodeId, ParentInformation, RNode} from '../r-bridge' +import type { NodeId, ParentInformation, RNode } from '../r-bridge' import { RType } from '../r-bridge' import type { SourcePosition } from '../util/range' import type { ReconstructionConfiguration } from './reconstruct' @@ -18,12 +18,12 @@ export interface PrettyPrintLine { * Splits text on linebreak to create lineParts and encapsulates them in the Code type */ export function plain(text: string, location: SourcePosition): Code { - const printLine: PrettyPrintLine = {linePart: [], indent: 0} + const printLine: PrettyPrintLine = { linePart: [], indent: 0 } const split = text.split('\n') let locationLine = location.line for(const line of split) { - printLine.linePart.push({part: line, loc: { column: location.column, line: locationLine++ }}) + printLine.linePart.push({ part: line, loc: { column: location.column, line: locationLine++ } }) } return [printLine] } @@ -41,7 +41,7 @@ export function merge(snipbits: Code[]): Code { for(const part of line.linePart) { const lineNumber = part.loc.line if(buckets[lineNumber] === undefined) { //may be necessary as empty elements count as undefined and we don't want to reassign filled buckets - buckets[lineNumber] = {linePart: [], indent: line.indent} + buckets[lineNumber] = { linePart: [], indent: line.indent } } buckets[lineNumber].linePart.push(part) } diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index c4d92fc3f3..d13d38b1b9 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,11 +1,11 @@ -import type { ReconstructionResult} from './reconstruct' +import type { ReconstructionResult } from './reconstruct' import { reconstructLogger, reconstructAstFolds } from './reconstruct' -import {prettyPrintCodeToString, removeOuterExpressionListIfApplicable} from './helper' +import { prettyPrintCodeToString, removeOuterExpressionListIfApplicable } from './helper' import { autoSelectLibrary } from './helper' import type { AutoSelectPredicate } from './helper' import type { Selection } from './helper' -import type { NormalizedAst, RNode, ParentInformation} from '../r-bridge' +import type { NormalizedAst, RNode, ParentInformation } from '../r-bridge' import { foldAstStateful } from '../r-bridge' import { LogLevel } from '../util/log' diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index bd92365c4c..94ce84e76d 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -19,7 +19,7 @@ import type { RRepeatLoop, RWhileLoop, RPipe, - StatefulFoldFunctions} from '../r-bridge' + StatefulFoldFunctions } from '../r-bridge' import { RType } from '../r-bridge' import { log } from '../util/log' import { guard, isNotNull } from '../util/assert' @@ -28,7 +28,7 @@ import type { Selection, PrettyPrintLine, Code, - AutoSelectPredicate} from './helper' + AutoSelectPredicate } from './helper' import { prettyPrintPartToString, plain, @@ -38,7 +38,7 @@ import { getIndentString, merge } from './helper' -import type { SourcePosition, SourceRange} from '../util/range' +import type { SourcePosition, SourceRange } from '../util/range' export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -56,11 +56,11 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC // return selectionHasLeaf ? wouldBe : [] } -const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start : {line: 0, column: 0}) +const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start : { line: 0, column: 0 }) function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, exprList)) { - const positionStart = exprList.location? exprList.location.start : {line: 0, column: 0} + const positionStart = exprList.location? exprList.location.start : { line: 0, column: 0 } return plain(getLexeme(exprList), positionStart) } @@ -128,8 +128,8 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v vectorLocation.column -= 1 //somehow the vector is consistently one space to late const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) const out = merge([ - [{ linePart: [{part: 'for', loc: start ?? loop.location.start}], indent: 0 }], - [{ linePart: [{part: getLexeme(loop.variable), loc: loop.variable.location.start}], indent: 0}], + [{ linePart: [{ part: 'for', loc: start ?? loop.location.start }], indent: 0 }], + [{ linePart: [{ part: getLexeme(loop.variable), loc: loop.variable.location.start }], indent: 0 }], reconstructedVector, ...additionalTokens ]) @@ -138,7 +138,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v // puts {} with one space separation after for(...) const hBody = out[out.length - 1].linePart const bodyLoc = hBody[hBody.length - 1].loc - out.push({ linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 2}}], indent: 0}) + out.push({ linePart: [{ part: '{}', loc: { line: bodyLoc.line, column: bodyLoc.column + 2 } }], indent: 0 }) return out } //normal reconstruct @@ -159,7 +159,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } else { const additionalTokens = reconstructAdditionalTokens(loop) return merge([ - [{ linePart: [{part: 'repeat', loc: loop.location.start}], indent: 0 }], + [{ linePart: [{ part: 'repeat', loc: loop.location.start }], indent: 0 }], body, ...additionalTokens ]) @@ -169,7 +169,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = ifThenElse.location.start const endPos = ifThenElse.location.end - const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : {line: 0, column: 0} + const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : { line: 0, column: 0 } if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse), startPos) } @@ -179,27 +179,27 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi } if(otherwise.length === 0 && when.length === 0) { return [ - { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) { }`, loc: startPos}], indent: 0 } + { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) { }`, loc: startPos }], indent: 0 } ] } else if(otherwise.length === 0) { return [ - { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos}], indent: 0 }, + { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos }], indent: 0 }, ...indentBy(removeExpressionListWrap(when), 1), - { linePart: [{part: '}', loc: endPos}], indent: 0 } + { linePart: [{ part: '}', loc: endPos }], indent: 0 } ] } else if(when.length === 0) { return [ - { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) { } else {`, loc: startPos}], indent: 0 }, + { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) { } else {`, loc: startPos }], indent: 0 }, ...indentBy(removeExpressionListWrap(otherwise), 1), - { linePart: [{part: '}', loc: startPos}], indent: 0 } + { linePart: [{ part: '}', loc: startPos }], indent: 0 } ] } else { return [ - { linePart: [{part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos}], indent: 0 }, + { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos }], indent: 0 }, ...indentBy(removeExpressionListWrap(when), 1), - { linePart: [{part: '} else {', loc: conditionPos}], indent: 0 }, + { linePart: [{ part: '} else {', loc: conditionPos }], indent: 0 }, ...indentBy(removeExpressionListWrap(otherwise), 1), - { linePart: [{part: '}', loc: endPos}], indent: 0 } + { linePart: [{ part: '}', loc: endPos }], indent: 0 } ] } } @@ -211,14 +211,14 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co } const additionalTokens = reconstructAdditionalTokens(loop) const out = merge([ - [{ linePart: [{part: `while(${getLexeme(loop.condition)})`, loc: start ?? loop.location.start}], indent: 0 }], + [{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? loop.location.start }], indent: 0 }], ...additionalTokens ]) if(body.length < 1) { //this puts {} one space after while(...) const hBody = out[out.length - 1].linePart - const bodyLoc = {line: hBody[hBody.length - 1].loc.line, column: hBody[hBody.length - 1].loc.column + hBody[hBody.length - 1].part.length} - out.push({linePart: [{part: '{}', loc: {line: bodyLoc.line, column: bodyLoc.column + 1}}], indent: 0}) + const bodyLoc = { line: hBody[hBody.length - 1].loc.line, column: hBody[hBody.length - 1].loc.column + hBody[hBody.length - 1].part.length } + out.push({ linePart: [{ part: '{}', loc: { line: bodyLoc.line, column: bodyLoc.column + 1 } }], indent: 0 }) } else { out.push(...body) } @@ -308,10 +308,12 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition): Code { diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 86d0020f23..775e0ebba4 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -1,22 +1,22 @@ import { assert } from 'chai' -import type { Code, PrettyPrintLinePart} from '../../../../src/reconstruct/helper' +import type { Code, PrettyPrintLinePart } from '../../../../src/reconstruct/helper' import { plain, merge, prettyPrintCodeToString, prettyPrintPartToString } from '../../../../src/reconstruct/helper' describe('Functions Reconstruct', () => { describe('plain', () => { function positive(input: string, line: number, column: number, expected: Code) { it(`${input} for ${line}:${column}`, () => { - const result:Code = plain(input, {line,column}) + const result:Code = plain(input, { line,column }) assert.deepStrictEqual(result,expected) }) } for(const testCase of [ - {input: 'Hello', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}}],indent: 0}]}, - {input: 'Hello World', line: 4, column: 3, expected: [{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]}, - {input: 'Hello\nWorld', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 2, column: 1}}],indent: 0}]}, - {input: 'Hello\nWorld', line: 3, column: 4, expected: [{linePart: [{part: 'Hello', loc: {line: 3, column: 4}},{part: 'World', loc: {line: 4, column: 4}}],indent: 0}]}, - {input: 'Hello\n World\n24', line: 1, column: 1, expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: ' World', loc: {line: 2, column: 1}},{part: '24', loc: {line: 3, column: 1}}],indent: 0}]} + { input: 'Hello', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } }],indent: 0 }] }, + { input: 'Hello World', line: 4, column: 3, expected: [{ linePart: [{ part: 'Hello World', loc: { line: 4, column: 3 } }],indent: 0 }] }, + { input: 'Hello\nWorld', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: 'World', loc: { line: 2, column: 1 } }],indent: 0 }] }, + { input: 'Hello\nWorld', line: 3, column: 4, expected: [{ linePart: [{ part: 'Hello', loc: { line: 3, column: 4 } },{ part: 'World', loc: { line: 4, column: 4 } }],indent: 0 }] }, + { input: 'Hello\n World\n24', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: ' World', loc: { line: 2, column: 1 } },{ part: '24', loc: { line: 3, column: 1 } }],indent: 0 }] } ]) { positive(testCase.input, testCase.line, testCase.column, testCase.expected) } @@ -31,8 +31,8 @@ describe('Functions Reconstruct', () => { } describe('single line merge', () => { for(const testCase of [ - {snipbit: [[{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]], expected: [{linePart: [{part: 'Hello World', loc: {line: 4, column: 3}}],indent: 0}]}, - {snipbit: [[{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 1, column: 1}}],indent: 0}]], expected: [{linePart: [{part: 'Hello', loc: {line: 1, column: 1}},{part: 'World', loc: {line: 1, column: 1}}],indent: 0}]} + { snipbit: [[{ linePart: [{ part: 'Hello World', loc: { line: 4, column: 3 } }],indent: 0 }]], expected: [{ linePart: [{ part: 'Hello World', loc: { line: 4, column: 3 } }],indent: 0 }] }, + { snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: 'World', loc: { line: 1, column: 1 } }],indent: 0 }]], expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: 'World', loc: { line: 1, column: 1 } }],indent: 0 }] } ]) { positive(testCase.snipbit, testCase.expected) } @@ -41,29 +41,29 @@ describe('Functions Reconstruct', () => { const testCases = [ //Case 1 (in order) { - snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}],[{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }],[{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] }, { - snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] }, { - snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 0}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 1, column: 7}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}, {part: '24', loc: {line: 1, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 1, column: 7 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }, { part: '24', loc: { line: 1, column: 7 } }], indent: 0 }] }, //Case 2 (out of order) { - snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}],[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }],[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] }, { - snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 0}}, {part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }, { part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] }, { - snipbit: [[{linePart: [{part: '24', loc: {line: 1, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 0}}, {part: '24', loc: {line: 1, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: '24', loc: { line: 1, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }, { part: '24', loc: { line: 1, column: 7 } }], indent: 0 }] } ] for(const testCase of testCases) { @@ -74,33 +74,33 @@ describe('Functions Reconstruct', () => { const testCases = [ //Case 1 (in order) 123 { - snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 1, column: 7}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] }, //Case 2 (2nd Line out of order) 213 { - snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 7}}, {part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }, { part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] }, //Case 3 (3rd Line out of order) 231 { - snipbit: [[{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}],[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] }, //Case 4 (reverse order) 321 { - snipbit: [[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}],[{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }],[{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] }, //Case 5 () 312 { - snipbit: [[{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}], [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}], [{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }], [{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] }, //Case 6 () 132 { - snipbit: [[{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0}], [{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}], [{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0}]], - expected: [{linePart: [{part: 'Hello', loc: {line: 0, column: 0}}], indent: 0},{linePart: [{part: 'World', loc: {line: 1, column: 7}}], indent: 0},{linePart: [{part: '24', loc: {line: 2, column: 7}}], indent: 0}] + snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }], [{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }]], + expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] } ] for(const testCase of testCases) { @@ -112,13 +112,13 @@ describe('Functions Reconstruct', () => { const testCase = [] as Code[] const partPool = ['Hello', 'World', 'FlowR', 'Is', 'Incredible'] for(let i = 0; i < lines; i++) { - const line: Code = [{linePart: [{part: partPool[Math.random() % 5], loc: {line: Math.random() % lines, column: Math.random() % 15}}], indent: 0}] + const line: Code = [{ linePart: [{ part: partPool[Math.random() % 5], loc: { line: Math.random() % lines, column: Math.random() % 15 } }], indent: 0 }] testCase.push(line) } return testCase } function checkTestCase(code: Code): boolean { - let currentLoc = {line: 0, column: 0} + let currentLoc = { line: 0, column: 0 } for(const line of code) { for(const part of line.linePart) { const nextLoc = part.loc @@ -151,11 +151,11 @@ describe('Functions Reconstruct', () => { }) } for(const testCase of [ - {input: [{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0}, - {input: [{part: 'Hello World', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0}, - {input: [{part: 'Hello', loc: {line: 0, column: 0}}, {part: 'World', loc: {line: 0, column: 6}}],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0}, - {input: [{part: 'Hello', loc: {line: 0, column: 6}}],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0}, - {input: [{part: 'World', loc: {line: 0, column: 6}},{part: 'Hello', loc: {line: 0, column: 0}}],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0}, + { input: [{ part: 'Hello', loc: { line: 0, column: 0 } }],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0 }, + { input: [{ part: 'Hello World', loc: { line: 0, column: 0 } }],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0 }, + { input: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 0, column: 6 } }],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0 }, + { input: [{ part: 'Hello', loc: { line: 0, column: 6 } }],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0 }, + { input: [{ part: 'World', loc: { line: 0, column: 6 } },{ part: 'Hello', loc: { line: 0, column: 0 } }],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0 }, ]) { positive(testCase.input, testCase.expected, testCase.msg, testCase.columnOffset) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index def9345a91..421e373322 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -96,10 +96,10 @@ describe('Simple', withShell(shell => { describe('function definition', () => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ - {name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }'}, - {name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2'}, - {name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }'}, - {name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2;'} + { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, + { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, + { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, + { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2;' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 677d8b89878c97adbe6a18a99d86867a1a246791 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 27 Feb 2024 22:24:59 +0100 Subject: [PATCH 49/88] test-fix(reconstructed): function definition reconstruct --- src/reconstruct/helper.ts | 25 +++++++++++++++++++ src/reconstruct/reconstruct.ts | 5 ++-- test/functionality/_helper/shell.ts | 1 + .../static-program-slices/calls-tests.ts | 8 +++--- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index ea6f2d1793..66b8569eba 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -28,6 +28,31 @@ export function plain(text: string, location: SourcePosition): Code { return [printLine] } +export function plainSplit(text: string, location: SourcePosition): Code { + const printLine: PrettyPrintLine = { linePart: [], indent: 0 } + let i = 0 + let token = '' + let currLoc = { line: location.line, column: location.column } + while(i < text.length) { + if(text[i] === ' ') { + if(!(token === '')) { + printLine.linePart.push({ part: token, loc: currLoc }) + } + currLoc = { column: currLoc.column + token.length + 1, line: currLoc.line } + token = '' + } else if(text[i] === '\n') { + printLine.linePart.push({ part: token, loc: currLoc }) + currLoc = { column: location.column, line: currLoc.line + 1 } + token = '' + } else { + token = token.concat(text[i]) + } + i++ + } + printLine.linePart.push({ part: token, loc: currLoc }) + return [printLine] +} + /** * this function will merge up to n code pieces into a singular code piece, garanting that there are no duplicate lines and all lines are in order */ diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 94ce84e76d..9d3449ffe1 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -36,7 +36,8 @@ import { isSelected, removeExpressionListWrap, getIndentString, - merge + merge, + plainSplit } from './helper' import type { SourcePosition, SourceRange } from '../util/range' @@ -304,7 +305,7 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition id), result.normalize.idMap)}), url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)}` + //, url: ${graphToMermaidUrl(result.dataflow.graph, result.normalize.idMap, true, result.slice.result)} ) }) } diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 12a8e171b7..3812ecfa4f 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -1,7 +1,7 @@ import { assertSliced, withShell } from '../../_helper/shell' describe('Calls', withShell(shell => { - describe('Simple Calls', () => { + describe.only('Simple Calls', () => { const code = `i <- 4 a <- function(x) { x } a(i)` @@ -11,11 +11,13 @@ a(i)` const constFunction = `i <- 4 a <- function(x) { x <- 2; 1 } a(i)` + //currently fails, whole body gets reconstructed assertSliced('Function call with constant function', shell, constFunction, ['3:1'], `i <- 4 a <- function(x) { 1 } a(i)`) + //currently fails, whole body gets reconstructed assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x) { }') - assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2') + assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2;') assertSliced('Multiple unknown calls', shell, ` foo(x, y) foo(x, 3) @@ -33,7 +35,7 @@ x <- 3 c <- 4 y <- 3 f2(1,x) - `, ['7@f2'], `f1 <- function (a, b) { c } + `, ['7@f2'], `f1 <- function (a,b) { c } f2 <- function (...) { f1(...) } x <- 3 c <- 4 From f2ae6841163961bb8a48887963bd4ddd5a8fcf3f Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 1 Mar 2024 14:26:55 +0100 Subject: [PATCH 50/88] wip: maintaining function body after reconstruct --- .vscode/settings.json | 5 +++++ src/reconstruct/reconstruct.ts | 17 +++++------------ 2 files changed, 10 insertions(+), 12 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..9f28e87980 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "cSpell.words": [ + "unnesseccary" + ] +} \ No newline at end of file diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 9d3449ffe1..a468b941db 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -37,7 +37,8 @@ import { removeExpressionListWrap, getIndentString, merge, - plainSplit + plainSplit, + prettyPrintCodeToString } from './helper' import type { SourcePosition, SourceRange } from '../util/range' @@ -79,10 +80,10 @@ function reconstructExpressionList(exprList: RExpressionList, } } -function reconstructRawBinaryOperator(lhs: PrettyPrintLine[], n: string, rhs: PrettyPrintLine[]): Code { +function reconstructRawBinaryOperator(lhs: Code, n: string, rhs: Code): Code { return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), - { linePart: [{ part: `${lhs[lhs.length - 1].linePart[lhs.length - 1].part} ${n} ${rhs[0].linePart[0].part}`, loc: lhs[lhs.length - 1].linePart[lhs.length - 1].loc }], indent: 0 }, + { linePart: [{ part: `${prettyPrintCodeToString([lhs[lhs.length - 1]])} ${n} ${prettyPrintCodeToString([rhs[0]])}`, loc: lhs[lhs.length - 1].linePart[lhs.length - 1].loc }], indent: 0 }, ...indentBy(rhs.slice(1, rhs.length), 1) ] } @@ -298,21 +299,13 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition Date: Fri, 1 Mar 2024 14:38:19 +0100 Subject: [PATCH 51/88] wip: handle parameters in fun-def reconstruct --- .../slicing/static-program-slices/calls-tests.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 3812ecfa4f..4ad02164c0 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -23,20 +23,20 @@ foo(x, y) foo(x, 3) `, ['3@foo'], 'foo(x, 3)') assertSliced('Multiple unknown calls sharing known def', shell, ` -x. <- function (x) { x } +x. <- function(x) { x } foo(x, x.(y)) foo(x, x.(3)) - `, ['4@foo'], `x. <- function (x) { x } + `, ['4@foo'], `x. <- function(x) { x } foo(x, x.(3))`) assertSliced('Using ...', shell, ` -f1 <- function (a,b) { c } -f2 <- function (...) { f1(...) } +f1 <- function(a, b) { c } +f2 <- function(...) { f1(...) } x <- 3 c <- 4 y <- 3 f2(1,x) - `, ['7@f2'], `f1 <- function (a,b) { c } -f2 <- function (...) { f1(...) } + `, ['7@f2'], `f1 <- function(a, b) { c } +f2 <- function(...) { f1(...) } x <- 3 c <- 4 f2(1,x)`) From 6d59c2e065f22f21a8058d348ecd26259d02dcf8 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 1 Mar 2024 14:52:57 +0100 Subject: [PATCH 52/88] feat: reconstruct calls correctly --- .../parser/xml/internal/structure/elements.ts | 18 +++++++++++++++++- src/reconstruct/helper.ts | 2 +- src/reconstruct/reconstruct.ts | 10 +++++----- .../static-program-slices/calls-tests.ts | 8 ++++---- 4 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts index fce4f2e05c..f2570834c2 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts @@ -159,15 +159,31 @@ export function normalizeBasedOnType( if(splitOnSemicolon.length > 1) { log.trace(`found ${splitOnSemicolon.length} expressions by semicolon-split, parsing them separately`) const flattened = [] + let i = 0 + let semis = [] for(const sub of splitOnSemicolon) { const result = normalizeBasedOnType(data, sub) + const semi = semiColons[i++] if(result.length === 1 && result[0].type === RType.ExpressionList) { + if(semi) { + result[0].info.additionalTokens ??= [] + result[0].info.additionalTokens.push(semi) + } flattened.push(...result[0].children) } else { + if(semi) { + if(result.length > 0) { + const last = result[result.length - 1] as RNode + last.info.additionalTokens ??= []; + last.info.additionalTokens?.push(semi) + } else { + semis.push(semi) + } + } flattened.push(...result) } } - return [...flattened, ...semiColons] + return [...flattened, ...semis] } /* diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 66b8569eba..f6d53b2e53 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -58,7 +58,7 @@ export function plainSplit(text: string, location: SourcePosition): Code { */ export function merge(snipbits: Code[]): Code { const buckets: PrettyPrintLine[] = [] - const result:Code = [] + const result: Code = [] //separate and group lineParts by lines for(const code of snipbits) { diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index a468b941db..5894bf2531 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -68,11 +68,11 @@ function reconstructExpressionList(exprList: RExpressionList, const subExpressions = expressions.filter(e => e.length > 0) + const additionalTokens = reconstructAdditionalTokens(exprList) + if(subExpressions.length === 0) { - return [] + return merge(additionalTokens) } else { - const additionalTokens = reconstructAdditionalTokens(exprList) - return merge([ ...subExpressions, ...additionalTokens @@ -148,8 +148,8 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v return merge([out]) } -function reconstructAdditionalTokens(loop: RNodeWithParent): Code[] { - return loop.info.additionalTokens?.filter(t => t.lexeme && t.location) +function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { + return node.info.additionalTokens?.filter(t => t.lexeme && t.location) .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] } diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 4ad02164c0..7770dc1141 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -1,7 +1,7 @@ import { assertSliced, withShell } from '../../_helper/shell' describe('Calls', withShell(shell => { - describe.only('Simple Calls', () => { + describe('Simple Calls', () => { const code = `i <- 4 a <- function(x) { x } a(i)` @@ -13,11 +13,11 @@ a <- function(x) { x <- 2; 1 } a(i)` //currently fails, whole body gets reconstructed assertSliced('Function call with constant function', shell, constFunction, ['3:1'], `i <- 4 -a <- function(x) { 1 } +a <- function(x) { 1 } a(i)`) //currently fails, whole body gets reconstructed - assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x) { }') - assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2;') + assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x) { }') + assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2') assertSliced('Multiple unknown calls', shell, ` foo(x, y) foo(x, 3) From c9f01b1f201d92acae60ffb037b48af4a327ad96 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 7 Mar 2024 22:13:54 +0100 Subject: [PATCH 53/88] tests-fix(reconstruct): fixed layout-preserving some tests still fail as the reconstruction seems to still have problems --- src/reconstruct/helper.ts | 1 - test/functionality/r-bridge/executor.ts | 1 + .../lang/ast/parse-expression-lists.ts | 2 + test/functionality/r-bridge/sessions.ts | 1 + .../slicing/reconstruct/simple-tests.ts | 26 +++-- .../static-program-slices/calls-tests.ts | 105 ++++++++++-------- .../static-program-slices/simple-tests.ts | 42 +++++-- 7 files changed, 109 insertions(+), 69 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index f6d53b2e53..1cb2e16693 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -27,7 +27,6 @@ export function plain(text: string, location: SourcePosition): Code { } return [printLine] } - export function plainSplit(text: string, location: SourcePosition): Code { const printLine: PrettyPrintLine = { linePart: [], indent: 0 } let i = 0 diff --git a/test/functionality/r-bridge/executor.ts b/test/functionality/r-bridge/executor.ts index d57230dc27..3f8e502e6b 100644 --- a/test/functionality/r-bridge/executor.ts +++ b/test/functionality/r-bridge/executor.ts @@ -11,6 +11,7 @@ describe('RShellExecutor', function() { assert.isTrue(semver.gt(version, '0.0.0'), `the version ${JSON.stringify(version)} should not be 0.0.0`) }) + //this test fails, object a can not be found it('ignore errors', () => { const executor = new RShellExecutor() .addPrerequisites('options(warn=-1); invisible(Sys.setlocale("LC_MESSAGES", \'en_GB.UTF-8\'))') diff --git a/test/functionality/r-bridge/lang/ast/parse-expression-lists.ts b/test/functionality/r-bridge/lang/ast/parse-expression-lists.ts index 48f5727a0b..f8883ae16e 100644 --- a/test/functionality/r-bridge/lang/ast/parse-expression-lists.ts +++ b/test/functionality/r-bridge/lang/ast/parse-expression-lists.ts @@ -208,6 +208,7 @@ describe('Parse expression lists', ) }) + //this test fails, info apears at the wrong location describe('Expression lists with semicolons', () => { assertAst('"42;a" (two elements in same line)', shell, '42;a', @@ -278,6 +279,7 @@ describe('Parse expression lists', ) + //this test fails, info has false information assertAst('Inconsistent split with semicolon', shell, '1\n2; 3\n4', { diff --git a/test/functionality/r-bridge/sessions.ts b/test/functionality/r-bridge/sessions.ts index 5f537d5e35..f969c4b484 100644 --- a/test/functionality/r-bridge/sessions.ts +++ b/test/functionality/r-bridge/sessions.ts @@ -47,6 +47,7 @@ describe('RShell sessions', function() { }) ) }) + //this test fails, object a can not be found testWithShell('clear environment should remove variable information', async shell => { shell.continueOnError() // we will produce an error! shell.sendCommand('options(warn=-1); invisible(Sys.setlocale("LC_MESSAGES", \'en_GB.UTF-8\'))') diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 421e373322..382785775f 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -8,7 +8,8 @@ describe('Simple', withShell(shell => { '{ x <- 5 }', '{ x <- 5; y <- 9 }' ]) { - assertReconstructed(code, shell, code, '0', code.includes(';') ? 'x <- 5;' : 'x <- 5') + //some tests fail due to removed semicollons, here it is intensional but it later causes troubles + assertReconstructed(code, shell, code, '0', 'x <- 5') } }) describe('Nested Assignments', () => { @@ -37,7 +38,9 @@ describe('Simple', withShell(shell => { describe('repeat', () => { const pool: [string, string | string[], string][] = [ ['repeat { x }', '0', 'repeat { x }'], - ['repeat { x <- 5; y <- 9 }', '0', 'repeat { x <- 5; }'], + //semicollon removed + ['repeat { x <- 5; y <- 9 }', '0', 'repeat { x <- 5 }'], + //semicollon has to stay, otherwise the statement loses information ['repeat { x <- 5; y <- 9 }', ['0', '1', '4'], 'repeat { x <- 5; 9 }'] ] for(const [code, id, expected] of pool) { @@ -51,14 +54,15 @@ describe('Simple', withShell(shell => { const pool: [string, string | string[], string][] = [ ['while(TRUE) { x }', '1', 'while(TRUE) { x }'], ['while(TRUE) { x <- 5 }', '1', 'while(TRUE) { x <- 5 }'], - ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) { x <- 5; }'], - ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) { x <- 5; }'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) { x <- 5; }'], - ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { ; y <- 9 }'], + ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) { x <- 5 }'], + ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) { }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) { x <- 5 }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) { x <- 5 }'], + ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { y <- 9 }'], + //semicollon has to stay, otherwise the statement loses information ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) { x <- 5; y <- 9 }',], ['while(TRUE) {\n x <- 5\n y <- 9\n}', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], - ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], + ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) { }'], ['while(x + 2 > 3) { x <- 0 }', ['5'], 'while(x + 2 > 3) { x <- 0 }'], ['while(x + 2 > 3) { x <- 0 }', ['0', '5'], 'while(x + 2 > 3) { x <- 0 }'] ] @@ -76,7 +80,7 @@ describe('Simple', withShell(shell => { } ` const pool: [string, string | string[], string][] = [ - [largeFor, '0', 'for (i in 1:20) {}'], + [largeFor, '0', 'for (i in 1:20) {\n}'], [largeFor, '4', 'for (i in 1:20) {\n y <- 9\n}'], [largeFor, ['0', '4'], 'for (i in 1:20) {\n y <- 9\n}'], [largeFor, ['0', '4', '7'], `for (i in 1:20) { @@ -96,10 +100,12 @@ describe('Simple', withShell(shell => { describe('function definition', () => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ + //this test does not reconstruct the function { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, + //this test does not reconstruct the function { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, - { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2;' } + { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 7770dc1141..cb61a38c13 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -89,6 +89,7 @@ a(x = 3)` assertSliced('Must work for same named arguments too', shell, 'a <- 3\nb <- foo(a=a)', ['2@b'], 'a <- 3\nb <- foo(a=a)') + //this test fails, result gets shifted to the beginning of the line assertSliced('Must work for same named arguments nested', shell, ` f <- function(some_variable="hello") { result <- some::other(some_variable=some_variable) @@ -103,15 +104,13 @@ f <- function(some_variable="hello") { const lateCode = `f <- function(a=b, m=3) { b <- 1; a; b <- 5; a + 1 } f() ` - assertSliced('Late bindings of parameter in body', shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { - b <- 1 - a + 1 - } + //this test fails, semicollons should not be removed + assertSliced('Late bindings of parameter in body', shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { b <- 1 a + 1 } f()`) const lateCodeB = `f <- function(a=b, b=3) { b <- 1; a; b <- 5; a + 1 } f() ` - assertSliced('Late bindings of parameter in parameters', shell, lateCodeB, ['2@f'], `f <- function(a=b, b=3) { a + 1 } + assertSliced('Late bindings of parameter in parameters', shell, lateCodeB, ['2@f'], `f <- function(a=b, b=3) { a + 1 } f()`) assertSliced('Parameters binding context', shell, `f <- function(a=y) { a } a <- 5 @@ -126,9 +125,9 @@ f()`) const code = `a <- function() { a <- 2; return(function() { 1 }) } b <- a() b()` - assertSliced('Must include outer function', shell, code, ['2@a'], `a <- function() { return(function() { 1 }) } + assertSliced('Must include outer function', shell, code, ['2@a'], `a <- function() { return(function() { 1 }) } a()`) - assertSliced('Must include linked function', shell, code, ['3@b'], `a <- function() { return(function() { 1 }) } + assertSliced('Must include linked function', shell, code, ['3@b'], `a <- function() { return(function() { 1 }) } b <- a() b()`) }) @@ -139,22 +138,19 @@ y <- 5 z <- 5 u <- a() u()` - assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { - x <- function() { } - return(x) - } + + //this test fails, we still need to retain some semicollons + assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { x <- function() { }; return(x) } a()`) - assertSliced('Must include function shell on call', shell, code, ['6@u'], `a <- function() { - x <- function() { z + y } - y <- 12 - return(x) - } + //this test fails, semicollons should not get removed here + assertSliced('Must include function shell on call', shell, code, ['6@u'], `a <- function() { x <- function() { z + y }; y <- 12; return(x) } z <- 5 u <- a() u()`) }) }) describe('Anonymous functions', () => { + //this test fails, we get a GuardError assertSliced('Keep anonymous', shell, ` x <- (function() { x <- 4 @@ -170,29 +166,31 @@ cat(x)`) i <- 4 b <- function(f) { i <- 5; f() } b(a)` + //this test fails, somehow we get no output assertSliced('Only i, not bound in context', shell, code, ['1@i'], 'i') - assertSliced('Slice of b is independent', shell, code, ['3@b'], 'b <- function(f) { }') - assertSliced('Slice of b-call uses function', shell, code, ['4@b'], `a <- function() { i } -b <- function(f) { - i <- 5 - f() - } + //this test fails, the output produces an additional { } + assertSliced('Slice of b is independent', shell, code, ['3@b'], 'b <- function(f) { }') + //this test fails, semicollons need to be preserved here + assertSliced('Slice of b-call uses function', shell, code, ['4@b'], `a <- function() { i } +b <- function(f) { i <- 5; f() } b(a)`) assertSliced('Directly call returned function', shell, `m <- 12 a <- function(x) { - b <- function() { function() { x } } - return(b()) + b <- function() { function() { x } } + return(b()) } a(m)()`, ['$25' /* we can't directly slice the second call as the "a" name would take the inner call */], `m <- 12 a <- function(x) { - b <- function() { function() { x } } - return(b()) + b <- function() { function() { x } } + return(b()) } a(m)()`) assertSliced('Higher order anonymous function', shell, `a <- function(b) { b } -x <- a(function() 2 + 3)() + a(function() 7)()`, ['4@x'], `a <- function(b) { b } +x <- a(function() 2 + 3)() + a(function() 7)()`, ['4@x'], `a <- function(b) { + b +} x <- a(function() 2 + 3)() + a(function() 7)()`) }) describe('Side-Effects', () => { @@ -204,6 +202,7 @@ cat(x) f() cat(x)`) + //this test fails, output only produces { } and not the cat(x) assertSliced('Unimportant Side-Effect', shell, `f <- function() { y <<- 3 } f() cat(x) @@ -217,10 +216,10 @@ cat(x) } b <- f() `, ['8@b'], `f <- function() { - a <- function() { x } - x <- 2 - a() - } + a <- function() { x } + x <- 2 + a() +} b <- f()`) // that it contains x <- 2 is an error in the current implementation as this happens due to the 'reads' edge from the closure linking // however, this read edge should not apply when the call happens within the same scope @@ -234,12 +233,12 @@ b <- f()`) } b <- f() `, ['9@b'], `f <- function() { - a <- function() { x } - x <- 3 - b <- a() - x <- 2 - b - } + a <- function() { x } + x <- 3 + b <- a() + x <- 2 + b +} b <- f()`) }) describe('Recursive functions', () => { @@ -262,12 +261,11 @@ a <- function() { x = x + 5; cat(x) } x <- 3 a() cat(x)` + //this test fails, the output only produces { } no reconstruction of the code assertSliced('Local redefinition has no effect', shell, localCode, ['5@x'], `x <- 3 cat(x)`) - assertSliced('Local redefinition must be kept as part of call', shell, localCode, ['4@a'], `a <- function() { - x = x + 5 - cat(x) - } + //this test fails, semicollons must be preserved here + assertSliced('Local redefinition must be kept as part of call', shell, localCode, ['4@a'], `a <- function() { x = x + 5; cat(x) } x <- 3 a()`) const globalCode = ` @@ -275,7 +273,7 @@ a <- function() { x <<- x + 5; cat(x) } x <- 3 a() cat(x)` - assertSliced('But the global redefinition remains', shell, globalCode, ['5@x'], `a <- function() { x <<- x + 5 } + assertSliced('But the global redefinition remains', shell, globalCode, ['5@x'], `a <- function() { x <<- x + 5 } x <- 3 a() cat(x)`) @@ -284,10 +282,11 @@ a <- function() { x <<- 5; cat(x) } x <- 3 a() cat(x)` - assertSliced('The local assignment is only needed if the global reads', shell, globalCodeWithoutLocal, ['5@x'], `a <- function() { x <<- 5 } + assertSliced('The local assignment is only needed if the global reads', shell, globalCodeWithoutLocal, ['5@x'], `a <- function() { x <<- 5 } a() cat(x)`) + //this test fails, somehow the function definition is wrapped in another {} assertSliced('Must work with nested globals', shell, `a <- function() { function(b) { x <<- b } } y <- 5 x <- 2 @@ -297,6 +296,7 @@ y <- 5 a()(y) cat(x)`) + //this test fails, the function gets reconstructed but only the if(FALSE) assertSliced('Must work with nested globals and known assignments not-happening', shell, `a <- function() { function(b) { if(FALSE) { x <<- b } } } y <- 5 x <- 2 @@ -304,6 +304,7 @@ a()(y) cat(x)`, ['5@x'], `x <- 2 cat(x)`) + //this test fails, invalid criterion for slicing assertSliced('Must work with nested globals and maybe assignments', shell, `a <- function() { function(b) { if(runif() > .5) { @@ -336,16 +337,21 @@ a <- function() { x <- 3; 5 } a() \`a\`() ` - assertSliced('Must link with string/string', shell, code, ['3@\'a\''], `'a' <- function() { 4 } + //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } + assertSliced('Must link with string/string', shell, code, ['3@\'a\''], `'a' <- function() { 4 } 'a'()`) - assertSliced('Must link with string/no-string', shell, code, ['4@a'], `'a' <- function() { 4 } + //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } + assertSliced('Must link with string/no-string', shell, code, ['4@a'], `'a' <- function() { 4 } a()`) - assertSliced('Must link with no-string/string', shell, code, ['6@\'a\''], `a <- function() { 5 } + //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } + assertSliced('Must link with no-string/string', shell, code, ['6@\'a\''], `a <- function() { 5 } 'a'()`) + //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } // the common case: - assertSliced('Must link with no-string/no-string', shell, code, ['7@a'], `a <- function() { 5 } + assertSliced('Must link with no-string/no-string', shell, code, ['7@a'], `a <- function() { 5 } a()`) - assertSliced('Try with special backticks', shell, code, ['8@`a`'], `a <- function() { 5 } + //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } + assertSliced('Try with special backticks', shell, code, ['8@`a`'], `a <- function() { 5 } \`a\`()`) }) describe('Using own infix operators', () => { @@ -359,8 +365,10 @@ a()`) cat(3 %a% 4) cat(4 %b% 5) ` + //this test fails, additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link with backticks', shell, code, ['8:7'], `\`%a%\` <- function(x, y) { x + y } cat(3 %a% 4)`) + //this test fails, additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link with backticks', shell, code, ['9:7'], `'%b%' <- function(x, y) { x * y } cat(4 %b% 5)`) assertSliced('Must work with assigned custom pipes too', shell, 'a <- b %>% c %>% d', ['1@a'], 'a <- b %>% c %>% d') @@ -371,6 +379,7 @@ cat(4 %b% 5)`) "%a%" <- pkg::"%a%" cat(4 %a% 5) ` + //this test fails, additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link alias but not namespace origin', shell, code, ['4:1'], `"%a%" <- pkg::"%a%" cat(4 %a% 5)`) }) diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index 4487e1f023..86e97ba5a7 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -7,8 +7,10 @@ describe('Simple', withShell(shell => { } }) describe('Constant conditionals', () => { - assertSliced('if(TRUE)', shell, 'if(TRUE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(TRUE) {\n x <- 3\n}\nx') - assertSliced('if(FALSE)', shell, 'if(FALSE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(FALSE) { } else {\n x <- 4\n}\nx') + //this test fails, the if conditional gets reconstructed wrong + assertSliced('if(TRUE)', shell, 'if(TRUE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(TRUE) { x <- 3 }\nx') + //this test fails, the if conditional gets reconstructed wrong + assertSliced('if(FALSE)', shell, 'if(FALSE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(FALSE) { } else { x <- 4}\nx') }) describe('Independent Control-Flow', () => { assertSliced('For-Loop', shell, ` @@ -17,15 +19,16 @@ for(i in 1:10) { x <- x * 2 } cat(x) - `, ['6@x'], 'x <- 1\nfor(i in 1:10) x <- x * 2\ncat(x)') + `, ['6@x'], 'x <- 1\nfor(i in 1:10) {\n x <- x * 2\n}\ncat(x)') assertSliced('While-Loop', shell, ` x <- 1 while(i > 3) { x <- x * 2 } cat(x) - `, ['6@x'], 'x <- 1\nwhile(i > 3) x <- x * 2\ncat(x)') + `, ['6@x'], 'x <- 1\nwhile(i > 3) {\n x <- x * 2\n}\ncat(x)') + //this test fails, the if conditional gets reconstructed wrong // urgh that is fragile assertSliced('If-Then', shell, ` x <- 1 @@ -39,6 +42,7 @@ if(i > 3) { } cat(x)`) + //this test fails, the if conditional gets reconstructed wrong assertSliced('Independent If-Then with extra requirements', shell, ` x <- 1 i <- 3 @@ -54,8 +58,11 @@ if(i > 3) { cat(x)`) }) describe('Access', () => { + //this test fails, we get a magical space in the reconstruct of a[3] assertSliced('Constant', shell, 'a <- 4\na <- list(1,2)\na[3]', ['3@a'], 'a <- list(1,2)\na[3]') + //this test fails, we get a magical space in the reconstruct of a[i] assertSliced('Variable', shell, 'i <- 4\na <- list(1,2)\na[i]', ['3@a'], 'i <- 4\na <- list(1,2)\na[i]') + //this test fails, we get a magical space in the reconstruct of a[1:i,] assertSliced('Subset Sequence', shell, 'i <- 4\na <- list(1,2)\na[1:i,]', ['3@a'], 'i <- 4\na <- list(1,2)\na[1:i,]') describe('definitions', () => { describe('[[', () => { @@ -68,6 +75,7 @@ cat(a) a <- list(3,4) cat(a) ` + //this test fails, we get a magical space in the reconstruction of the access assertSliced('Repeated named access and definition', shell, code, ['6@a'], `a <- list(1,2) a[[1]] = 2 a[[2]] = 3 @@ -85,6 +93,7 @@ cat(a) a <- list(a=3,b=4) cat(a) ` + //this test fails, we get a magical space in the reconstruction of the access assertSliced('Repeated named access and definition', shell, codeB, ['6@a'], `a <- list(a=1,b=2) a$a = 2 a$b = 3 @@ -120,43 +129,56 @@ cat("Product:", product, "\\n") `sum <- 0 w <- 7 N <- 10 -for(i in 1:(N-1)) sum <- sum + i + w` +for(i in 1:(N-1)) { + sum <- sum + i + w +}` ) assertSliced('Sum rhs in for', shell, code, ['8:10'], `sum <- 0 w <- 7 N <- 10 -for(i in 1:(N-1)) sum <- sum + i + w` +for(i in 1:(N-1)) { + sum <- sum + i + w +}` ) assertSliced('Product lhs in for', shell, code, ['9:3'], `product <- 1 N <- 10 -for(i in 1:(N-1)) product <- product * i` +for(i in 1:(N-1)) { + product <- product * i +}` ) assertSliced('Product rhs in for', shell, code, ['9:14'], `product <- 1 N <- 10 -for(i in 1:(N-1)) product <- product * i` +for(i in 1:(N-1)) { + product <- product * i +}` ) assertSliced('Sum in call', shell, code, ['12:13'], `sum <- 0 w <- 7 N <- 10 -for(i in 1:(N-1)) sum <- sum + i + w +for(i in 1:(N-1)) { + sum <- sum + i + w +} cat("Sum:", sum, "\\n")` ) assertSliced('Product in call', shell, code, ['13:17'], `product <- 1 N <- 10 -for(i in 1:(N-1)) product <- product * i +for(i in 1:(N-1)) { + product <- product * i + } cat("Product:", product, "\\n")` ) + //this test fails, the for loop gets reconstructed as well assertSliced('Top by name', shell, code, ['2@sum'], 'sum <- 0' ) From 97ce67d914cf51c54c16f3987125ab92bf82ec49 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 8 Mar 2024 14:31:14 +0100 Subject: [PATCH 54/88] further reconstruct --- src/reconstruct/reconstruct.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 5894bf2531..72708a8715 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -68,11 +68,11 @@ function reconstructExpressionList(exprList: RExpressionList, const subExpressions = expressions.filter(e => e.length > 0) - const additionalTokens = reconstructAdditionalTokens(exprList) if(subExpressions.length === 0) { - return merge(additionalTokens) + return [] } else { + const additionalTokens = reconstructAdditionalTokens(exprList) return merge([ ...subExpressions, ...additionalTokens @@ -129,6 +129,11 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc vectorLocation.column -= 1 //somehow the vector is consistently one space to late const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) + + if(variable.length === 0 && vector.length === 0 && body.length === 0) { + return [] + } + const out = merge([ [{ linePart: [{ part: 'for', loc: start ?? loop.location.start }], indent: 0 }], [{ linePart: [{ part: getLexeme(loop.variable), loc: loop.variable.location.start }], indent: 0 }], @@ -239,7 +244,8 @@ function reconstructParameters(parameters: RParameter[]): str } function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { - const start = node.location.start + const start = node.info.fullRange?.start ?? node.location.start + if(isSelected(configuration, node)) { return plain(getLexeme(node), start) } From 73e3d46c03b4959c7a973a988c62c41a9b517924 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:35:54 +0100 Subject: [PATCH 55/88] test(reconstruct): started work on if testing --- src/reconstruct/reconstruct.ts | 5 +-- .../slicing/reconstruct/simple-tests.ts | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 5894bf2531..2fbfe76695 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -26,7 +26,6 @@ import { guard, isNotNull } from '../util/assert' import type { MergeableRecord } from '../util/objects' import type { Selection, - PrettyPrintLine, Code, AutoSelectPredicate } from './helper' import { @@ -37,7 +36,6 @@ import { removeExpressionListWrap, getIndentString, merge, - plainSplit, prettyPrintCodeToString } from './helper' import type { SourcePosition, SourceRange } from '../util/range' @@ -148,6 +146,8 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v return merge([out]) } +//add heuristic to select needed semicollons +//maybe if expr 1,5 => select next semicollon function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { return node.info.additionalTokens?.filter(t => t.lexeme && t.location) .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] @@ -168,6 +168,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } } +//make use of additional tokens function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = ifThenElse.location.start const endPos = ifThenElse.location.end diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 382785775f..4e468109c0 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -97,20 +97,29 @@ describe('Simple', withShell(shell => { assertReconstructed(`${JSON.stringify(id)}: ${code}`, shell, code, id, expected) } }) + }) - describe('function definition', () => { - const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ - //this test does not reconstruct the function - { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, - { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, - //this test does not reconstruct the function - { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, - { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } - ] - for(const test of testCases) { - assertReconstructed(test.name, shell, test.case, test.argument, test.expected) - } - }) + describe('function definition', () => { + const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ + //this test does not reconstruct the function + { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, + { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, + //this test does not reconstruct the function + { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, + { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } + ] + for(const test of testCases) { + assertReconstructed(test.name, shell, test.case, test.argument, test.expected) + } + }) + + describe.only('Branches', () => { + const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4}', argument: ['2@x'], expected: 'if(TRUE) { x <- 3 }' } + ] + for(const test of testCases) { + assertReconstructed(test.name, shell, test.case, test.argument, test.expected) + } }) describe('Failures in practice', () => { assertReconstructed('Reconstruct expression list in call', shell, ` From 94d0e4b6e705969b8fedd854c56e325ff8bee02d Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 8 Mar 2024 14:42:39 +0100 Subject: [PATCH 56/88] feat(reconstruct): lint fixes --- .../ast/parser/xml/internal/structure/elements.ts | 4 ++-- src/reconstruct/main.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts index f2570834c2..e34bd494f8 100644 --- a/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts +++ b/src/r-bridge/lang-4.x/ast/parser/xml/internal/structure/elements.ts @@ -160,7 +160,7 @@ export function normalizeBasedOnType( log.trace(`found ${splitOnSemicolon.length} expressions by semicolon-split, parsing them separately`) const flattened = [] let i = 0 - let semis = [] + const semis = [] for(const sub of splitOnSemicolon) { const result = normalizeBasedOnType(data, sub) const semi = semiColons[i++] @@ -174,7 +174,7 @@ export function normalizeBasedOnType( if(semi) { if(result.length > 0) { const last = result[result.length - 1] as RNode - last.info.additionalTokens ??= []; + last.info.additionalTokens ??= [] last.info.additionalTokens?.push(semi) } else { semis.push(semi) diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index d13d38b1b9..ad15d21864 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,9 +1,9 @@ import type { ReconstructionResult } from './reconstruct' import { reconstructLogger, reconstructAstFolds } from './reconstruct' -import { prettyPrintCodeToString, removeOuterExpressionListIfApplicable } from './helper' -import { autoSelectLibrary } from './helper' -import type { AutoSelectPredicate } from './helper' -import type { Selection } from './helper' +import { prettyPrintCodeToString, removeOuterExpressionListIfApplicable , autoSelectLibrary } from './helper' + +import type { AutoSelectPredicate , Selection } from './helper' + import type { NormalizedAst, RNode, ParentInformation } from '../r-bridge' import { foldAstStateful } from '../r-bridge' From 4c4abcfb292f47425ba72a973ca0a68c63316b63 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 15 Mar 2024 13:28:16 +0100 Subject: [PATCH 57/88] feat(reconstruct): changed if-reconstruct; WIP if reconstruct now uses additional Tokens --- src/reconstruct/reconstruct.ts | 47 +++++++++---------- .../slicing/reconstruct/simple-tests.ts | 2 +- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 2fbfe76695..25842a9f9d 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -176,35 +176,30 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse), startPos) } - otherwise ??= [] + otherwise ??= [] //what does ??= do and is it important + if(condition.length === 0 && when.length === 0 && otherwise.length === 0) { return [] } - if(otherwise.length === 0 && when.length === 0) { - return [ - { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) { }`, loc: startPos }], indent: 0 } - ] - } else if(otherwise.length === 0) { - return [ - { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos }], indent: 0 }, - ...indentBy(removeExpressionListWrap(when), 1), - { linePart: [{ part: '}', loc: endPos }], indent: 0 } - ] - } else if(when.length === 0) { - return [ - { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) { } else {`, loc: startPos }], indent: 0 }, - ...indentBy(removeExpressionListWrap(otherwise), 1), - { linePart: [{ part: '}', loc: startPos }], indent: 0 } - ] - } else { - return [ - { linePart: [{ part: `if(${getLexeme(ifThenElse.condition)}) {`, loc: startPos }], indent: 0 }, - ...indentBy(removeExpressionListWrap(when), 1), - { linePart: [{ part: '} else {', loc: conditionPos }], indent: 0 }, - ...indentBy(removeExpressionListWrap(otherwise), 1), - { linePart: [{ part: '}', loc: endPos }], indent: 0 } - ] - } + const additionalTokens = reconstructAdditionalTokens(ifThenElse) + + let out = merge([ + ...additionalTokens, + [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }] + ]) + + if(!(when.length === 0)) { + out = merge([ + out, + removeExpressionListWrap(when) + ]) + if(!(otherwise.length === 0)) { + out = merge([ + out, + [{ linePart: [{ part: 'else', loc: conditionPos }], indent: 0 }], //may have to change the location + removeExpressionListWrap(otherwise) + ]) + return out } function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 4e468109c0..51941bd995 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -115,7 +115,7 @@ describe('Simple', withShell(shell => { describe.only('Branches', () => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4}', argument: ['2@x'], expected: 'if(TRUE) { x <- 3 }' } + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4}', argument: ['3'], expected: 'if(TRUE) { x <- 3 }' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From fe57bd21a044e5ffaf4c85ca6c6d3babbf819336 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 15 Mar 2024 15:41:07 +0100 Subject: [PATCH 58/88] fix: imp for result reconstruction --- src/reconstruct/reconstruct.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 0362f8ee8e..173f1cf528 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -192,18 +192,20 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi ...additionalTokens, [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }] ]) - + if(!(when.length === 0)) { out = merge([ out, removeExpressionListWrap(when) ]) + } if(!(otherwise.length === 0)) { - out = merge([ - out, - [{ linePart: [{ part: 'else', loc: conditionPos }], indent: 0 }], //may have to change the location - removeExpressionListWrap(otherwise) - ]) + out = merge([ + out, + [{ linePart: [{ part: 'else', loc: conditionPos }], indent: 0 }], //may have to change the location + removeExpressionListWrap(otherwise) + ]) + } return out } From 600abc91ca519572b4763ac4c65ce523ba5bc921 Mon Sep 17 00:00:00 2001 From: Florian Sihler Date: Fri, 15 Mar 2024 15:48:03 +0100 Subject: [PATCH 59/88] doc: remove question comment --- src/reconstruct/reconstruct.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 173f1cf528..f7b3eaf38b 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -181,7 +181,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse), startPos) } - otherwise ??= [] //what does ??= do and is it important + otherwise ??= [] if(condition.length === 0 && when.length === 0 && otherwise.length === 0) { return [] From 870a9f2611c28710d88d2e178e673527a4e97b2d Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:51:52 +0100 Subject: [PATCH 60/88] feat(reconstruct): if-body works, else-body doesnt --- src/reconstruct/reconstruct.ts | 23 +++++++++++++------ .../slicing/reconstruct/simple-tests.ts | 3 ++- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 25842a9f9d..811ee5555f 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -33,12 +33,12 @@ import { plain, indentBy, isSelected, - removeExpressionListWrap, getIndentString, merge, prettyPrintCodeToString } from './helper' import type { SourcePosition, SourceRange } from '../util/range' +import { jsonReplacer } from '../util/json' export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -171,12 +171,12 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, //make use of additional tokens function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = ifThenElse.location.start - const endPos = ifThenElse.location.end + //const endPos = ifThenElse.location.end const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : { line: 0, column: 0 } if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse), startPos) } - otherwise ??= [] //what does ??= do and is it important + otherwise ??= [] if(condition.length === 0 && when.length === 0 && otherwise.length === 0) { return [] @@ -187,18 +187,27 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi ...additionalTokens, [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }] ]) + + console.log(JSON.stringify(when,jsonReplacer)) + console.log(JSON.stringify(otherwise,jsonReplacer)) + + console.log(JSON.stringify(out,jsonReplacer)) - if(!(when.length === 0)) { + if(!(when[0].linePart.length === 2)) { + console.log('we have an if-body') out = merge([ out, - removeExpressionListWrap(when) + when ]) - if(!(otherwise.length === 0)) { + } + if(!(otherwise[0].linePart.length === 2)) { + console.log('we have an else-body') out = merge([ out, [{ linePart: [{ part: 'else', loc: conditionPos }], indent: 0 }], //may have to change the location - removeExpressionListWrap(otherwise) + otherwise ]) + } return out } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 51941bd995..dd56b9ce22 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -115,7 +115,8 @@ describe('Simple', withShell(shell => { describe.only('Branches', () => { const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4}', argument: ['3'], expected: 'if(TRUE) { x <- 3 }' } + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4}', argument: ['1'], expected: 'if(TRUE) { x <- 3 }' }, + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4}', argument: ['1'], expected: 'if(FALSE) { } else { x <- 4 }' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 576d17ec15a6ec9932374d1effdfaeb5ed4b8958 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 19 Mar 2024 22:22:13 +0100 Subject: [PATCH 61/88] feat(reconstruct): if reconstruct good but broken --- src/reconstruct/reconstruct.ts | 23 ++++++++++++------- .../slicing/reconstruct/simple-tests.ts | 8 +++---- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 811ee5555f..43f3bef7c7 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -172,7 +172,9 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = ifThenElse.location.start //const endPos = ifThenElse.location.end - const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : { line: 0, column: 0 } + //const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : { line: 0, column: 0 } + + if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse), startPos) } @@ -182,17 +184,17 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi return [] } const additionalTokens = reconstructAdditionalTokens(ifThenElse) + console.log('additional Tokens: ', JSON.stringify(additionalTokens,jsonReplacer)) let out = merge([ ...additionalTokens, - [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }] + [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }], + when ]) - console.log(JSON.stringify(when,jsonReplacer)) - console.log(JSON.stringify(otherwise,jsonReplacer)) - - console.log(JSON.stringify(out,jsonReplacer)) - + console.log('when: ', JSON.stringify(when,jsonReplacer)) + console.log('otherwise: ', JSON.stringify(otherwise,jsonReplacer)) + /* if(!(when[0].linePart.length === 2)) { console.log('we have an if-body') out = merge([ @@ -200,14 +202,19 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi when ]) } + */ if(!(otherwise[0].linePart.length === 2)) { console.log('we have an else-body') + const hBody = out[out.length - 1].linePart + const elsePos = hBody[hBody.length - 1].loc out = merge([ out, - [{ linePart: [{ part: 'else', loc: conditionPos }], indent: 0 }], //may have to change the location + [{ linePart: [{ part: 'else', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }], //may have to change the location otherwise ]) } + + console.log('out: ', JSON.stringify(out,jsonReplacer)) return out } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index dd56b9ce22..34439cbe1e 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -100,7 +100,7 @@ describe('Simple', withShell(shell => { }) describe('function definition', () => { - const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ + const testCases: {name: string, case: string, argument: string[], expected: string}[] = [ //this test does not reconstruct the function { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, @@ -114,9 +114,9 @@ describe('Simple', withShell(shell => { }) describe.only('Branches', () => { - const testCases: {name: string, case: string, argument: [string], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4}', argument: ['1'], expected: 'if(TRUE) { x <- 3 }' }, - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4}', argument: ['1'], expected: 'if(FALSE) { } else { x <- 4 }' } + const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: '10', expected: 'if(TRUE) { x <- 3 }\nx' }, + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: '10', expected: 'if(FALSE) { } else { x <- 4 }\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 8a403e0537af6731bf04d7a6d8a01093935b1f2f Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 22 Mar 2024 14:16:23 +0100 Subject: [PATCH 62/88] working on tests --- src/reconstruct/reconstruct.ts | 1 - test/functionality/slicing/reconstruct/simple-tests.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 88eec2f5f2..0ba0b37bad 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -179,7 +179,6 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi //const endPos = ifThenElse.location.end //const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : { line: 0, column: 0 } - if(isSelected(configuration, ifThenElse)) { return plain(getLexeme(ifThenElse), startPos) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 34439cbe1e..880d973e99 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -115,7 +115,7 @@ describe('Simple', withShell(shell => { describe.only('Branches', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: '10', expected: 'if(TRUE) { x <- 3 }\nx' }, + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '8'], expected: 'if(TRUE) { x <- 3 }\nx' }, { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: '10', expected: 'if(FALSE) { } else { x <- 4 }\nx' } ] for(const test of testCases) { From 812b291cff282ad2cdbeb275d6ce33d08fabd152 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:59:01 +0200 Subject: [PATCH 63/88] test-fix(reconstruct): tried finding correct index --- src/reconstruct/reconstruct.ts | 17 ++++------------- .../slicing/reconstruct/simple-tests.ts | 6 +++--- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 0ba0b37bad..0356a8ae70 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -38,7 +38,7 @@ import { prettyPrintCodeToString } from './helper' import type { SourcePosition, SourceRange } from '../util/range' -import { jsonReplacer } from '../util/json' +//import { jsonReplacer } from '../util/json' export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -188,7 +188,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi return [] } const additionalTokens = reconstructAdditionalTokens(ifThenElse) - console.log('additional Tokens: ', JSON.stringify(additionalTokens,jsonReplacer)) + //console.log('additional Tokens: ', JSON.stringify(additionalTokens,jsonReplacer)) let out = merge([ ...additionalTokens, @@ -196,17 +196,8 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi when ]) - /* - if(!(when[0].linePart.length === 2)) { - console.log('we have an if-body') - out = merge([ - out, - when - ]) - } - */ if(!(otherwise[0].linePart.length === 2)) { - console.log('we have an else-body') + //console.log('we have an else-body') const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc out = merge([ @@ -216,7 +207,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi ]) } - console.log('out: ', JSON.stringify(out,jsonReplacer)) + //console.log('out: ', JSON.stringify(out,jsonReplacer)) return out } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 880d973e99..5912c6df5a 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -113,10 +113,10 @@ describe('Simple', withShell(shell => { } }) - describe.only('Branches', () => { + describe('Branches', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '8'], expected: 'if(TRUE) { x <- 3 }\nx' }, - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: '10', expected: 'if(FALSE) { } else { x <- 4 }\nx' } + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '3', '0'], expected: 'if(TRUE) { x <- 3 }\nx' }, + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) { } else { x <- 4 }\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 0231dd63ad53f2aab3f3171869b5bfacc63c3667 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 5 Apr 2024 14:14:49 +0200 Subject: [PATCH 64/88] if fixes --- src/reconstruct/reconstruct.ts | 8 +++++--- test/functionality/slicing/reconstruct/simple-tests.ts | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 0356a8ae70..68d354eb23 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -196,18 +196,20 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi when ]) - if(!(otherwise[0].linePart.length === 2)) { + if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { //console.log('we have an else-body') const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc + const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' { } ', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }] : [] + const elseOffset = when.length === 0 ? 4 : 0 out = merge([ out, - [{ linePart: [{ part: 'else', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }], //may have to change the location + fakeWhenBlock, + [{ linePart: [{ part: 'else', loc: { line: elsePos.line, column: elsePos.column + 2 +elseOffset } }], indent: 0 }], //may have to change the location otherwise ]) } - //console.log('out: ', JSON.stringify(out,jsonReplacer)) return out } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 5912c6df5a..a9d5607474 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -116,7 +116,7 @@ describe('Simple', withShell(shell => { describe('Branches', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '3', '0'], expected: 'if(TRUE) { x <- 3 }\nx' }, - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) { } else { x <- 4 }\nx' } + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) { } else { x <- 4 }\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From bbfa26d632601ffdf9d384ac6e6f24b9ff397c1c Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:13:55 +0200 Subject: [PATCH 65/88] tests-fix(reconstruction): added layout preserving --- .../slicing/reconstruct/simple-tests.ts | 7 +- .../static-program-slices/calls-tests.ts | 87 +++++-------------- .../static-program-slices/simple-tests.ts | 59 ++++--------- 3 files changed, 43 insertions(+), 110 deletions(-) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index a9d5607474..5854090aff 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -55,14 +55,14 @@ describe('Simple', withShell(shell => { ['while(TRUE) { x }', '1', 'while(TRUE) { x }'], ['while(TRUE) { x <- 5 }', '1', 'while(TRUE) { x <- 5 }'], ['while(TRUE) { x <- 5; y <- 9 }', '1', 'while(TRUE) { x <- 5 }'], - ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) { }'], + ['while(TRUE) { x <- 5; y <- 9 }', '0', 'while(TRUE) {}'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) { x <- 5 }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) { x <- 5 }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { y <- 9 }'], //semicollon has to stay, otherwise the statement loses information ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) { x <- 5; y <- 9 }',], ['while(TRUE) {\n x <- 5\n y <- 9\n}', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], - ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) { }'], + ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], ['while(x + 2 > 3) { x <- 0 }', ['5'], 'while(x + 2 > 3) { x <- 0 }'], ['while(x + 2 > 3) { x <- 0 }', ['0', '5'], 'while(x + 2 > 3) { x <- 0 }'] ] @@ -80,7 +80,8 @@ describe('Simple', withShell(shell => { } ` const pool: [string, string | string[], string][] = [ - [largeFor, '0', 'for (i in 1:20) {\n}'], + //here we may want the \n to carry over in the reconstruction + [largeFor, '0', 'for (i in 1:20) {}'], [largeFor, '4', 'for (i in 1:20) {\n y <- 9\n}'], [largeFor, ['0', '4'], 'for (i in 1:20) {\n y <- 9\n}'], [largeFor, ['0', '4', '7'], `for (i in 1:20) { diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index cb61a38c13..7b94582340 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -11,12 +11,10 @@ a(i)` const constFunction = `i <- 4 a <- function(x) { x <- 2; 1 } a(i)` - //currently fails, whole body gets reconstructed assertSliced('Function call with constant function', shell, constFunction, ['3:1'], `i <- 4 a <- function(x) { 1 } a(i)`) - //currently fails, whole body gets reconstructed - assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x) { }') + assertSliced('Slice function definition', shell, constFunction, ['2@a'], 'a <- function(x)') assertSliced('Slice within function', shell, constFunction, ['2:20'], 'x <- 2') assertSliced('Multiple unknown calls', shell, ` foo(x, y) @@ -105,7 +103,7 @@ f <- function(some_variable="hello") { f() ` //this test fails, semicollons should not be removed - assertSliced('Late bindings of parameter in body', shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { b <- 1 a + 1 } + assertSliced('Late bindings of parameter in body', shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { b <- 1; a + 1 } f()`) const lateCodeB = `f <- function(a=b, b=3) { b <- 1; a; b <- 5; a + 1 } f() @@ -125,6 +123,7 @@ f()`) const code = `a <- function() { a <- 2; return(function() { 1 }) } b <- a() b()` + //we may want to fix the second line to the start of the line, as it is a single token outside of the function body assertSliced('Must include outer function', shell, code, ['2@a'], `a <- function() { return(function() { 1 }) } a()`) assertSliced('Must include linked function', shell, code, ['3@b'], `a <- function() { return(function() { 1 }) } @@ -140,7 +139,7 @@ u <- a() u()` //this test fails, we still need to retain some semicollons - assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { x <- function() { }; return(x) } + assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { x <- function() ; return(x) } a()`) //this test fails, semicollons should not get removed here assertSliced('Must include function shell on call', shell, code, ['6@u'], `a <- function() { x <- function() { z + y }; y <- 12; return(x) } @@ -166,14 +165,13 @@ cat(x)`) i <- 4 b <- function(f) { i <- 5; f() } b(a)` - //this test fails, somehow we get no output assertSliced('Only i, not bound in context', shell, code, ['1@i'], 'i') - //this test fails, the output produces an additional { } - assertSliced('Slice of b is independent', shell, code, ['3@b'], 'b <- function(f) { }') + assertSliced('Slice of b is independent', shell, code, ['3@b'], 'b <- function(f)') //this test fails, semicollons need to be preserved here assertSliced('Slice of b-call uses function', shell, code, ['4@b'], `a <- function() { i } b <- function(f) { i <- 5; f() } b(a)`) + //line offsets for a's function body may be wrong assertSliced('Directly call returned function', shell, `m <- 12 a <- function(x) { b <- function() { function() { x } } @@ -181,17 +179,14 @@ a <- function(x) { } a(m)()`, ['$25' /* we can't directly slice the second call as the "a" name would take the inner call */], `m <- 12 a <- function(x) { - b <- function() { function() { x } } - return(b()) - } -a(m)()`) - assertSliced('Higher order anonymous function', shell, `a <- function(b) { - b -} -x <- a(function() 2 + 3)() + a(function() 7)()`, ['4@x'], `a <- function(b) { - b + b <- function() { function() { x } } + return(b()) } -x <- a(function() 2 + 3)() + a(function() 7)()`) +a(m)()`) + //may be fixed + assertSliced('Higher order anonymous function', shell, + 'a <- function(b) {\n b\n}\nx <- a(function() 2 + 3)() + a(function() 7)()', ['4@x'], + 'a <- function(b) {\n b\n}\nx <- a(function() 2 + 3)() + a(function() 7)()') }) describe('Side-Effects', () => { assertSliced('Important Side-Effect', shell, `x <- 2 @@ -202,44 +197,20 @@ cat(x) f() cat(x)`) - //this test fails, output only produces { } and not the cat(x) assertSliced('Unimportant Side-Effect', shell, `f <- function() { y <<- 3 } f() cat(x) `, ['3@x'], 'cat(x)') - assertSliced('Nested Side-Effect For Last', shell, `f <- function() { - a <- function() { x } - x <- 3 - a() - x <- 2 - a() -} -b <- f() - `, ['8@b'], `f <- function() { - a <- function() { x } - x <- 2 - a() -} -b <- f()`) + //may be fixed + assertSliced('Nested Side-Effect For Last', shell, + 'f <- function() {\n a <- function() { x }\n x <- 3\n a()\n x <- 2\n a()\n}\nb <- f()', ['8@b'], + 'f <- function() {\n a <- function() { x }\n x <- 3\n a()\n x <- 2\n a()\n}\nb <- f()') // that it contains x <- 2 is an error in the current implementation as this happens due to the 'reads' edge from the closure linking // however, this read edge should not apply when the call happens within the same scope - assertSliced('Nested Side-Effect For First', shell, `f <- function() { - a <- function() { x } - x <- 3 - b <- a() - x <- 2 - a() - b -} -b <- f() - `, ['9@b'], `f <- function() { - a <- function() { x } - x <- 3 - b <- a() - x <- 2 - b -} -b <- f()`) + //line offsets may be fixed + assertSliced('Nested Side-Effect For First', shell, + 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', ['9@b'], + 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n') }) describe('Recursive functions', () => { const code = `f <- function() { f() } @@ -261,7 +232,6 @@ a <- function() { x = x + 5; cat(x) } x <- 3 a() cat(x)` - //this test fails, the output only produces { } no reconstruction of the code assertSliced('Local redefinition has no effect', shell, localCode, ['5@x'], `x <- 3 cat(x)`) //this test fails, semicollons must be preserved here @@ -296,7 +266,6 @@ y <- 5 a()(y) cat(x)`) - //this test fails, the function gets reconstructed but only the if(FALSE) assertSliced('Must work with nested globals and known assignments not-happening', shell, `a <- function() { function(b) { if(FALSE) { x <<- b } } } y <- 5 x <- 2 @@ -304,7 +273,7 @@ a()(y) cat(x)`, ['5@x'], `x <- 2 cat(x)`) - //this test fails, invalid criterion for slicing + //this test fails, invalid criterion for slicing ['5@x'] assertSliced('Must work with nested globals and maybe assignments', shell, `a <- function() { function(b) { if(runif() > .5) { @@ -315,7 +284,7 @@ cat(x)`) y <- 5 x <- 2 a()(y) -cat(x)`, ['5@x'], `a <- function() { +cat(x)`, ['4@x'], `a <- function() { function(b) { if(runif() > .5) { x <<- b @@ -337,20 +306,15 @@ a <- function() { x <- 3; 5 } a() \`a\`() ` - //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link with string/string', shell, code, ['3@\'a\''], `'a' <- function() { 4 } 'a'()`) - //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link with string/no-string', shell, code, ['4@a'], `'a' <- function() { 4 } a()`) - //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } - assertSliced('Must link with no-string/string', shell, code, ['6@\'a\''], `a <- function() { 5 } + assertSliced('Must link with no-string/string', shell, code, ['6@\'a\''], `a <- function() { 5 } 'a'()`) - //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } // the common case: assertSliced('Must link with no-string/no-string', shell, code, ['7@a'], `a <- function() { 5 } a()`) - //this test fails, all additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Try with special backticks', shell, code, ['8@`a`'], `a <- function() { 5 } \`a\`()`) }) @@ -365,10 +329,8 @@ a()`) cat(3 %a% 4) cat(4 %b% 5) ` - //this test fails, additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link with backticks', shell, code, ['8:7'], `\`%a%\` <- function(x, y) { x + y } cat(3 %a% 4)`) - //this test fails, additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link with backticks', shell, code, ['9:7'], `'%b%' <- function(x, y) { x * y } cat(4 %b% 5)`) assertSliced('Must work with assigned custom pipes too', shell, 'a <- b %>% c %>% d', ['1@a'], 'a <- b %>% c %>% d') @@ -379,7 +341,6 @@ cat(4 %b% 5)`) "%a%" <- pkg::"%a%" cat(4 %a% 5) ` - //this test fails, additional tokens get reconstructed exept semicollons, we get additional { } assertSliced('Must link alias but not namespace origin', shell, code, ['4:1'], `"%a%" <- pkg::"%a%" cat(4 %a% 5)`) }) diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index 86e97ba5a7..8b4a0359ec 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -7,10 +7,9 @@ describe('Simple', withShell(shell => { } }) describe('Constant conditionals', () => { - //this test fails, the if conditional gets reconstructed wrong assertSliced('if(TRUE)', shell, 'if(TRUE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(TRUE) { x <- 3 }\nx') - //this test fails, the if conditional gets reconstructed wrong - assertSliced('if(FALSE)', shell, 'if(FALSE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(FALSE) { } else { x <- 4}\nx') + //if reconstruction needs more work to handle this + assertSliced('if(FALSE)', shell, 'if(FALSE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(FALSE) {} else { x <- 4}\nx') }) describe('Independent Control-Flow', () => { assertSliced('For-Loop', shell, ` @@ -28,7 +27,6 @@ while(i > 3) { cat(x) `, ['6@x'], 'x <- 1\nwhile(i > 3) {\n x <- x * 2\n}\ncat(x)') - //this test fails, the if conditional gets reconstructed wrong // urgh that is fragile assertSliced('If-Then', shell, ` x <- 1 @@ -42,7 +40,6 @@ if(i > 3) { } cat(x)`) - //this test fails, the if conditional gets reconstructed wrong assertSliced('Independent If-Then with extra requirements', shell, ` x <- 1 i <- 3 @@ -58,46 +55,21 @@ if(i > 3) { cat(x)`) }) describe('Access', () => { - //this test fails, we get a magical space in the reconstruct of a[3] assertSliced('Constant', shell, 'a <- 4\na <- list(1,2)\na[3]', ['3@a'], 'a <- list(1,2)\na[3]') - //this test fails, we get a magical space in the reconstruct of a[i] assertSliced('Variable', shell, 'i <- 4\na <- list(1,2)\na[i]', ['3@a'], 'i <- 4\na <- list(1,2)\na[i]') - //this test fails, we get a magical space in the reconstruct of a[1:i,] assertSliced('Subset Sequence', shell, 'i <- 4\na <- list(1,2)\na[1:i,]', ['3@a'], 'i <- 4\na <- list(1,2)\na[1:i,]') describe('definitions', () => { describe('[[', () => { - const code = ` -a <- list(1,2) -a[[1]] = 2 -a[[2]] = 3 -b[[4]] = 5 -cat(a) -a <- list(3,4) -cat(a) -` - //this test fails, we get a magical space in the reconstruction of the access - assertSliced('Repeated named access and definition', shell, code, ['6@a'], `a <- list(1,2) -a[[1]] = 2 -a[[2]] = 3 -cat(a)`) + const code = '\na <- list(1,2)\na[[1]] = 2\na[[2]] = 3\nb[[4]] = 5\ncat(a)\na <- list(3,4)\ncat(a)\n' + //may be fixed + assertSliced('Repeated named access and definition', shell, code, ['6@a'], 'a <- list(1,2)\na[[1]] = 2\na[[2]] = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, code, ['8@a'], `a <- list(3,4) cat(a)`) }) describe('$', () => { - const codeB = ` -a <- list(a=1,b=2) -a$a = 2 -a$b = 3 -b[[4]] = 5 -cat(a) -a <- list(a=3,b=4) -cat(a) -` - //this test fails, we get a magical space in the reconstruction of the access - assertSliced('Repeated named access and definition', shell, codeB, ['6@a'], `a <- list(a=1,b=2) -a$a = 2 -a$b = 3 -cat(a)`) + const codeB = '\na <- list(a=1,b=2)\na$a = 2\na$b = 3\nb[[4]] = 5\ncat(a)\na <- list(a=3,b=4)\ncat(a)\n' + //may be fixed + assertSliced('Repeated named access and definition', shell, codeB, ['6@a'], 'a <- list(a=1,b=2)\na$a = 2\na$b = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, codeB, ['8@a'], `a <- list(a=3,b=4) cat(a)`) }) @@ -129,7 +101,7 @@ cat("Product:", product, "\\n") `sum <- 0 w <- 7 N <- 10 -for(i in 1:(N-1)) { +for (i in 1:(N-1)) { sum <- sum + i + w }` ) @@ -138,7 +110,7 @@ for(i in 1:(N-1)) { `sum <- 0 w <- 7 N <- 10 -for(i in 1:(N-1)) { +for (i in 1:(N-1)) { sum <- sum + i + w }` ) @@ -146,7 +118,7 @@ for(i in 1:(N-1)) { assertSliced('Product lhs in for', shell, code, ['9:3'], `product <- 1 N <- 10 -for(i in 1:(N-1)) { +for (i in 1:(N-1)) { product <- product * i }` ) @@ -154,7 +126,7 @@ for(i in 1:(N-1)) { assertSliced('Product rhs in for', shell, code, ['9:14'], `product <- 1 N <- 10 -for(i in 1:(N-1)) { +for (i in 1:(N-1)) { product <- product * i }` ) @@ -163,7 +135,7 @@ for(i in 1:(N-1)) { `sum <- 0 w <- 7 N <- 10 -for(i in 1:(N-1)) { +for (i in 1:(N-1)) { sum <- sum + i + w } cat("Sum:", sum, "\\n")` @@ -172,13 +144,12 @@ cat("Sum:", sum, "\\n")` assertSliced('Product in call', shell, code, ['13:17'], `product <- 1 N <- 10 -for(i in 1:(N-1)) { +for (i in 1:(N-1)) { product <- product * i - } +} cat("Product:", product, "\\n")` ) - //this test fails, the for loop gets reconstructed as well assertSliced('Top by name', shell, code, ['2@sum'], 'sum <- 0' ) From 3c0e2c2705a56b7cd84a2fc79da3d49ea704cd88 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 25 Apr 2024 22:39:33 +0200 Subject: [PATCH 66/88] tests-fix(reconstruct): more test fixes --- .../static-program-slices/calls-tests.ts | 25 ++++++++----------- .../static-program-slices/simple-tests.ts | 4 +-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 7b94582340..4286ee1a2c 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -87,16 +87,10 @@ a(x = 3)` assertSliced('Must work for same named arguments too', shell, 'a <- 3\nb <- foo(a=a)', ['2@b'], 'a <- 3\nb <- foo(a=a)') - //this test fails, result gets shifted to the beginning of the line - assertSliced('Must work for same named arguments nested', shell, ` -f <- function(some_variable="hello") { - result <- some::other(some_variable=some_variable) - result -} - `, ['4@result'], `function(some_variable="hello") { - result <- some::other(some_variable=some_variable) - result -}`) + //may be fixed + assertSliced('Must work for same named arguments nested', shell, + '\nf <- function(some_variable="hello") {\n result <- some::other(some_variable=some_variable)\n result\n}\n ', ['4@result'], + 'f <- function(some_variable="hello") {\n result <- some::other(some_variable=some_variable)\n result\n}') const lateCode = `f <- function(a=b, m=3) { b <- 1; a; b <- 5; a + 1 } @@ -183,7 +177,8 @@ a <- function(x) { return(b()) } a(m)()`) - //may be fixed + //offsets seem to be wrong if the function is in an assignment + //we may have to look at offsets again assertSliced('Higher order anonymous function', shell, 'a <- function(b) {\n b\n}\nx <- a(function() 2 + 3)() + a(function() 7)()', ['4@x'], 'a <- function(b) {\n b\n}\nx <- a(function() 2 + 3)() + a(function() 7)()') @@ -201,13 +196,15 @@ cat(x)`) f() cat(x) `, ['3@x'], 'cat(x)') - //may be fixed + //offsets seem to be wrong if the function is in an assignment + //we may have to look at offsets again assertSliced('Nested Side-Effect For Last', shell, 'f <- function() {\n a <- function() { x }\n x <- 3\n a()\n x <- 2\n a()\n}\nb <- f()', ['8@b'], 'f <- function() {\n a <- function() { x }\n x <- 3\n a()\n x <- 2\n a()\n}\nb <- f()') // that it contains x <- 2 is an error in the current implementation as this happens due to the 'reads' edge from the closure linking // however, this read edge should not apply when the call happens within the same scope - //line offsets may be fixed + //offsets seem to be wrong if the function is in an assignment + //we may have to look at offsets again assertSliced('Nested Side-Effect For First', shell, 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', ['9@b'], 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n') @@ -284,7 +281,7 @@ cat(x)`) y <- 5 x <- 2 a()(y) -cat(x)`, ['4@x'], `a <- function() { +cat(x)`, ['6@x'], `a <- function() { function(b) { if(runif() > .5) { x <<- b diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index 8b4a0359ec..c319b5bf0b 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -61,14 +61,14 @@ cat(x)`) describe('definitions', () => { describe('[[', () => { const code = '\na <- list(1,2)\na[[1]] = 2\na[[2]] = 3\nb[[4]] = 5\ncat(a)\na <- list(3,4)\ncat(a)\n' - //may be fixed + //we get an added space in front of the access assertSliced('Repeated named access and definition', shell, code, ['6@a'], 'a <- list(1,2)\na[[1]] = 2\na[[2]] = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, code, ['8@a'], `a <- list(3,4) cat(a)`) }) describe('$', () => { const codeB = '\na <- list(a=1,b=2)\na$a = 2\na$b = 3\nb[[4]] = 5\ncat(a)\na <- list(a=3,b=4)\ncat(a)\n' - //may be fixed + //we get an added space in front of the access assertSliced('Repeated named access and definition', shell, codeB, ['6@a'], 'a <- list(a=1,b=2)\na$a = 2\na$b = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, codeB, ['8@a'], `a <- list(a=3,b=4) cat(a)`) From 7bf27504aafdd12e27730c488942247a69430c55 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 25 Apr 2024 23:04:57 +0200 Subject: [PATCH 67/88] tests(reconstruct): made changes to isolate failed tests --- src/reconstruct/reconstruct.ts | 2 +- .../slicing/reconstruct/simple-tests.ts | 2 +- .../static-program-slices/calls-tests.ts | 21 +++++++------------ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 68d354eb23..b5af74c08d 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -200,7 +200,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi //console.log('we have an else-body') const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc - const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' { } ', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }] : [] + const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }] : [] const elseOffset = when.length === 0 ? 4 : 0 out = merge([ out, diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 5854090aff..c75ad72783 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -117,7 +117,7 @@ describe('Simple', withShell(shell => { describe('Branches', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '3', '0'], expected: 'if(TRUE) { x <- 3 }\nx' }, - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) { } else { x <- 4 }\nx' } + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) {} else { x <- 4 }\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 4286ee1a2c..89b2100fa4 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -87,7 +87,7 @@ a(x = 3)` assertSliced('Must work for same named arguments too', shell, 'a <- 3\nb <- foo(a=a)', ['2@b'], 'a <- 3\nb <- foo(a=a)') - //may be fixed + //somehow line offsets get lost assertSliced('Must work for same named arguments nested', shell, '\nf <- function(some_variable="hello") {\n result <- some::other(some_variable=some_variable)\n result\n}\n ', ['4@result'], 'f <- function(some_variable="hello") {\n result <- some::other(some_variable=some_variable)\n result\n}') @@ -165,18 +165,11 @@ b(a)` assertSliced('Slice of b-call uses function', shell, code, ['4@b'], `a <- function() { i } b <- function(f) { i <- 5; f() } b(a)`) - //line offsets for a's function body may be wrong - assertSliced('Directly call returned function', shell, `m <- 12 -a <- function(x) { - b <- function() { function() { x } } - return(b()) -} -a(m)()`, ['$25' /* we can't directly slice the second call as the "a" name would take the inner call */], `m <- 12 -a <- function(x) { - b <- function() { function() { x } } - return(b()) -} -a(m)()`) + //offsets seem to be wrong if the function is in an assignment + //we may have to look at offsets again + assertSliced('Directly call returned function', shell, + 'm <- 12\na <- function(x) {\n b <- function() { function() { x } }\n return(b())\n}\na(m)()', ['$25' /* we can't directly slice the second call as the "a" name would take the inner call */], + 'm <- 12\na <- function(x) {\n b <- function() { function() { x } }\n return(b())\n}\na(m)()') //offsets seem to be wrong if the function is in an assignment //we may have to look at offsets again assertSliced('Higher order anonymous function', shell, @@ -281,7 +274,7 @@ cat(x)`) y <- 5 x <- 2 a()(y) -cat(x)`, ['6@x'], `a <- function() { +cat(x)`, ['3@x'], `a <- function() { function(b) { if(runif() > .5) { x <<- b From 63be95efcf20896fe5f5f45d79ac85767c82ee45 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 25 Apr 2024 23:38:14 +0200 Subject: [PATCH 68/88] test(reconstruct): added test to better understand assignments --- src/reconstruct/reconstruct.ts | 7 +++++-- .../slicing/reconstruct/simple-tests.ts | 12 ++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index b5af74c08d..2e98f639f9 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -38,7 +38,7 @@ import { prettyPrintCodeToString } from './helper' import type { SourcePosition, SourceRange } from '../util/range' -//import { jsonReplacer } from '../util/json' +import { jsonReplacer } from '../util/json' export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -154,8 +154,11 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v //add heuristic to select needed semicollons //maybe if expr 1,5 => select next semicollon function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { - return node.info.additionalTokens?.filter(t => t.lexeme && t.location) + const out = node.info.additionalTokens?.filter(t => t.lexeme && t.location) .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] + + console.log(JSON.stringify(out,jsonReplacer)) + return out } function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index c75ad72783..647ea54c75 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -123,6 +123,18 @@ describe('Simple', withShell(shell => { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) } }) + + describe('Functions in assignments', () => { + const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ + { name: 'Nested Side-Effect For First', + case: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', + argument: ['0', '4', '5', '19', '20'], + expected: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n' } + ] + for(const test of testCases) { + assertReconstructed(test.name, shell, test.case, test.argument, test.expected) + } + }) describe('Failures in practice', () => { assertReconstructed('Reconstruct expression list in call', shell, ` a <- foo({ From 0e5bf7c25f1e325f36d71390ec634c75057a7a27 Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 26 Apr 2024 15:28:27 +0200 Subject: [PATCH 69/88] fix: local patches --- src/reconstruct/reconstruct.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 2e98f639f9..214feac120 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -82,7 +82,7 @@ function reconstructRawBinaryOperator(lhs: Code, n: string, rhs: Code): Code { return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), { linePart: [{ part: `${prettyPrintCodeToString([lhs[lhs.length - 1]])} ${n} ${prettyPrintCodeToString([rhs[0]])}`, loc: lhs[lhs.length - 1].linePart[lhs.length - 1].loc }], indent: 0 }, - ...indentBy(rhs.slice(1, rhs.length), 1) + ...rhs.slice(1, rhs.length) ] } From d8004b68055eeeec52d7de1dd48d23612b18cba3 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:00:15 +0200 Subject: [PATCH 70/88] test-fix(reconstruct): some fixes and minor change to merge --- src/reconstruct/helper.ts | 4 +- src/reconstruct/reconstruct.ts | 46 +++++++++---------- .../slicing/reconstruct/simple-tests.ts | 7 +-- .../static-program-slices/calls-tests.ts | 8 +++- 4 files changed, 35 insertions(+), 30 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 1cb2e16693..f537f8ab49 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -55,7 +55,7 @@ export function plainSplit(text: string, location: SourcePosition): Code { /** * this function will merge up to n code pieces into a singular code piece, garanting that there are no duplicate lines and all lines are in order */ -export function merge(snipbits: Code[]): Code { +export function merge(...snipbits: Code[]): Code { const buckets: PrettyPrintLine[] = [] const result: Code = [] @@ -136,7 +136,7 @@ export function getIndentString(indent: number): string { } export function prettyPrintCodeToString(code: Code, lf = '\n'): string { - code = merge([code]) + code = merge(code) return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc.column)}`).join(lf) } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 214feac120..98fc527bb1 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -71,10 +71,10 @@ function reconstructExpressionList(exprList: RExpressionList, return [] } else { const additionalTokens = reconstructAdditionalTokens(exprList) - return merge([ + return merge( ...subExpressions, ...additionalTokens - ]) + ) } } @@ -121,7 +121,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v return plain(getLexeme(loop), start ?? loop.location.start) } if(isSelected(configuration, loop.body)) { - return merge([body]) + return merge(body) } const additionalTokens = reconstructAdditionalTokens(loop) const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc @@ -132,12 +132,12 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v return [] } - const out = merge([ + const out = merge( [{ linePart: [{ part: 'for', loc: start ?? loop.location.start }], indent: 0 }], [{ linePart: [{ part: getLexeme(loop.variable), loc: loop.variable.location.start }], indent: 0 }], reconstructedVector, ...additionalTokens - ]) + ) //if body empty if(body.length < 1) { // puts {} with one space separation after for(...) @@ -148,7 +148,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } //normal reconstruct out.push(...body) - return merge([out]) + return merge(out) } //add heuristic to select needed semicollons @@ -168,15 +168,14 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, return [] } else { const additionalTokens = reconstructAdditionalTokens(loop) - return merge([ + return merge( [{ linePart: [{ part: 'repeat', loc: loop.location.start }], indent: 0 }], body, ...additionalTokens - ]) + ) } } -//make use of additional tokens function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = ifThenElse.location.start //const endPos = ifThenElse.location.end @@ -193,11 +192,11 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi const additionalTokens = reconstructAdditionalTokens(ifThenElse) //console.log('additional Tokens: ', JSON.stringify(additionalTokens,jsonReplacer)) - let out = merge([ + let out = merge( ...additionalTokens, [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }], when - ]) + ) if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { //console.log('we have an else-body') @@ -205,12 +204,12 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi const elsePos = hBody[hBody.length - 1].loc const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }] : [] const elseOffset = when.length === 0 ? 4 : 0 - out = merge([ + out = merge( out, fakeWhenBlock, [{ linePart: [{ part: 'else', loc: { line: elsePos.line, column: elsePos.column + 2 +elseOffset } }], indent: 0 }], //may have to change the location otherwise - ]) + ) } return out @@ -222,10 +221,10 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co return plain(getLexeme(loop), start) } const additionalTokens = reconstructAdditionalTokens(loop) - const out = merge([ + const out = merge( [{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? loop.location.start }], indent: 0 }], ...additionalTokens - ]) + ) if(body.length < 1) { //this puts {} one space after while(...) const hBody = out[out.length - 1].linePart @@ -234,7 +233,7 @@ function reconstructWhileLoop(loop: RWhileLoop, condition: Co } else { out.push(...body) } - return merge([out]) + return merge(out) } function reconstructParameters(parameters: RParameter[]): string[] { @@ -297,11 +296,11 @@ function reconstructParameter(parameter: RParameter, name: Co function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { - return merge([body]) + return merge(body) } if(isSelected(configuration, definition.body)) { - const out = merge([body]) + const out = merge(body) const h = out[out.length - 1].linePart if(h[h.length - 1].part === ';') { out.pop() @@ -314,11 +313,11 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition): Code { @@ -333,9 +332,9 @@ function reconstructSpecialInfixFunctionCall(args: (Code | undefined)[], call: R // else if (rhs === undefined || rhs.length === 0) { // if rhs is undefined we still have to keep both now, but reconstruct manually :/ if(lhs !== undefined && lhs.length > 0) { - const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, lhs[0].linePart[0].loc.column)}`).join('\n') + const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, 0)}`).join('\n') if(rhs !== undefined && rhs.length > 0) { - const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, rhs[0].linePart[0].loc.column)}`).join('\n') + const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, 0)}`).join('\n') return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location.start) } else { return plain(lhsText, call.location.start) @@ -366,7 +365,8 @@ function reconstructFunctionCall(call: RFunctionCall, functio // add empty call braces if not present functionName[0].linePart[functionName[0].linePart.length - 1].part += '()' } - return [{ linePart: functionName[0].linePart, indent: functionName[0].indent }] + const additionalTokens = reconstructAdditionalTokens(call) + return merge([{ linePart: functionName[0].linePart, indent: functionName[0].indent }], ...additionalTokens) } else { return plain(getLexeme(call), call.location.start) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 647ea54c75..b8d4a59c1f 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -117,7 +117,7 @@ describe('Simple', withShell(shell => { describe('Branches', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '3', '0'], expected: 'if(TRUE) { x <- 3 }\nx' }, - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) {} else { x <- 4 }\nx' } + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) {} else { x <- 4 }\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) @@ -128,8 +128,8 @@ describe('Simple', withShell(shell => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ { name: 'Nested Side-Effect For First', case: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', - argument: ['0', '4', '5', '19', '20'], - expected: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n' } + argument: ['0', '1', '2', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '20', '21', '22', '23'], + expected: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) @@ -146,6 +146,7 @@ a <- foo({ c <- 3 })`) + //we need a way to detect if the offset can be zeroed assertReconstructed('Reconstruct access in pipe', shell, ` ls <- x[[1]] %>% st_cast() class(ls)`, '2', 'x[[1]]') diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 89b2100fa4..d518649fae 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -88,6 +88,7 @@ a(x = 3)` assertSliced('Must work for same named arguments too', shell, 'a <- 3\nb <- foo(a=a)', ['2@b'], 'a <- 3\nb <- foo(a=a)') //somehow line offsets get lost + //the assignment gets lost too assertSliced('Must work for same named arguments nested', shell, '\nf <- function(some_variable="hello") {\n result <- some::other(some_variable=some_variable)\n result\n}\n ', ['4@result'], 'f <- function(some_variable="hello") {\n result <- some::other(some_variable=some_variable)\n result\n}') @@ -133,6 +134,8 @@ u <- a() u()` //this test fails, we still need to retain some semicollons + //a() gets preserved but the assignment is removed + //we may want to fix a() to the start of the line in this case assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { x <- function() ; return(x) } a()`) //this test fails, semicollons should not get removed here @@ -200,7 +203,7 @@ cat(x) //we may have to look at offsets again assertSliced('Nested Side-Effect For First', shell, 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', ['9@b'], - 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n') + 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()') }) describe('Recursive functions', () => { const code = `f <- function() { f() } @@ -274,7 +277,7 @@ cat(x)`) y <- 5 x <- 2 a()(y) -cat(x)`, ['3@x'], `a <- function() { +cat(x)`, ['7@x'], `a <- function() { function(b) { if(runif() > .5) { x <<- b @@ -323,6 +326,7 @@ cat(4 %b% 5) cat(3 %a% 4)`) assertSliced('Must link with backticks', shell, code, ['9:7'], `'%b%' <- function(x, y) { x * y } cat(4 %b% 5)`) + //something went wrong assertSliced('Must work with assigned custom pipes too', shell, 'a <- b %>% c %>% d', ['1@a'], 'a <- b %>% c %>% d') }) describe('Using own alias infix operators', () => { From eef785489a7c69b29e602c8e957466939d78b396 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:03:13 +0200 Subject: [PATCH 71/88] lint-fix(reconstruct): linter fixes --- src/reconstruct/reconstruct.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 98fc527bb1..c4dff93add 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -31,7 +31,6 @@ import type { import { prettyPrintPartToString, plain, - indentBy, isSelected, getIndentString, merge, From 2ca0554ab56571ad5e6a27dac0ed4599f4094fab Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 29 Apr 2024 15:30:13 +0200 Subject: [PATCH 72/88] feat-fix(reconstruct): implemented merge in function call --- src/reconstruct/reconstruct.ts | 6 ++++++ test/functionality/slicing/reconstruct/function-tests.ts | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index c4dff93add..382f69a6bc 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -354,6 +354,12 @@ function reconstructFunctionCall(call: RFunctionCall, functio return [] } + functionName = merge(functionName) + if(args !== undefined) { + const defArgs: Code[] = args as Code[] + args = [merge(...defArgs)] + } + if(args.length === 0) { guard(functionName.length === 1, `without args, we need the function name to be present! got: ${JSON.stringify(functionName)}`) if(call.flavor === 'unnamed' && !functionName[0].linePart[functionName[0].linePart.length - 1].part.endsWith(')')) { diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 775e0ebba4..94c436b7f1 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -24,7 +24,7 @@ describe('Functions Reconstruct', () => { describe('merge', () => { function positive(snipbits: Code[],expected: Code) { it(prettyPrintCodeToString(expected),() => { - const result:Code = merge(snipbits) + const result:Code = merge(...snipbits) //console.log(JSON.stringify(result)) assert.deepStrictEqual(result, expected) }) @@ -134,7 +134,7 @@ describe('Functions Reconstruct', () => { function positive(lines: number) { const snipbit = makeTestCase(lines) it(`case with ${lines} lines`, () => { - const merged = merge(snipbit) + const merged = merge(...snipbit) assert.isTrue(checkTestCase(merged), JSON.stringify(merged)) }) } From 183dac051f57d0f30888af9c197c95a68f221d78 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 3 May 2024 14:33:13 +0200 Subject: [PATCH 73/88] feat(reconstruct): started work on semicolon adding --- src/reconstruct/helper.ts | 20 +++++++++++++++++++ .../slicing/reconstruct/simple-tests.ts | 7 +++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index f537f8ab49..d6253649bb 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,3 +1,4 @@ +import { boolean } from 'joi' import type { NodeId, ParentInformation, RNode } from '../r-bridge' import { RType } from '../r-bridge' import type { SourcePosition } from '../util/range' @@ -135,8 +136,27 @@ export function getIndentString(indent: number): string { return ' '.repeat(indent * 4) } +function addSemis(code: Code): boolean { + const line: string[][] = [] + for(const elem of code) { + let currLine = 0 + for(const linePart of elem.linePart) { + currLine = linePart.loc.line + line[currLine].push(linePart.part) + } + } + + for(const lineElements of line) { + //add search for space in need of semicolon + //if two stetment blocks follow each other add a delimiter + } + + return true +} + export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge(code) + addSemis(code) return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc.column)}`).join(lf) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index b8d4a59c1f..fec4cf6337 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -100,14 +100,13 @@ describe('Simple', withShell(shell => { }) }) - describe('function definition', () => { + describe.only('function definition', () => { const testCases: {name: string, case: string, argument: string[], expected: string}[] = [ - //this test does not reconstruct the function { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, - //this test does not reconstruct the function { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, - { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } + { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' }, + { name: 'semicollon reconstruction', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['0', '1', '2', '3', '4', '5', '6', '7', '8'], expected: 'a <- function (x) { x <- 2; x + 4 }' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 71a79c75e3cf922bcfb9552df61a53b28830bdaf Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Mon, 6 May 2024 16:25:58 +0200 Subject: [PATCH 74/88] feat(reconstruction): semicolon problem solving --- src/reconstruct/helper.ts | 106 ++++++++++++++++-- src/reconstruct/reconstruct.ts | 3 - .../slicing/reconstruct/function-tests.ts | 23 +++- .../slicing/reconstruct/simple-tests.ts | 5 +- 4 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index d6253649bb..4fed2bbe8f 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,6 +1,6 @@ -import { boolean } from 'joi' import type { NodeId, ParentInformation, RNode } from '../r-bridge' import { RType } from '../r-bridge' +import { jsonReplacer } from '../util/json' import type { SourcePosition } from '../util/range' import type { ReconstructionConfiguration } from './reconstruct' @@ -136,21 +136,113 @@ export function getIndentString(indent: number): string { return ' '.repeat(indent * 4) } +/* +function dist(pos1: number, pos2: number) { + Math.abs(pos1 - pos2) +} +*/ + function addSemis(code: Code): boolean { - const line: string[][] = [] + + function contains(array: string[], elem: string): boolean { + if(elem === '<-' || elem === '->' || elem === '<<-' || elem === '->>') { + return true + } + for(const arrElem of array) { + for(const char of elem) { + if(arrElem === char) { + return true + } + } + } + return false + } + const line: PrettyPrintLinePart[][] = [] + const specialChar = ['+', '-', '*', '/', ':', '<-', '->', '<<-', '->>', '$', '$$', '&', '&&', '||', '?', '<', '>', '=', '<=', '>=', '==', '(', ')', '{', '}', '[', '[[', ']', ']]'] for(const elem of code) { - let currLine = 0 + let currLine = 1 for(const linePart of elem.linePart) { currLine = linePart.loc.line - line[currLine].push(linePart.part) + if(line[currLine] === undefined) { + line[currLine] = [] + } + line[currLine].push(linePart) } } for(const lineElements of line) { - //add search for space in need of semicolon - //if two stetment blocks follow each other add a delimiter - } + if(lineElements === undefined) { + continue + } + //console.log('current line: ', lineElements[0].loc.line) + const heuristic = { assignment: false, brackets: false, lastChar: lineElements[0], statement: false, addedSemi: false } + let possibleSemi = heuristic.lastChar.loc + for(const elem of lineElements) { + console.log('\nelem: ', JSON.stringify(elem, jsonReplacer),'\n') + const lastChar = heuristic.lastChar.part + heuristic.brackets = lastChar[lastChar.length - 1] === ')' || lastChar[lastChar.length - 1] === '}' + heuristic.statement = contains(specialChar, heuristic.lastChar.part) + + //check if the current element may be followed by a semicolon + if(elem.part[elem.part.length - 1] === ')' || elem.part[elem.part.length - 1] === '}') { + //closing brackets + const lastSemi = { line: possibleSemi.line, column: possibleSemi.column + 1 } + const other = { line: elem.loc.line, column: elem.loc.column } + //possibleSemi = (dist(lastSemi.column, elem.loc.column) < dist(other.column, elem.loc.column))? other : possibleSemi + possibleSemi = other + heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) + console.log('\n last semi: ', JSON.stringify(lastSemi, jsonReplacer), '\n other: ', JSON.stringify(other, jsonReplacer), '\n possibleSemi: ', JSON.stringify(possibleSemi, jsonReplacer), '\n elem.loc: ', JSON.stringify(elem.loc, jsonReplacer), '\n') + } else if((elem.loc.column - heuristic.lastChar.loc.column - heuristic.lastChar.part.length) >= 2) { + //large space + const other = { line: heuristic.lastChar.loc.line, column: heuristic.lastChar.loc.column + 1 } + const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } + //possibleSemi = (dist(lastSemi.column, elem.loc.column) < dist(other.column, elem.loc.column))? other : possibleSemi + possibleSemi = other + heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) + console.log('\n last semi: ', JSON.stringify(lastSemi, jsonReplacer), '\n other: ', JSON.stringify(other, jsonReplacer), '\n possibleSemi: ', JSON.stringify(possibleSemi, jsonReplacer), '\n elem.loc: ', JSON.stringify(elem.loc, jsonReplacer), '\n') + } + console.log('possible semis: ', JSON.stringify(possibleSemi, jsonReplacer), '\n heuristic: ', JSON.stringify(heuristic, jsonReplacer), '\n') + + + //checking condishions for adding semicolons + if((elem.part === '<-') || (elem.part === '->') || (elem.part === '<<-') || (elem.part === '->>')) { + //check for assignments + if(heuristic.assignment) { + if(!heuristic.addedSemi) { + code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) + heuristic.addedSemi = true + console.log(`semicolon added: ${JSON.stringify(possibleSemi,jsonReplacer)}`) + } + } + heuristic.assignment = !heuristic.assignment + } else if((elem.part[0] === '(' || elem.part[0] === '{')) { + //check for brackets + heuristic.assignment = false + if(heuristic.brackets) { + if(!heuristic.addedSemi) { + code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) + heuristic.addedSemi = true + console.log(`semicolon added: ${JSON.stringify(possibleSemi,jsonReplacer)}`) + } + heuristic.brackets = false + } + } else if(!contains(specialChar, elem.part)) { + //check for two consecutive statements + if(heuristic.statement) { + if(!heuristic.addedSemi) { + code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) + heuristic.addedSemi = true + console.log(`semicolon added: ${JSON.stringify(possibleSemi,jsonReplacer)}`) + } + } + } + + //update the last character seen + heuristic.lastChar = elem + } + } + code = merge(code) return true } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 382f69a6bc..da5f014d86 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -37,7 +37,6 @@ import { prettyPrintCodeToString } from './helper' import type { SourcePosition, SourceRange } from '../util/range' -import { jsonReplacer } from '../util/json' export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -155,8 +154,6 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { const out = node.info.additionalTokens?.filter(t => t.lexeme && t.location) .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] - - console.log(JSON.stringify(out,jsonReplacer)) return out } diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 94c436b7f1..174bc33183 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -1,6 +1,7 @@ import { assert } from 'chai' import type { Code, PrettyPrintLinePart } from '../../../../src/reconstruct/helper' -import { plain, merge, prettyPrintCodeToString, prettyPrintPartToString } from '../../../../src/reconstruct/helper' +import { plain, merge, prettyPrintCodeToString, prettyPrintPartToString, plainSplit } from '../../../../src/reconstruct/helper' +import { jsonReplacer } from '../../../../src/util/json' describe('Functions Reconstruct', () => { describe('plain', () => { @@ -160,4 +161,24 @@ describe('Functions Reconstruct', () => { positive(testCase.input, testCase.expected, testCase.msg, testCase.columnOffset) } }) + describe.only('semicolon reconstruction', () => { + function positive(input: string, expected: string, msg: string) { + it(`Add semicolons to ${JSON.stringify(input, jsonReplacer)}`, () => { + const convertedInput = plainSplit(input, { line: 1, column: 1 }) + const out = prettyPrintCodeToString(convertedInput) + //console.log(`initial input: ${JSON.stringify(convertedInput, jsonReplacer)}`) + assert.strictEqual(out, expected, msg) + }) + } + + const testCases = [ + { input: 'a <- function (x) { x <- 2 x + 4 }', expected: 'a <- function (x) { x <- 2; x + 4 }', msg: 'semi in function that gets assigned' }, + { input: 'function (x) {x <- 2 3}', expected: 'function (x) {x <- 2; 3}', msg: 'standart single line function' } + ] + for(const testCase of testCases) { + positive(testCase.input, testCase.expected, testCase.msg) + } + + //[{ linePart: [{ part: 'a', loc: { line: 1, column: 1 } }], indent: 0 }, { linePart: [{ part: '<-', loc: { line: 1, column: 3 } }], indent: 0 }, { linePart: [{ part: 'function (x)', loc: { line: 1, column: 1 } }], indent: 0 }] + }) }) \ No newline at end of file diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index fec4cf6337..33f12598e5 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -100,13 +100,12 @@ describe('Simple', withShell(shell => { }) }) - describe.only('function definition', () => { + describe('function definition', () => { const testCases: {name: string, case: string, argument: string[], expected: string}[] = [ { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, - { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' }, - { name: 'semicollon reconstruction', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['0', '1', '2', '3', '4', '5', '6', '7', '8'], expected: 'a <- function (x) { x <- 2; x + 4 }' } + { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) From 04f9b021cb0adfaa8c13ff78267b1f0163f8521f Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Wed, 8 May 2024 00:16:51 +0200 Subject: [PATCH 75/88] feat(reconstruct): finished semicolon reconstruction --- src/reconstruct/helper.ts | 71 +++++++++++-------- .../slicing/reconstruct/function-tests.ts | 9 +-- .../slicing/reconstruct/simple-tests.ts | 6 -- .../static-program-slices/calls-tests.ts | 9 +-- 4 files changed, 46 insertions(+), 49 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 4fed2bbe8f..9ac916e968 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,6 +1,5 @@ import type { NodeId, ParentInformation, RNode } from '../r-bridge' import { RType } from '../r-bridge' -import { jsonReplacer } from '../util/json' import type { SourcePosition } from '../util/range' import type { ReconstructionConfiguration } from './reconstruct' @@ -142,17 +141,18 @@ function dist(pos1: number, pos2: number) { } */ -function addSemis(code: Code): boolean { +function addSemis(code: Code): Code { function contains(array: string[], elem: string): boolean { if(elem === '<-' || elem === '->' || elem === '<<-' || elem === '->>') { return true } + if(elem === 'in' || elem === ' {} ') { + return true + } for(const arrElem of array) { - for(const char of elem) { - if(arrElem === char) { - return true - } + if(elem === arrElem) { + return true } } return false @@ -174,81 +174,92 @@ function addSemis(code: Code): boolean { if(lineElements === undefined) { continue } - //console.log('current line: ', lineElements[0].loc.line) - const heuristic = { assignment: false, brackets: false, lastChar: lineElements[0], statement: false, addedSemi: false } + const heuristic = { assignment: false, brackets: false, lastChar: lineElements[0], statement: false, addedSemi: false, curlyBrackets: false } let possibleSemi = heuristic.lastChar.loc + lineElements.splice(0, 1) for(const elem of lineElements) { - console.log('\nelem: ', JSON.stringify(elem, jsonReplacer),'\n') const lastChar = heuristic.lastChar.part - heuristic.brackets = lastChar[lastChar.length - 1] === ')' || lastChar[lastChar.length - 1] === '}' - heuristic.statement = contains(specialChar, heuristic.lastChar.part) + heuristic.brackets = lastChar[lastChar.length - 1] === ')' + heuristic.curlyBrackets = lastChar[lastChar.length - 1] === '}' + heuristic.statement = !contains(specialChar, heuristic.lastChar.part) + if(heuristic.addedSemi) { + heuristic.assignment = false + } + //check if the current element may be followed by a semicolon - if(elem.part[elem.part.length - 1] === ')' || elem.part[elem.part.length - 1] === '}') { + if(elem.part[elem.part.length - 1] === ')') { //closing brackets - const lastSemi = { line: possibleSemi.line, column: possibleSemi.column + 1 } - const other = { line: elem.loc.line, column: elem.loc.column } - //possibleSemi = (dist(lastSemi.column, elem.loc.column) < dist(other.column, elem.loc.column))? other : possibleSemi + const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } + const other = { line: heuristic.lastChar.loc.line , column: heuristic.lastChar.loc.column + heuristic.lastChar.part.length } + possibleSemi = other + heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) + } else if(elem.part[elem.part.length - 1] === '}') { + //closing curlyBrackets + const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } + const other = { line: heuristic.lastChar.loc.line , column: heuristic.lastChar.loc.column + heuristic.lastChar.part.length } possibleSemi = other heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) - console.log('\n last semi: ', JSON.stringify(lastSemi, jsonReplacer), '\n other: ', JSON.stringify(other, jsonReplacer), '\n possibleSemi: ', JSON.stringify(possibleSemi, jsonReplacer), '\n elem.loc: ', JSON.stringify(elem.loc, jsonReplacer), '\n') - } else if((elem.loc.column - heuristic.lastChar.loc.column - heuristic.lastChar.part.length) >= 2) { + } else if((elem.loc.column - (heuristic.lastChar.loc.column + heuristic.lastChar.part.length)) >= 1) { //large space - const other = { line: heuristic.lastChar.loc.line, column: heuristic.lastChar.loc.column + 1 } + const other = { line: heuristic.lastChar.loc.line , column: heuristic.lastChar.loc.column + heuristic.lastChar.part.length } const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } - //possibleSemi = (dist(lastSemi.column, elem.loc.column) < dist(other.column, elem.loc.column))? other : possibleSemi possibleSemi = other heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) - console.log('\n last semi: ', JSON.stringify(lastSemi, jsonReplacer), '\n other: ', JSON.stringify(other, jsonReplacer), '\n possibleSemi: ', JSON.stringify(possibleSemi, jsonReplacer), '\n elem.loc: ', JSON.stringify(elem.loc, jsonReplacer), '\n') } - console.log('possible semis: ', JSON.stringify(possibleSemi, jsonReplacer), '\n heuristic: ', JSON.stringify(heuristic, jsonReplacer), '\n') - //checking condishions for adding semicolons if((elem.part === '<-') || (elem.part === '->') || (elem.part === '<<-') || (elem.part === '->>')) { - //check for assignments + //check for assignments if(heuristic.assignment) { if(!heuristic.addedSemi) { code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) heuristic.addedSemi = true - console.log(`semicolon added: ${JSON.stringify(possibleSemi,jsonReplacer)}`) } } heuristic.assignment = !heuristic.assignment - } else if((elem.part[0] === '(' || elem.part[0] === '{')) { + } else if(elem.part[0] === '(') { //check for brackets heuristic.assignment = false if(heuristic.brackets) { if(!heuristic.addedSemi) { code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) heuristic.addedSemi = true - console.log(`semicolon added: ${JSON.stringify(possibleSemi,jsonReplacer)}`) } heuristic.brackets = false } + } else if(elem.part[0] === '{') { + //check for curlyBrackets + heuristic.assignment = false + if(heuristic.curlyBrackets) { + if(!heuristic.addedSemi) { + code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) + heuristic.addedSemi = true + } + heuristic.curlyBrackets = false + } } else if(!contains(specialChar, elem.part)) { //check for two consecutive statements if(heuristic.statement) { if(!heuristic.addedSemi) { code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) heuristic.addedSemi = true - console.log(`semicolon added: ${JSON.stringify(possibleSemi,jsonReplacer)}`) } } } - + //update the last character seen heuristic.lastChar = elem } } code = merge(code) - return true + return code } export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge(code) - addSemis(code) + code = addSemis(code) return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc.column)}`).join(lf) } diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 174bc33183..2c956e679a 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -26,7 +26,6 @@ describe('Functions Reconstruct', () => { function positive(snipbits: Code[],expected: Code) { it(prettyPrintCodeToString(expected),() => { const result:Code = merge(...snipbits) - //console.log(JSON.stringify(result)) assert.deepStrictEqual(result, expected) }) } @@ -161,24 +160,22 @@ describe('Functions Reconstruct', () => { positive(testCase.input, testCase.expected, testCase.msg, testCase.columnOffset) } }) - describe.only('semicolon reconstruction', () => { + describe('semicolon reconstruction', () => { function positive(input: string, expected: string, msg: string) { it(`Add semicolons to ${JSON.stringify(input, jsonReplacer)}`, () => { const convertedInput = plainSplit(input, { line: 1, column: 1 }) const out = prettyPrintCodeToString(convertedInput) - //console.log(`initial input: ${JSON.stringify(convertedInput, jsonReplacer)}`) assert.strictEqual(out, expected, msg) }) } const testCases = [ { input: 'a <- function (x) { x <- 2 x + 4 }', expected: 'a <- function (x) { x <- 2; x + 4 }', msg: 'semi in function that gets assigned' }, - { input: 'function (x) {x <- 2 3}', expected: 'function (x) {x <- 2; 3}', msg: 'standart single line function' } + { input: 'function (x) { x <- 2 3 }', expected: 'function (x) { x <- 2; 3 }', msg: 'standart single line function' }, + { input: 'x <- 3 y <- 4', expected: 'x <- 3; y <- 4', msg: 'single line, double assign' } ] for(const testCase of testCases) { positive(testCase.input, testCase.expected, testCase.msg) } - - //[{ linePart: [{ part: 'a', loc: { line: 1, column: 1 } }], indent: 0 }, { linePart: [{ part: '<-', loc: { line: 1, column: 3 } }], indent: 0 }, { linePart: [{ part: 'function (x)', loc: { line: 1, column: 1 } }], indent: 0 }] }) }) \ No newline at end of file diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 33f12598e5..6400d1c785 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -8,7 +8,6 @@ describe('Simple', withShell(shell => { '{ x <- 5 }', '{ x <- 5; y <- 9 }' ]) { - //some tests fail due to removed semicollons, here it is intensional but it later causes troubles assertReconstructed(code, shell, code, '0', 'x <- 5') } }) @@ -38,9 +37,7 @@ describe('Simple', withShell(shell => { describe('repeat', () => { const pool: [string, string | string[], string][] = [ ['repeat { x }', '0', 'repeat { x }'], - //semicollon removed ['repeat { x <- 5; y <- 9 }', '0', 'repeat { x <- 5 }'], - //semicollon has to stay, otherwise the statement loses information ['repeat { x <- 5; y <- 9 }', ['0', '1', '4'], 'repeat { x <- 5; 9 }'] ] for(const [code, id, expected] of pool) { @@ -48,8 +45,6 @@ describe('Simple', withShell(shell => { } }) - //output consistend "while(...) {" - //may want to look into reconstruct for while describe('while', () => { const pool: [string, string | string[], string][] = [ ['while(TRUE) { x }', '1', 'while(TRUE) { x }'], @@ -59,7 +54,6 @@ describe('Simple', withShell(shell => { ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1'], 'while(TRUE) { x <- 5 }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '2'], 'while(TRUE) { x <- 5 }'], ['while(TRUE) { x <- 5; y <- 9 }', ['0', '4'], 'while(TRUE) { y <- 9 }'], - //semicollon has to stay, otherwise the statement loses information ['while(TRUE) { x <- 5; y <- 9 }', ['0', '1', '4'], 'while(TRUE) { x <- 5; y <- 9 }',], ['while(TRUE) {\n x <- 5\n y <- 9\n}', ['0', '1', '4'], 'while(TRUE) {\n x <- 5\n y <- 9\n}'], ['while(x + 2 > 3) { x <- 0 }', ['0'], 'while(x + 2 > 3) {}'], diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index d518649fae..6e4a198f7b 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -97,7 +97,6 @@ a(x = 3)` const lateCode = `f <- function(a=b, m=3) { b <- 1; a; b <- 5; a + 1 } f() ` - //this test fails, semicollons should not be removed assertSliced('Late bindings of parameter in body', shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { b <- 1; a + 1 } f()`) const lateCodeB = `f <- function(a=b, b=3) { b <- 1; a; b <- 5; a + 1 } @@ -133,12 +132,10 @@ z <- 5 u <- a() u()` - //this test fails, we still need to retain some semicollons //a() gets preserved but the assignment is removed //we may want to fix a() to the start of the line in this case - assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { x <- function() ; return(x) } + assertSliced('Must include function shell', shell, code, ['5@a'], `a <- function() { x <- function(); return(x) } a()`) - //this test fails, semicollons should not get removed here assertSliced('Must include function shell on call', shell, code, ['6@u'], `a <- function() { x <- function() { z + y }; y <- 12; return(x) } z <- 5 u <- a() @@ -164,7 +161,6 @@ b <- function(f) { i <- 5; f() } b(a)` assertSliced('Only i, not bound in context', shell, code, ['1@i'], 'i') assertSliced('Slice of b is independent', shell, code, ['3@b'], 'b <- function(f)') - //this test fails, semicollons need to be preserved here assertSliced('Slice of b-call uses function', shell, code, ['4@b'], `a <- function() { i } b <- function(f) { i <- 5; f() } b(a)`) @@ -227,7 +223,6 @@ a() cat(x)` assertSliced('Local redefinition has no effect', shell, localCode, ['5@x'], `x <- 3 cat(x)`) - //this test fails, semicollons must be preserved here assertSliced('Local redefinition must be kept as part of call', shell, localCode, ['4@a'], `a <- function() { x = x + 5; cat(x) } x <- 3 a()`) @@ -277,7 +272,7 @@ cat(x)`) y <- 5 x <- 2 a()(y) -cat(x)`, ['7@x'], `a <- function() { +cat(x)`, ['5@x'], `a <- function() { function(b) { if(runif() > .5) { x <<- b From b26c78063f80c3e2134fc5ea2efa03e8f5e800dd Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 23 May 2024 17:16:04 +0200 Subject: [PATCH 76/88] feat(reconstruct): started cleaning up merge --- src/reconstruct/helper.ts | 54 ++++---- src/reconstruct/reconstruct.ts | 240 +++++++++------------------------ 2 files changed, 88 insertions(+), 206 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 9ac916e968..b6da63aaac 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -1,6 +1,9 @@ -import type { NodeId, ParentInformation, RNode } from '../r-bridge' -import { RType } from '../r-bridge' +import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id' +import type { ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate' +import type { RNode } from '../r-bridge/lang-4.x/ast/model/model' +import { RType } from '../r-bridge/lang-4.x/ast/model/type' import type { SourcePosition } from '../util/range' +//Is this depricated? import type { ReconstructionConfiguration } from './reconstruct' export type Code = PrettyPrintLine[] @@ -20,10 +23,10 @@ export interface PrettyPrintLine { export function plain(text: string, location: SourcePosition): Code { const printLine: PrettyPrintLine = { linePart: [], indent: 0 } const split = text.split('\n') - let locationLine = location.line + let locationLine = location[0] for(const line of split) { - printLine.linePart.push({ part: line, loc: { column: location.column, line: locationLine++ } }) + printLine.linePart.push({ part: line, loc: [locationLine++, location[1]] }) } return [printLine] } @@ -31,17 +34,17 @@ export function plainSplit(text: string, location: SourcePosition): Code { const printLine: PrettyPrintLine = { linePart: [], indent: 0 } let i = 0 let token = '' - let currLoc = { line: location.line, column: location.column } + let currLoc: SourcePosition = [location[0], location[1]] while(i < text.length) { if(text[i] === ' ') { if(!(token === '')) { printLine.linePart.push({ part: token, loc: currLoc }) } - currLoc = { column: currLoc.column + token.length + 1, line: currLoc.line } + currLoc = [currLoc[0], currLoc[1] + token.length + 1] token = '' } else if(text[i] === '\n') { printLine.linePart.push({ part: token, loc: currLoc }) - currLoc = { column: location.column, line: currLoc.line + 1 } + currLoc = [currLoc[0] + 1, location[1]] token = '' } else { token = token.concat(text[i]) @@ -63,7 +66,7 @@ export function merge(...snipbits: Code[]): Code { for(const code of snipbits) { for(const line of code) { for(const part of line.linePart) { - const lineNumber = part.loc.line + const lineNumber = part.loc[0] if(buckets[lineNumber] === undefined) { //may be necessary as empty elements count as undefined and we don't want to reassign filled buckets buckets[lineNumber] = { linePart: [], indent: line.indent } } @@ -77,7 +80,7 @@ export function merge(...snipbits: Code[]): Code { if(line === undefined){ //appears to be necessary as 'buckets' may be sparse (empty elements count as undefined) continue } - line.linePart.sort((a, b) => a.loc.column - b.loc.column) + line.linePart.sort((a, b) => a.loc[1] - b.loc[1]) result.push(line) } @@ -88,12 +91,12 @@ export function prettyPrintPartToString(line: PrettyPrintLinePart[],columnOffset if(line.length === 0) { return '' } - line.sort((a, b) => a.loc.column - b.loc.column) + line.sort((a, b) => a.loc[1] - b.loc[1]) let result = '' for(const part of line) { const currLength = result.length + columnOffset //we have to 0 any negative values as they can happen??? - result += ' '.repeat(Math.max(part.loc.column - currLength, 0)) + result += ' '.repeat(Math.max(part.loc[1] - currLength, 0)) result = result.concat(part.part) } return result @@ -162,7 +165,7 @@ function addSemis(code: Code): Code { for(const elem of code) { let currLine = 1 for(const linePart of elem.linePart) { - currLine = linePart.loc.line + currLine = linePart.loc[0] if(line[currLine] === undefined) { line[currLine] = [] } @@ -191,22 +194,13 @@ function addSemis(code: Code): Code { //check if the current element may be followed by a semicolon if(elem.part[elem.part.length - 1] === ')') { //closing brackets - const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } - const other = { line: heuristic.lastChar.loc.line , column: heuristic.lastChar.loc.column + heuristic.lastChar.part.length } - possibleSemi = other - heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) + possibleSemi = updateSemi(possibleSemi, heuristic) } else if(elem.part[elem.part.length - 1] === '}') { //closing curlyBrackets - const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } - const other = { line: heuristic.lastChar.loc.line , column: heuristic.lastChar.loc.column + heuristic.lastChar.part.length } - possibleSemi = other - heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) - } else if((elem.loc.column - (heuristic.lastChar.loc.column + heuristic.lastChar.part.length)) >= 1) { + possibleSemi = updateSemi(possibleSemi, heuristic) + } else if((elem.loc[1] - (heuristic.lastChar.loc[1] + heuristic.lastChar.part.length)) >= 1) { //large space - const other = { line: heuristic.lastChar.loc.line , column: heuristic.lastChar.loc.column + heuristic.lastChar.part.length } - const lastSemi = { line: possibleSemi.line, column: possibleSemi.column } - possibleSemi = other - heuristic.addedSemi = (lastSemi.line === possibleSemi.line) && (lastSemi.column === possibleSemi.column) + possibleSemi = updateSemi(possibleSemi, heuristic) } //checking condishions for adding semicolons @@ -255,12 +249,20 @@ function addSemis(code: Code): Code { } code = merge(code) return code + + function updateSemi(possibleSemi: SourcePosition, heuristic: { assignment: boolean; brackets: boolean; lastChar: PrettyPrintLinePart; statement: boolean; addedSemi: boolean; curlyBrackets: boolean }) { + const lastSemi: SourcePosition = [possibleSemi[0], possibleSemi[1]] + const other: SourcePosition = [heuristic.lastChar.loc[0], heuristic.lastChar.loc[1] + heuristic.lastChar.part.length] + possibleSemi = other + heuristic.addedSemi = (lastSemi[0] === possibleSemi[0]) && (lastSemi[1] === possibleSemi[0]) + return possibleSemi + } } export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge(code) code = addSemis(code) - return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc.column)}`).join(lf) + return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc[1])}`).join(lf) } export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[]): Code { diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index b290b01863..3ed919cd96 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -8,41 +8,6 @@ import { expensiveTrace, log, LogLevel } from '../util/log' import { guard } from '../util/assert' import type { MergeableRecord } from '../util/objects' import type { -<<<<<<< HEAD - ParentInformation, - RAccess, - RArgument, - RBinaryOp, - RExpressionList, - RForLoop, - RFunctionCall, - RFunctionDefinition, - RIfThenElse, - RNodeWithParent, - RParameter, - RRepeatLoop, - RWhileLoop, - RPipe, - StatefulFoldFunctions } from '../r-bridge' -import { RType } from '../r-bridge' -import { log } from '../util/log' -import { guard, isNotNull } from '../util/assert' -import type { MergeableRecord } from '../util/objects' -import type { - Selection, - Code, - AutoSelectPredicate } from './helper' -import { - prettyPrintPartToString, - plain, - isSelected, - getIndentString, - merge, - prettyPrintCodeToString -} from './helper' -import type { SourcePosition, SourceRange } from '../util/range' - -======= NormalizedAst, ParentInformation, RNodeWithParent @@ -65,17 +30,11 @@ import type { RFunctionDefinition } from '../r-bridge/lang-4.x/ast/model/nodes/r import type { StatefulFoldFunctions } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' import { foldAstStateful } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id' +import { getRangeStart, SourcePosition, SourceRange } from '../util/range' +import { autoSelectLibrary, getIndentString, indentBy, isSelected, merge, plain, prettyPrintCodeToString, prettyPrintPartToString } from './helper' +import type { AutoSelectPredicate, Code, PrettyPrintLine, PrettyPrintLinePart } from './helper' type Selection = ReadonlySet -interface PrettyPrintLine { - line: string - indent: number -} -function plain(text: string): PrettyPrintLine[] { - return [{ line: text, indent: 0 }] -} -type Code = PrettyPrintLine[] ->>>>>>> upstream/main export const reconstructLogger = log.getSubLogger({ name: 'reconstruct' }) @@ -86,20 +45,15 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC return selectionHasLeaf ? foldToConst(leaf) : [] } -const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? n.location.start : { line: 0, column: 0 }) - -<<<<<<< HEAD -function reconstructExpressionList(exprList: RExpressionList, expressions: Code[], configuration: ReconstructionConfiguration): Code { - if(isSelected(configuration, exprList)) { - const positionStart = exprList.location? exprList.location.start : { line: 0, column: 0 } - return plain(getLexeme(exprList), positionStart) - } +const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? getRangeStart(n.location) : [0, 0]) -======= function reconstructExpressionList(exprList: RExpressionList, _grouping: [Code, Code] | undefined, expressions: Code[], config: ReconstructionConfiguration): Code { ->>>>>>> upstream/main const subExpressions = expressions.filter(e => e.length > 0) + if(isSelected(config, exprList)) { + const positionStart: SourcePosition = exprList.location? getRangeStart(exprList.location) : [0, 0] + return plain(getLexeme(exprList), positionStart) + } if(subExpressions.length === 0) { <<<<<<< HEAD @@ -239,8 +193,8 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v } const out = merge( - [{ linePart: [{ part: 'for', loc: start ?? loop.location.start }], indent: 0 }], - [{ linePart: [{ part: getLexeme(loop.variable), loc: loop.variable.location.start }], indent: 0 }], + [{ linePart: [{ part: 'for', loc: start ?? getRangeStart(loop.location) }], indent: 0 }], + [{ linePart: [{ part: getLexeme(loop.variable), loc: getRangeStart(loop.variable.location) }], indent: 0 }], reconstructedVector, ...additionalTokens ) @@ -249,7 +203,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v // puts {} with one space separation after for(...) const hBody = out[out.length - 1].linePart const bodyLoc = hBody[hBody.length - 1].loc - out.push({ linePart: [{ part: '{}', loc: { line: bodyLoc.line, column: bodyLoc.column + 2 } }], indent: 0 }) + out.push({ linePart: [{ part: '{}', loc: [bodyLoc[0], bodyLoc[1] + 2] }], indent: 0 }) return out } //normal reconstruct @@ -260,23 +214,24 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v //add heuristic to select needed semicollons //maybe if expr 1,5 => select next semicollon function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { - const out = node.info.additionalTokens?.filter(t => t.lexeme && t.location) - .map(t => plain(t.lexeme as string, (t.location as SourceRange).start)) ?? [] + let out: Code[] = node.info.additionalTokens?.filter(t => t.lexeme && t.location) + .map(t => plain(t.lexeme as string, getRangeStart((t.location? t.location : [0, 0]) as SourceRange)) ?? ([] as Code[])).filter(t => !(t === undefined)) as Code[] return out } function reconstructBodyWithHeader(header: PrettyPrintLine, body: Code, onEmpty: string): Code { + const h = body[body.length - 1].linePart if(body.length === 0) { - return [{ line: `${header.line}${onEmpty}`, indent: header.indent }] + return [{ linePart: [{ part: `${prettyPrintPartToString(header.linePart, 0)}${onEmpty}`, loc: header.linePart[0].loc }], indent: header.indent }] } else if(body.length === 1) { return [ - { line: `${header.line} ${body[0].line}`, indent: header.indent } + { linePart: [{ part: `${prettyPrintPartToString(header.linePart, 0)} ${body[0].linePart}`, loc: header.linePart[0].loc }], indent: header.indent } ] - } else if(body[0].line === '{' && body[body.length - 1].line === '}') { + } else if(body[0].linePart[0].part === '{' && h[h.length - 1].part === '}') { return [ - { line: `${header.line} {`, indent: header.indent }, + { linePart: [{ part: `${prettyPrintPartToString(header.linePart, 0)} {`, loc: header.linePart[0].loc }], indent: header.indent }, ...body.slice(1, body.length - 1), - { line: '}', indent: header.indent } + { linePart: [{ part: '}', loc: header.linePart[header.linePart.length - 1].loc }], indent: header.indent } ] } else { return [ @@ -287,31 +242,29 @@ function reconstructBodyWithHeader(header: PrettyPrintLine, body: Code, onEmpty: } +//what is happening here??? function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { -<<<<<<< HEAD if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), loop.location.start) + return body } else if(body.length === 0) { return [] } else { const additionalTokens = reconstructAdditionalTokens(loop) return merge( - [{ linePart: [{ part: 'repeat', loc: loop.location.start }], indent: 0 }], + [{ linePart: [{ part: 'repeat', loc: getRangeStart(loop.location) }], indent: 0 }], body, ...additionalTokens ) -======= const sel = isSelected(configuration, loop) if(!sel) { return body ->>>>>>> upstream/main } return reconstructBodyWithHeader({ line: 'repeat', indent: 0 }, body, '{}') } -<<<<<<< HEAD +//why is there pretty printing again?? function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { - const startPos = ifThenElse.location.start + const startPos = getRangeStart(ifThenElse.location) //const endPos = ifThenElse.location.end //const conditionPos = ifThenElse.condition.location? ifThenElse.condition.location.start : { line: 0, column: 0 } @@ -344,8 +297,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi [{ linePart: [{ part: 'else', loc: { line: elsePos.line, column: elsePos.column + 2 +elseOffset } }], indent: 0 }], //may have to change the location otherwise ) -======= -function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, then: Code, otherwise: Code | undefined, config: ReconstructionConfiguration): Code { + /*function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, then: Code, otherwise: Code | undefined, config: ReconstructionConfiguration): Code { otherwise ??= [] if(then.length === 0 && otherwise.length === 0) { if(isSelected(config, ifThenElse)) { @@ -386,53 +338,29 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi { line: `${otherwise[0].line}`, indent: 0 }, ...indentBy(otherwise.splice(1), 1) ] ->>>>>>> upstream/main } - + */ return out + } } function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { -<<<<<<< HEAD - const start = loop.location.start + const start = getRangeStart(loop.location) if(isSelected(configuration, loop)) { return plain(getLexeme(loop), start) } const additionalTokens = reconstructAdditionalTokens(loop) const out = merge( - [{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? loop.location.start }], indent: 0 }], + [{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? getRangeStart(loop.location) }], indent: 0 }], ...additionalTokens ) if(body.length < 1) { //this puts {} one space after while(...) const hBody = out[out.length - 1].linePart - const bodyLoc = { line: hBody[hBody.length - 1].loc.line, column: hBody[hBody.length - 1].loc.column + hBody[hBody.length - 1].part.length } - out.push({ linePart: [{ part: '{}', loc: { line: bodyLoc.line, column: bodyLoc.column + 1 } }], indent: 0 }) + const bodyLoc = [hBody[hBody.length - 1].loc[0], hBody[hBody.length - 1].loc[1] + hBody[hBody.length - 1].part.length] + out.push({ linePart: [{ part: '{}', loc: [bodyLoc[0], bodyLoc[1] + 1] }], indent: 0 }) } else { out.push(...body) -======= - const sel = isSelected(configuration, loop) - if(!sel && condition.length === 0) { - return body - } else if(body.length === 0 && condition.length === 0) { - return [] - } else if(body.length <= 1) { - // 'inline' - return [{ line: `while(${getLexeme(loop.condition)}) ${body.length === 0 ? '{}' : body[0].line}`, indent: 0 }] - } else if(body[0].line === '{' && body[body.length - 1].line === '}') { - // 'block' - return [ - { line: `while(${getLexeme(loop.condition)}) {`, indent: 0 }, - ...body.slice(1, body.length - 1), - { line: '}', indent: 0 } - ] - } else { - // unknown - return [ - { line: `while(${getLexeme(loop.condition)})`, indent: 0 }, - ...indentBy(body, 1) - ] ->>>>>>> upstream/main } return merge(out) } @@ -448,21 +376,18 @@ function reconstructParameters(parameters: RParameter[]): str }) } -<<<<<<< HEAD -function reconstructFoldAccess(node: RAccess, accessed: Code, access: string | (Code | null)[], configuration: ReconstructionConfiguration): Code { - const start = node.info.fullRange?.start ?? node.location.start - - if(isSelected(configuration, node)) { - return plain(getLexeme(node), start) - } - -======= function isNotEmptyArgument(a: Code | typeof EmptyArgument): a is Code { return a !== EmptyArgument } function reconstructFoldAccess(node: RAccess, accessed: Code, access: readonly (Code | typeof EmptyArgument)[]): Code { ->>>>>>> upstream/main + + const start = node.info.fullRange? getRangeStart(node.info.fullRange) : getRangeStart(node.location) + + if(isSelected(configuration, node)) { + return plain(getLexeme(node), start) + } + if(accessed.length === 0) { return access.filter(isNotEmptyArgument).flat() } else if(access.every(a => a === EmptyArgument || a.length === 0)) { @@ -472,16 +397,9 @@ function reconstructFoldAccess(node: RAccess, accessed: Code, return plain(getLexeme(node), start) } -<<<<<<< HEAD -function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined, configuration: ReconstructionConfiguration): Code { - const start = argument.location.start - if(isSelected(configuration, argument)) { - return plain(getLexeme(argument), start) - } - -======= function reconstructArgument(argument: RArgument, name: Code | undefined, value: Code | undefined): Code { ->>>>>>> upstream/main + const start = getRangeStart(argument.location) + if(argument.name !== undefined && name !== undefined && name.length > 0) { return plain(`${getLexeme(argument.name)}=${argument.value ? getLexeme(argument.value) : ''}`, start) } else { @@ -489,17 +407,12 @@ function reconstructArgument(argument: RArgument, name: Code } } -<<<<<<< HEAD -function reconstructParameter(parameter: RParameter, name: Code, value: Code | undefined, configuration: ReconstructionConfiguration): Code { - const start = parameter.location.start - if(isSelected(configuration, parameter)) { - return plain(getLexeme(parameter), start) -======= function reconstructParameter(parameter: RParameter, name: Code, defaultValue: unknown, configuration: ReconstructionConfiguration): Code { + const start = getRangeStart(parameter.location) + if(!isSelected(configuration, parameter)) { return [] ->>>>>>> upstream/main } if(parameter.defaultValue !== undefined && name.length > 0) { return plain(`${getLexeme(parameter.name)}=${getLexeme(parameter.defaultValue)}`, start) @@ -510,14 +423,12 @@ function reconstructParameter(parameter: RParameter, name: Co } } -<<<<<<< HEAD -function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: Code[], body: Code, configuration: ReconstructionConfiguration): Code { +//what are we doing here?? +function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: readonly Code[], body: Code, config: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { return merge(body) -======= - -function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: readonly Code[], body: Code, config: ReconstructionConfiguration): Code { + } // if a definition is not selected, we only use the body - slicing will always select the definition if(functionParameters.every(p => p.length === 0)) { const empty = body === undefined || body.length === 0 @@ -540,7 +451,6 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition>>>>>> upstream/main } if(isSelected(configuration, definition.body)) { @@ -574,24 +484,16 @@ function reconstructSpecialInfixFunctionCall(args: (Code | typeof EmptyArgument) } // else if (rhs === undefined || rhs.length === 0) { // if rhs is undefined we still have to keep both now, but reconstruct manually :/ -<<<<<<< HEAD - if(lhs !== undefined && lhs.length > 0) { - const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, 0)}`).join('\n') - if(rhs !== undefined && rhs.length > 0) { - const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, 0)}`).join('\n') - return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, call.location.start) -======= if(lhs !== EmptyArgument && lhs.length > 0) { - const lhsText = lhs.map(l => `${getIndentString(l.indent)}${l.line}`).join('\n') + const lhsText = lhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, 0)}`).join('\n') if(rhs !== EmptyArgument && rhs.length > 0) { - const rhsText = rhs.map(l => `${getIndentString(l.indent)}${l.line}`).join('\n') - return plain(`${lhsText} ${call.functionName.content} ${rhsText}`) ->>>>>>> upstream/main + const rhsText = rhs.map(l => `${getIndentString(l.indent)}${prettyPrintPartToString(l.linePart, 0)}`).join('\n') + return plain(`${lhsText} ${call.functionName.content} ${rhsText}`, lhs[0].linePart[0].loc) } else { - return plain(lhsText, call.location.start) + return plain(lhsText, getRangeStart(call.location)) } } - return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`, call.location.start) + return plain(`${getLexeme(call.arguments[0] as RArgument)} ${call.functionName.content} ${getLexeme(call.arguments[1] as RArgument)}`, getRangeStart(call.location)) } function reconstructFunctionCall(call: RFunctionCall, functionName: Code, args: (Code | typeof EmptyArgument)[], configuration: ReconstructionConfiguration): Code { @@ -608,13 +510,8 @@ function reconstructFunctionCall(call: RFunctionCall, functio if(call.infixSpecial === true) { return reconstructSpecialInfixFunctionCall(args, call) } -<<<<<<< HEAD - if(call.flavor === 'named' && isSelected(configuration, call)) { - return plain(getLexeme(call), call.location.start) -======= if(call.flavor === 'named' && selected) { - return plain(getLexeme(call)) ->>>>>>> upstream/main + return plain(getLexeme(call), getRangeStart(call.location)) } const filteredArgs = args.filter(a => a !== undefined && a.length > 0) if(functionName.length === 0 && filteredArgs.length === 0) { @@ -628,34 +525,20 @@ function reconstructFunctionCall(call: RFunctionCall, functio } if(args.length === 0) { -<<<<<<< HEAD - guard(functionName.length === 1, `without args, we need the function name to be present! got: ${JSON.stringify(functionName)}`) - if(call.flavor === 'unnamed' && !functionName[0].linePart[functionName[0].linePart.length - 1].part.endsWith(')')) { - functionName[0].linePart[0].part = `(${functionName[0].linePart[0].part})` - } - - if(!functionName[0].linePart[functionName[0].linePart.length - 1].part.endsWith('()')) { - // add empty call braces if not present - functionName[0].linePart[functionName[0].linePart.length - 1].part += '()' - } - const additionalTokens = reconstructAdditionalTokens(call) - return merge([{ linePart: functionName[0].linePart, indent: functionName[0].indent }], ...additionalTokens) -======= guard(functionName.length > 0, `without args, we need the function name to be present! got: ${JSON.stringify(functionName)}`) - const last = functionName[functionName.length - 1] - if(call.flavor === 'unnamed' && !last.line.endsWith(')')) { - functionName[0].line = `(${functionName[0].line}` - last.line += ')' + const last: PrettyPrintLinePart[] = functionName[functionName.length - 1].linePart + if(call.flavor === 'unnamed' && !prettyPrintPartToString(last, 0).endsWith(')')) { + functionName[0].linePart = [{ part: `(${prettyPrintPartToString(functionName[0].linePart, 0)}`, loc: functionName[0].linePart[0].loc}] + last.push({ part: ')', loc: last[last.length - 1].loc }) } - if(!last.line.endsWith('()')) { + if(!prettyPrintPartToString(last, 0).endsWith('()')) { // add empty call braces if not present - last.line += '()' + last.push({ part: '()', loc: last[last.length - 1].loc }) } return functionName ->>>>>>> upstream/main } else { - return plain(getLexeme(call), call.location.start) + return plain(getLexeme(call), getRangeStart(call.location)) } } @@ -708,11 +591,8 @@ export interface ReconstructionResult { linesWithAutoSelected: number } -<<<<<<< HEAD - -======= function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[], linesWithAutoSelected: number) { - if(result.length > 1 && result[0].line === '{' && result[result.length - 1].line === '}') { + if(result.length > 1 && prettyPrintPartToString(result[0].linePart, 0) === '{' && prettyPrintPartToString(result[result.length - 1].linePart, 0) === '}') { // remove outer block return { code: prettyPrintCodeToString(indentBy(result.slice(1, result.length - 1), -1)), linesWithAutoSelected } } else { @@ -753,4 +633,4 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel return removeOuterExpressionListIfApplicable(result, linesWithAutoSelected.size) } ->>>>>>> upstream/main +} \ No newline at end of file From 7f5afec2732846fca9122e87664fbba7fc40fada Mon Sep 17 00:00:00 2001 From: EagleoutIce Date: Fri, 24 May 2024 13:58:37 +0200 Subject: [PATCH 77/88] feat-fix: handle some of the problems introduced by the merge --- .../all/static-slicing/10-reconstruct.ts | 4 +- .../ast/model/nodes/info/r-delimiter.ts | 3 +- src/reconstruct/helper.ts | 12 +-- src/reconstruct/main.ts | 11 ++- src/reconstruct/reconstruct.ts | 97 +------------------ .../static-program-slices/simple-tests.ts | 19 +--- 6 files changed, 21 insertions(+), 125 deletions(-) diff --git a/src/core/steps/all/static-slicing/10-reconstruct.ts b/src/core/steps/all/static-slicing/10-reconstruct.ts index edda1f0cef..867e325140 100644 --- a/src/core/steps/all/static-slicing/10-reconstruct.ts +++ b/src/core/steps/all/static-slicing/10-reconstruct.ts @@ -3,9 +3,9 @@ import type { IPipelineStep } from '../../pipeline-step' import { PipelineStepStage } from '../../pipeline-step' import type { DeepReadonly } from 'ts-essentials' import type { SliceResult } from '../../../../slicing/static/slicer-types' -import type { AutoSelectPredicate } from '../../../../reconstruct/reconstruct' -import { reconstructToCode } from '../../../../reconstruct/reconstruct' import type { NormalizedAst } from '../../../../r-bridge/lang-4.x/ast/model/processing/decorate' +import {reconstructToCode} from "../../../../reconstruct/main" +import {AutoSelectPredicate} from "../../../../reconstruct/helper" export interface ReconstructRequiredInput { autoSelectIf?: AutoSelectPredicate diff --git a/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts b/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts index ef9bd90c74..163927ffec 100644 --- a/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts +++ b/src/r-bridge/lang-4.x/ast/model/nodes/info/r-delimiter.ts @@ -2,7 +2,6 @@ import type { RawRType, RType } from '../../type' import type { Location } from '../../model' import type { MergeableRecord } from '../../../../../../util/objects' -export type RDelimiterNode = RawRType.BraceLeft | RawRType.BraceRight | RawRType.ParenLeft | RawRType.ParenRight | RawRType.Semicolon /** * Combines '`{`', '`}`', '`(`', '`)`', and other delimiters used by R, they are ignored for most analysis * but helpful during reconstruction. @@ -10,5 +9,5 @@ export type RDelimiterNode = RawRType.BraceLeft | RawRType.BraceRight | RawRType export interface RDelimiter extends MergeableRecord, Location { readonly type: RType.Delimiter; readonly lexeme: string; - readonly subtype: RDelimiterNode; + readonly subtype: RawRType; } diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index b6da63aaac..2a2231ef6f 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -3,11 +3,11 @@ import type { ParentInformation } from '../r-bridge/lang-4.x/ast/model/processin import type { RNode } from '../r-bridge/lang-4.x/ast/model/model' import { RType } from '../r-bridge/lang-4.x/ast/model/type' import type { SourcePosition } from '../util/range' -//Is this depricated? -import type { ReconstructionConfiguration } from './reconstruct' +import {ReconstructionConfiguration} from "./reconstruct"; +//Is this deprecated? export type Code = PrettyPrintLine[] -export type Selection = Set +export type Selection = ReadonlySet export interface PrettyPrintLinePart { part: string loc: SourcePosition @@ -34,7 +34,7 @@ export function plainSplit(text: string, location: SourcePosition): Code { const printLine: PrettyPrintLine = { linePart: [], indent: 0 } let i = 0 let token = '' - let currLoc: SourcePosition = [location[0], location[1]] + let currLoc: SourcePosition = [location[0], location[1]] while(i < text.length) { if(text[i] === ' ') { if(!(token === '')) { @@ -190,7 +190,7 @@ function addSemis(code: Code): Code { if(heuristic.addedSemi) { heuristic.assignment = false } - + //check if the current element may be followed by a semicolon if(elem.part[elem.part.length - 1] === ')') { //closing brackets @@ -242,7 +242,7 @@ function addSemis(code: Code): Code { } } } - + //update the last character seen heuristic.lastChar = elem } diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index ad15d21864..69c0ee89d4 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,13 +1,14 @@ -import type { ReconstructionResult } from './reconstruct' -import { reconstructLogger, reconstructAstFolds } from './reconstruct' +import {reconstructLogger, ReconstructionResult, reconstructAstFolds } from './reconstruct' import { prettyPrintCodeToString, removeOuterExpressionListIfApplicable , autoSelectLibrary } from './helper' import type { AutoSelectPredicate , Selection } from './helper' -import type { NormalizedAst, RNode, ParentInformation } from '../r-bridge' -import { foldAstStateful } from '../r-bridge' import { LogLevel } from '../util/log' +import {RNode} from "../r-bridge/lang-4.x/ast/model/model"; +import {NormalizedAst, ParentInformation} from "../r-bridge/lang-4.x/ast/model/processing/decorate"; +import {foldAstStateful} from "../r-bridge/lang-4.x/ast/model/processing/stateful-fold"; + /** @@ -43,5 +44,5 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel reconstructLogger.trace('reconstructed ast before string conversion: ', JSON.stringify(result)) } - return { code: prettyPrintCodeToString(removeOuterExpressionListIfApplicable(result)), autoSelected } + return { code: prettyPrintCodeToString(removeOuterExpressionListIfApplicable(result)), linesWithAutoSelected: autoSelected } } diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 3ed919cd96..ddbe60902f 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -56,7 +56,6 @@ function reconstructExpressionList(exprList: RExpressionList, } if(subExpressions.length === 0) { -<<<<<<< HEAD return [] } else { const additionalTokens = reconstructAdditionalTokens(exprList) @@ -64,34 +63,6 @@ function reconstructExpressionList(exprList: RExpressionList, ...subExpressions, ...additionalTokens ) -======= - if(isSelected(config, exprList)) { - return plain('{}') - } else { - return [] - } - } else if(subExpressions.length === 1) { - if(!isSelected(config, exprList)) { - return subExpressions[0] - } - const [fst] = subExpressions - const g = exprList.grouping - - if(g && fst.length > 0) { - const start = g[0].content - const end = g[1].content - fst[0].line = `${start}${start === '{' ? ' ' : ''}${fst[0].line}` - fst[fst.length - 1].line = `${fst[fst.length - 1].line}${end === '}' ? ' ' : ''}${end}` - } - return fst - } else { - const g = exprList.grouping - return [ - ...(g ? plain(g[0].content) : plain('{')), - ...indentBy(subExpressions.flat(), 1), - ...(g ? plain(g[1].content) : plain('}')) - ] ->>>>>>> upstream/main } } @@ -113,42 +84,14 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: } } -<<<<<<< HEAD function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { return plain(getLexeme(n), n.lhs.location?.start ?? n.location.start) } -======= -function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, config: ReconstructionConfiguration): Code { ->>>>>>> upstream/main - if(lhs.length === 0 && rhs.length === 0) { - if(isSelected(config, n)) { - return plain(getLexeme(n)) - } else { - return [] - } - } else if(lhs.length === 0) { // if we have no lhs, only return rhs - return rhs -<<<<<<< HEAD - } - if(rhs.length === 0) { - // if we have no rhs we have to keep everything to get the rhs - return plain(getLexeme(n), n.lhs.location?.start ?? n.location.start) -======= - } else if(rhs.length === 0) { - if(isSelected(config, n)) { - return plain(getLexeme(n)) - } else { - return lhs - } ->>>>>>> upstream/main - } - return reconstructRawBinaryOperator(lhs, n.type === RType.Pipe ? '|>' : n.operator, rhs) } -<<<<<<< HEAD function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = loop.info.fullRange?.start //may be unnesseccary if(isSelected(configuration, loop)) { @@ -164,32 +107,6 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v if(variable.length === 0 && vector.length === 0 && body.length === 0) { return [] -======= -function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, config: ReconstructionConfiguration): Code { - if(!isSelected(config, loop) && variable.length === 0 && vector.length === 0) { - return body - } else if(body.length === 0 && variable.length === 0 && vector.length === 0) { - return [] - } else if(body.length <= 1) { - // 'inline' - return [{ - line: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) ${body.length === 0 ? '{}' : body[0].line}`, - indent: 0 - }] - } else if(body[0].line === '{' && body[body.length - 1].line === '}') { - // 'block' - return [ - { line: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)}) {`, indent: 0 }, - ...body.slice(1, body.length - 1), - { line: '}', indent: 0 } - ] - } else { - // unknown - return [ - { line: `for(${getLexeme(loop.variable)} in ${getLexeme(loop.vector)})`, indent: 0 }, - ...indentBy(body, 1) - ] ->>>>>>> upstream/main } const out = merge( @@ -251,15 +168,11 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } else { const additionalTokens = reconstructAdditionalTokens(loop) return merge( - [{ linePart: [{ part: 'repeat', loc: getRangeStart(loop.location) }], indent: 0 }], + [{linePart: [{part: 'repeat', loc: getRangeStart(loop.location)}], indent: 0}], body, ...additionalTokens ) - const sel = isSelected(configuration, loop) - if(!sel) { - return body } - return reconstructBodyWithHeader({ line: 'repeat', indent: 0 }, body, '{}') } //why is there pretty printing again?? @@ -380,7 +293,7 @@ function isNotEmptyArgument(a: Code | typeof EmptyArgument): a is Code { return a !== EmptyArgument } -function reconstructFoldAccess(node: RAccess, accessed: Code, access: readonly (Code | typeof EmptyArgument)[]): Code { +function reconstructFoldAccess(node: RAccess, accessed: Code, access: readonly (Code | typeof EmptyArgument)[], configuration: ReconstructionConfiguration): Code { const start = node.info.fullRange? getRangeStart(node.info.fullRange) : getRangeStart(node.location) @@ -410,7 +323,7 @@ function reconstructArgument(argument: RArgument, name: Code function reconstructParameter(parameter: RParameter, name: Code, defaultValue: unknown, configuration: ReconstructionConfiguration): Code { const start = getRangeStart(parameter.location) - + if(!isSelected(configuration, parameter)) { return [] } @@ -428,7 +341,7 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition p.length === 0)) { return merge(body) - } + } // if a definition is not selected, we only use the body - slicing will always select the definition if(functionParameters.every(p => p.length === 0)) { const empty = body === undefined || body.length === 0 @@ -633,4 +546,4 @@ export function reconstructToCode(ast: NormalizedAst, selection: Sel return removeOuterExpressionListIfApplicable(result, linesWithAutoSelected.size) } -} \ No newline at end of file +} diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index fd3641ad63..b6a2390ea2 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -43,18 +43,6 @@ while(i > 3) { } cat(x) `, ['6@x'], 'x <- 1\nwhile(i > 3) {\n x <- x * 2\n}\ncat(x)') - `, ['6@x'], 'x <- 1\nwhile(i > 3) x <- x * 2\nx') - - assertSliced(label('if-then', ['name-normal', 'if', 'newlines', 'numbers', 'unnamed-arguments', ...OperatorDatabase['<-'].capabilities, 'function-calls', ...OperatorDatabase['*'].capabilities, 'precedence']), - shell, ` -x <- 1 -if(i > 3) { - x <- x * 2 -} -cat(x) - `, ['6@x'], `x <- 1 -if(i > 3) { x <- x * 2 } -x`) assertSliced(label('independent if-then with extra requirements', ['name-normal', 'if', 'newlines', 'unnamed-arguments', 'numbers', ...OperatorDatabase['<-'].capabilities, 'function-calls', ...OperatorDatabase['*'].capabilities, 'precedence']), shell, ` @@ -162,8 +150,7 @@ w <- 7 N <- 10 for (i in 1:(N-1)) { sum <- sum + i + w -}` -for(i in 1:(N-1)) sum <- sum + i + w`, { +}`, { expectedOutput: 'Sum: 108\nProduct: 362880' } ) @@ -205,8 +192,6 @@ for (i in 1:(N-1)) { sum <- sum + i + w } cat("Sum:", sum, "\\n")` -for(i in 1:(N-1)) sum <- sum + i + w -sum` ) assertSliced(label('Product in call', capabilities), @@ -217,8 +202,6 @@ for (i in 1:(N-1)) { product <- product * i } cat("Product:", product, "\\n")` -for(i in 1:(N-1)) product <- product * i -product` ) assertSliced(label('Top by name', capabilities), From ec43a73227113f90605809920ca6faccb289ff2c Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 30 May 2024 22:58:11 +0200 Subject: [PATCH 78/88] feat(reconstruct): somehow there are no errors but its broken --- src/reconstruct/helper.ts | 6 +- src/reconstruct/reconstruct.ts | 88 +++--------------- .../slicing/reconstruct/function-tests.ts | 90 ++++++++++--------- .../static-program-slices/simple-tests.ts | 18 ---- 4 files changed, 63 insertions(+), 139 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 2a2231ef6f..06b90878e8 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -3,8 +3,7 @@ import type { ParentInformation } from '../r-bridge/lang-4.x/ast/model/processin import type { RNode } from '../r-bridge/lang-4.x/ast/model/model' import { RType } from '../r-bridge/lang-4.x/ast/model/type' import type { SourcePosition } from '../util/range' -import {ReconstructionConfiguration} from "./reconstruct"; -//Is this deprecated? +import {ReconstructionConfiguration} from "./reconstruct" export type Code = PrettyPrintLine[] export type Selection = ReadonlySet @@ -65,6 +64,9 @@ export function merge(...snipbits: Code[]): Code { //separate and group lineParts by lines for(const code of snipbits) { for(const line of code) { + if(line === undefined) { + continue + } for(const part of line.linePart) { const lineNumber = part.loc[0] if(buckets[lineNumber] === undefined) { //may be necessary as empty elements count as undefined and we don't want to reassign filled buckets diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index ddbe60902f..e9962ee57b 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -86,23 +86,24 @@ function reconstructUnaryOp(leaf: RNodeWithParent, operand: Code, configuration: function reconstructBinaryOp(n: RBinaryOp | RPipe, lhs: Code, rhs: Code, configuration: ReconstructionConfiguration): Code { if(isSelected(configuration, n)) { - return plain(getLexeme(n), n.lhs.location?.start ?? n.location.start) + const loc = (n.lhs.location? getRangeStart(n.lhs.location) : getRangeStart(n.location)) + return plain(getLexeme(n), loc) } return reconstructRawBinaryOperator(lhs, n.type === RType.Pipe ? '|>' : n.operator, rhs) } function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { - const start = loop.info.fullRange?.start //may be unnesseccary + const start = getRangeStart(loop.info.fullRange) //may be unnesseccary if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), start ?? loop.location.start) + return plain(getLexeme(loop), start ?? getRangeStart(loop.location)) } if(isSelected(configuration, loop.body)) { return merge(body) } const additionalTokens = reconstructAdditionalTokens(loop) - const vectorLocation: SourcePosition = loop.vector.location? loop.vector.location.start : vector[0].linePart[0].loc - vectorLocation.column -= 1 //somehow the vector is consistently one space to late + const vectorLocation: SourcePosition = loop.vector.location? getRangeStart(loop.vector.location) : vector[0].linePart[0].loc + vectorLocation[1] -= 1 //somehow the vector is consistently one space to late const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) if(variable.length === 0 && vector.length === 0 && body.length === 0) { @@ -202,59 +203,16 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi //console.log('we have an else-body') const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc - const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: { line: elsePos.line, column: elsePos.column + 2 } }], indent: 0 }] : [] + const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: [elsePos[0], elsePos[1] + 2] as SourcePosition }], indent: 0 }] : ([] as Code) const elseOffset = when.length === 0 ? 4 : 0 out = merge( out, fakeWhenBlock, - [{ linePart: [{ part: 'else', loc: { line: elsePos.line, column: elsePos.column + 2 +elseOffset } }], indent: 0 }], //may have to change the location + [{ linePart: [{ part: 'else', loc: [elsePos[0], elsePos[1] + 2 +elseOffset] }], indent: 0 }], //may have to change the location otherwise ) - /*function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, then: Code, otherwise: Code | undefined, config: ReconstructionConfiguration): Code { - otherwise ??= [] - if(then.length === 0 && otherwise.length === 0) { - if(isSelected(config, ifThenElse)) { - return [{ line: `if(${getLexeme(ifThenElse.condition)}) { }`, indent: 0 }] - } else if(condition.length > 0) { - return condition - } else { - return [] - } - } else if(otherwise.length === 0) { - if(isSelected(config, ifThenElse)) { - return reconstructBodyWithHeader( - { line: `if(${getLexeme(ifThenElse.condition)})`, indent: 0 }, - then, '{}' - ) - } else { - return then - } - } else if(then.length === 0) { - if(isSelected(config, ifThenElse)) { - return reconstructBodyWithHeader( - { line: `if(${getLexeme(ifThenElse.condition)}) { } else`, indent: 0 }, - then, '{}' - ) - } else { - return otherwise - } - } else { - const thenRemainder = indentBy(then.slice(1), 1) - if(thenRemainder.length > 0) { - if(!thenRemainder[thenRemainder.length - 1].line.trim().endsWith('else')) { - thenRemainder[thenRemainder.length - 1].line += ' else ' - } - } - return [ - { line: `if(${getLexeme(ifThenElse.condition)}) ${then[0].line} ${then.length === 1 ? 'else' : ''}`, indent: 0 }, - ...thenRemainder, - { line: `${otherwise[0].line}`, indent: 0 }, - ...indentBy(otherwise.splice(1), 1) - ] } - */ return out - } } function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { @@ -339,34 +297,11 @@ function reconstructParameter(parameter: RParameter, name: Co //what are we doing here?? function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: readonly Code[], body: Code, config: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition - if(!isSelected(configuration, definition) && functionParameters.every(p => p.length === 0)) { + if(!isSelected(config, definition) && functionParameters.every(p => p.length === 0)) { return merge(body) } - // if a definition is not selected, we only use the body - slicing will always select the definition - if(functionParameters.every(p => p.length === 0)) { - const empty = body === undefined || body.length === 0 - const selected = isSelected(config, definition) - if(empty && selected) { // give function stub - return plain(`function(${reconstructParameters(definition.parameters).join(', ')}) { }`) - } else if(!selected) { // do not require function - return body - } - } - const parameters = reconstructParameters(definition.parameters).join(', ') - if(body.length <= 1) { - // 'inline' - const bodyStr = body.length === 0 ? '{ }' : `${body[0].line}` - // we keep the braces in every case because I do not like no-brace functions - return [{ line: `function(${parameters}) ${bodyStr}`, indent: 0 }] - } else { - // 'block' - return [ - { line: `function(${parameters}) ${body[0].line}`, indent: 0 }, - ...body.slice(1), - ] - } - if(isSelected(configuration, definition.body)) { + if(isSelected(config, definition.body)) { const out = merge(body) const h = out[out.length - 1].linePart if(h[h.length - 1].part === ';') { @@ -375,7 +310,7 @@ function reconstructFunctionDefinition(definition: RFunctionDefinition(ast: NormalizedAst, selection: Sel return removeOuterExpressionListIfApplicable(result, linesWithAutoSelected.size) } -} diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 2c956e679a..1a3fc3e2e5 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -2,22 +2,23 @@ import { assert } from 'chai' import type { Code, PrettyPrintLinePart } from '../../../../src/reconstruct/helper' import { plain, merge, prettyPrintCodeToString, prettyPrintPartToString, plainSplit } from '../../../../src/reconstruct/helper' import { jsonReplacer } from '../../../../src/util/json' +import { SourcePosition } from '../../../../src/util/range' describe('Functions Reconstruct', () => { describe('plain', () => { function positive(input: string, line: number, column: number, expected: Code) { it(`${input} for ${line}:${column}`, () => { - const result:Code = plain(input, { line,column }) + const result:Code = plain(input, [line,column] ) assert.deepStrictEqual(result,expected) }) } for(const testCase of [ - { input: 'Hello', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } }],indent: 0 }] }, - { input: 'Hello World', line: 4, column: 3, expected: [{ linePart: [{ part: 'Hello World', loc: { line: 4, column: 3 } }],indent: 0 }] }, - { input: 'Hello\nWorld', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: 'World', loc: { line: 2, column: 1 } }],indent: 0 }] }, - { input: 'Hello\nWorld', line: 3, column: 4, expected: [{ linePart: [{ part: 'Hello', loc: { line: 3, column: 4 } },{ part: 'World', loc: { line: 4, column: 4 } }],indent: 0 }] }, - { input: 'Hello\n World\n24', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: ' World', loc: { line: 2, column: 1 } },{ part: '24', loc: { line: 3, column: 1 } }],indent: 0 }] } + { input: 'Hello', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc:[1, 1] }],indent: 0 }] as Code}, + { input: 'Hello World', line: 4, column: 3, expected: [{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }] as Code}, + { input: 'Hello\nWorld', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [2, 1] }],indent: 0 }] as Code}, + { input: 'Hello\nWorld', line: 3, column: 4, expected: [{ linePart: [{ part: 'Hello', loc: [3, 4] },{ part: 'World', loc: [4, 4] }],indent: 0 }] as Code}, + { input: 'Hello\n World\n24', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: ' World', loc: [2, 1] },{ part: '24', loc: [3, 1] }],indent: 0 }] as Code } ]) { positive(testCase.input, testCase.line, testCase.column, testCase.expected) } @@ -31,39 +32,41 @@ describe('Functions Reconstruct', () => { } describe('single line merge', () => { for(const testCase of [ - { snipbit: [[{ linePart: [{ part: 'Hello World', loc: { line: 4, column: 3 } }],indent: 0 }]], expected: [{ linePart: [{ part: 'Hello World', loc: { line: 4, column: 3 } }],indent: 0 }] }, - { snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: 'World', loc: { line: 1, column: 1 } }],indent: 0 }]], expected: [{ linePart: [{ part: 'Hello', loc: { line: 1, column: 1 } },{ part: 'World', loc: { line: 1, column: 1 } }],indent: 0 }] } + { snipbit: [[{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }]] as [Code], expected: [{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }] as Code}, + { snipbit: [[{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [1, 1] }],indent: 0 }]] as [Code], expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [1, 1] }],indent: 0 }] as Code} ]) { positive(testCase.snipbit, testCase.expected) } }) describe('two lines merge', () => { + const helloLocation = [0, 0] as SourcePosition + const worldLocation = [1, 0] as SourcePosition + const numLocation = [1, 7] as SourcePosition const testCases = [ - //Case 1 (in order) { - snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }],[{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 }], [{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }]] as [Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }] }, { - snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'Hello', loc: helloLocation }, { part: 'World', loc: worldLocation }], indent: 0 }]] as [Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }] }, { - snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 1, column: 7 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }, { part: '24', loc: { line: 1, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'Hello', loc: helloLocation }, { part: 'World', loc: worldLocation }], indent: 0 }],[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }]] as [Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }, { part: '24', loc: numLocation }], indent: 0 }] }, //Case 2 (out of order) { - snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }],[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }],[{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 }]] as [Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }] }, { - snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }, { part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'World', loc: worldLocation }, { part: 'Hello', loc: helloLocation }], indent: 0 }]] as [Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }] }, { - snipbit: [[{ linePart: [{ part: '24', loc: { line: 1, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 0 } }, { part: '24', loc: { line: 1, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: helloLocation }, { part: 'World', loc: worldLocation }], indent: 0 }]] as [Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }, { part: '24', loc: numLocation }], indent: 0 }] } ] for(const testCase of testCases) { @@ -71,36 +74,39 @@ describe('Functions Reconstruct', () => { } }) describe('three lines merge', () => { + const helloLocation = [0, 0] as SourcePosition + const worldLocation = [1, 7] as SourcePosition + const numLocation = [2, 7] as SourcePosition const testCases = [ //Case 1 (in order) 123 { - snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'Hello', loc: helloLocation }, { part: 'World', loc: worldLocation }], indent: 0 }],[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }]] as [Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 },{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }] }, //Case 2 (2nd Line out of order) 213 { - snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }, { part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'World', loc: worldLocation }, { part: 'Hello', loc: helloLocation }], indent: 0 }],[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }]] as [Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 },{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }] }, //Case 3 (3rd Line out of order) 231 { - snipbit: [[{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }],[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }],[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 }]] as [Code, Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 },{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }] }, //Case 4 (reverse order) 321 { - snipbit: [[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }],[{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }],[{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 }]] as [Code, Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 },{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }] }, //Case 5 () 312 { - snipbit: [[{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }], [{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }], [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 }], [{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }]] as [Code, Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 },{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }] }, //Case 6 () 132 { - snipbit: [[{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 }], [{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }], [{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 }]], - expected: [{ linePart: [{ part: 'Hello', loc: { line: 0, column: 0 } }], indent: 0 },{ linePart: [{ part: 'World', loc: { line: 1, column: 7 } }], indent: 0 },{ linePart: [{ part: '24', loc: { line: 2, column: 7 } }], indent: 0 }] + snipbit: [[{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 }], [{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }], [{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 }]] as [Code, Code, Code], + expected: [{ linePart: [{ part: 'Hello', loc: helloLocation }], indent: 0 },{ linePart: [{ part: 'World', loc: worldLocation }], indent: 0 },{ linePart: [{ part: '24', loc: numLocation }], indent: 0 }] } ] for(const testCase of testCases) { @@ -112,17 +118,17 @@ describe('Functions Reconstruct', () => { const testCase = [] as Code[] const partPool = ['Hello', 'World', 'FlowR', 'Is', 'Incredible'] for(let i = 0; i < lines; i++) { - const line: Code = [{ linePart: [{ part: partPool[Math.random() % 5], loc: { line: Math.random() % lines, column: Math.random() % 15 } }], indent: 0 }] + const line: Code = [{ linePart: [{ part: partPool[Math.random() % 5], loc: [Math.random() % lines, Math.random() % 15] }], indent: 0 }] testCase.push(line) } return testCase } function checkTestCase(code: Code): boolean { - let currentLoc = { line: 0, column: 0 } + let currentLoc = [0, 0] as SourcePosition for(const line of code) { for(const part of line.linePart) { const nextLoc = part.loc - if(currentLoc.line <= nextLoc.line && currentLoc.column <= nextLoc.column) { + if(currentLoc[0] <= nextLoc[0] && currentLoc[1] <= nextLoc[1]) { currentLoc = nextLoc } else { return false @@ -151,11 +157,11 @@ describe('Functions Reconstruct', () => { }) } for(const testCase of [ - { input: [{ part: 'Hello', loc: { line: 0, column: 0 } }],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0 }, - { input: [{ part: 'Hello World', loc: { line: 0, column: 0 } }],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0 }, - { input: [{ part: 'Hello', loc: { line: 0, column: 0 } }, { part: 'World', loc: { line: 0, column: 6 } }],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0 }, - { input: [{ part: 'Hello', loc: { line: 0, column: 6 } }],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0 }, - { input: [{ part: 'World', loc: { line: 0, column: 6 } },{ part: 'Hello', loc: { line: 0, column: 0 } }],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0 }, + { input: [{ part: 'Hello', loc: [0, 0] }] as [PrettyPrintLinePart],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0 }, + { input: [{ part: 'Hello World', loc: [0, 0] }] as [PrettyPrintLinePart],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0 }, + { input: [{ part: 'Hello', loc: [0, 0] }, { part: 'World', loc: [0, 6] }] as [PrettyPrintLinePart, PrettyPrintLinePart],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0 }, + { input: [{ part: 'Hello', loc: [0, 6] }] as [PrettyPrintLinePart],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0 }, + { input: [{ part: 'World', loc: [0, 6] },{ part: 'Hello', loc: [0, 0] }] as [PrettyPrintLinePart, PrettyPrintLinePart],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0 }, ]) { positive(testCase.input, testCase.expected, testCase.msg, testCase.columnOffset) } @@ -163,7 +169,7 @@ describe('Functions Reconstruct', () => { describe('semicolon reconstruction', () => { function positive(input: string, expected: string, msg: string) { it(`Add semicolons to ${JSON.stringify(input, jsonReplacer)}`, () => { - const convertedInput = plainSplit(input, { line: 1, column: 1 }) + const convertedInput = plainSplit(input, [1, 1]) const out = prettyPrintCodeToString(convertedInput) assert.strictEqual(out, expected, msg) }) diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index b6a2390ea2..2166c7c154 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -75,15 +75,6 @@ x`, { assertSliced('Repeated named access and definition', shell, code, ['6@a'], 'a <- list(1,2)\na[[1]] = 2\na[[2]] = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, code, ['8@a'], `a <- list(3,4) cat(a)`) - const code = ` -a <- list(1,2) -a[[1]] = 2 -a[[2]] = 3 -b[[4]] = 5 -cat(a) -a <- list(3,4) -cat(a) -` assertSliced(label('Repeated named access and definition', ['name-normal', 'numbers', 'double-bracket-access', 'unnamed-arguments', 'function-calls', ...OperatorDatabase['<-'].capabilities, 'newlines', 'unnamed-arguments']), shell, code, ['6@a'], `a <- list(1,2) a[[1]] = 2 @@ -99,15 +90,6 @@ a`) assertSliced('Repeated named access and definition', shell, codeB, ['6@a'], 'a <- list(a=1,b=2)\na$a = 2\na$b = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, codeB, ['8@a'], `a <- list(a=3,b=4) cat(a)`) - const codeB = ` -a <- list(a=1,b=2) -a$a = 2 -a$b = 3 -b[[4]] = 5 -cat(a) -a <- list(a=3,b=4) -cat(a) -` assertSliced(label('Repeated named access and definition', ['name-normal', 'function-calls', 'named-arguments', 'unnamed-arguments', 'dollar-access', ...OperatorDatabase['<-'].capabilities, 'numbers']), shell, codeB, ['6@a'], `a <- list(a=1,b=2) a$a = 2 From 57349a6e462533fb0620e89464d90fa2d2fbcdb2 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 30 May 2024 23:12:08 +0200 Subject: [PATCH 79/88] lint-fix(reconstruct): now the linter doesnt scream --- .../all/static-slicing/10-reconstruct.ts | 4 +-- src/reconstruct/helper.ts | 2 +- src/reconstruct/main.ts | 9 +++--- src/reconstruct/reconstruct.ts | 31 +++---------------- .../slicing/reconstruct/function-tests.ts | 14 ++++----- .../static-program-slices/simple-tests.ts | 4 +-- 6 files changed, 22 insertions(+), 42 deletions(-) diff --git a/src/core/steps/all/static-slicing/10-reconstruct.ts b/src/core/steps/all/static-slicing/10-reconstruct.ts index 867e325140..f7c9b26610 100644 --- a/src/core/steps/all/static-slicing/10-reconstruct.ts +++ b/src/core/steps/all/static-slicing/10-reconstruct.ts @@ -4,8 +4,8 @@ import { PipelineStepStage } from '../../pipeline-step' import type { DeepReadonly } from 'ts-essentials' import type { SliceResult } from '../../../../slicing/static/slicer-types' import type { NormalizedAst } from '../../../../r-bridge/lang-4.x/ast/model/processing/decorate' -import {reconstructToCode} from "../../../../reconstruct/main" -import {AutoSelectPredicate} from "../../../../reconstruct/helper" +import { reconstructToCode } from '../../../../reconstruct/main' +import type { AutoSelectPredicate } from '../../../../reconstruct/helper' export interface ReconstructRequiredInput { autoSelectIf?: AutoSelectPredicate diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 06b90878e8..a9137b668a 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -3,7 +3,7 @@ import type { ParentInformation } from '../r-bridge/lang-4.x/ast/model/processin import type { RNode } from '../r-bridge/lang-4.x/ast/model/model' import { RType } from '../r-bridge/lang-4.x/ast/model/type' import type { SourcePosition } from '../util/range' -import {ReconstructionConfiguration} from "./reconstruct" +import type { ReconstructionConfiguration } from './reconstruct' export type Code = PrettyPrintLine[] export type Selection = ReadonlySet diff --git a/src/reconstruct/main.ts b/src/reconstruct/main.ts index 69c0ee89d4..5cc6dcc9cc 100644 --- a/src/reconstruct/main.ts +++ b/src/reconstruct/main.ts @@ -1,13 +1,14 @@ -import {reconstructLogger, ReconstructionResult, reconstructAstFolds } from './reconstruct' +import { reconstructLogger, reconstructAstFolds } from './reconstruct' +import type { ReconstructionResult } from './reconstruct' import { prettyPrintCodeToString, removeOuterExpressionListIfApplicable , autoSelectLibrary } from './helper' import type { AutoSelectPredicate , Selection } from './helper' import { LogLevel } from '../util/log' -import {RNode} from "../r-bridge/lang-4.x/ast/model/model"; -import {NormalizedAst, ParentInformation} from "../r-bridge/lang-4.x/ast/model/processing/decorate"; -import {foldAstStateful} from "../r-bridge/lang-4.x/ast/model/processing/stateful-fold"; +import type { RNode } from '../r-bridge/lang-4.x/ast/model/model' +import type { NormalizedAst, ParentInformation } from '../r-bridge/lang-4.x/ast/model/processing/decorate' +import { foldAstStateful } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index e9962ee57b..edd4ec9f75 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -30,7 +30,8 @@ import type { RFunctionDefinition } from '../r-bridge/lang-4.x/ast/model/nodes/r import type { StatefulFoldFunctions } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' import { foldAstStateful } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id' -import { getRangeStart, SourcePosition, SourceRange } from '../util/range' +import { getRangeStart } from '../util/range' +import type { SourcePosition, SourceRange } from '../util/range' import { autoSelectLibrary, getIndentString, indentBy, isSelected, merge, plain, prettyPrintCodeToString, prettyPrintPartToString } from './helper' import type { AutoSelectPredicate, Code, PrettyPrintLine, PrettyPrintLinePart } from './helper' @@ -132,33 +133,11 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v //add heuristic to select needed semicollons //maybe if expr 1,5 => select next semicollon function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { - let out: Code[] = node.info.additionalTokens?.filter(t => t.lexeme && t.location) + const out: Code[] = node.info.additionalTokens?.filter(t => t.lexeme && t.location) .map(t => plain(t.lexeme as string, getRangeStart((t.location? t.location : [0, 0]) as SourceRange)) ?? ([] as Code[])).filter(t => !(t === undefined)) as Code[] return out } -function reconstructBodyWithHeader(header: PrettyPrintLine, body: Code, onEmpty: string): Code { - const h = body[body.length - 1].linePart - if(body.length === 0) { - return [{ linePart: [{ part: `${prettyPrintPartToString(header.linePart, 0)}${onEmpty}`, loc: header.linePart[0].loc }], indent: header.indent }] - } else if(body.length === 1) { - return [ - { linePart: [{ part: `${prettyPrintPartToString(header.linePart, 0)} ${body[0].linePart}`, loc: header.linePart[0].loc }], indent: header.indent } - ] - } else if(body[0].linePart[0].part === '{' && h[h.length - 1].part === '}') { - return [ - { linePart: [{ part: `${prettyPrintPartToString(header.linePart, 0)} {`, loc: header.linePart[0].loc }], indent: header.indent }, - ...body.slice(1, body.length - 1), - { linePart: [{ part: '}', loc: header.linePart[header.linePart.length - 1].loc }], indent: header.indent } - ] - } else { - return [ - header, - ...indentBy(body, 1) - ] - } -} - //what is happening here??? function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { @@ -169,7 +148,7 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } else { const additionalTokens = reconstructAdditionalTokens(loop) return merge( - [{linePart: [{part: 'repeat', loc: getRangeStart(loop.location)}], indent: 0}], + [{ linePart: [{ part: 'repeat', loc: getRangeStart(loop.location) }], indent: 0 }], body, ...additionalTokens ) @@ -376,7 +355,7 @@ function reconstructFunctionCall(call: RFunctionCall, functio guard(functionName.length > 0, `without args, we need the function name to be present! got: ${JSON.stringify(functionName)}`) const last: PrettyPrintLinePart[] = functionName[functionName.length - 1].linePart if(call.flavor === 'unnamed' && !prettyPrintPartToString(last, 0).endsWith(')')) { - functionName[0].linePart = [{ part: `(${prettyPrintPartToString(functionName[0].linePart, 0)}`, loc: functionName[0].linePart[0].loc}] + functionName[0].linePart = [{ part: `(${prettyPrintPartToString(functionName[0].linePart, 0)}`, loc: functionName[0].linePart[0].loc }] last.push({ part: ')', loc: last[last.length - 1].loc }) } diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 1a3fc3e2e5..407bed011e 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -2,7 +2,7 @@ import { assert } from 'chai' import type { Code, PrettyPrintLinePart } from '../../../../src/reconstruct/helper' import { plain, merge, prettyPrintCodeToString, prettyPrintPartToString, plainSplit } from '../../../../src/reconstruct/helper' import { jsonReplacer } from '../../../../src/util/json' -import { SourcePosition } from '../../../../src/util/range' +import type { SourcePosition } from '../../../../src/util/range' describe('Functions Reconstruct', () => { describe('plain', () => { @@ -14,10 +14,10 @@ describe('Functions Reconstruct', () => { } for(const testCase of [ - { input: 'Hello', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc:[1, 1] }],indent: 0 }] as Code}, - { input: 'Hello World', line: 4, column: 3, expected: [{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }] as Code}, - { input: 'Hello\nWorld', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [2, 1] }],indent: 0 }] as Code}, - { input: 'Hello\nWorld', line: 3, column: 4, expected: [{ linePart: [{ part: 'Hello', loc: [3, 4] },{ part: 'World', loc: [4, 4] }],indent: 0 }] as Code}, + { input: 'Hello', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] }],indent: 0 }] as Code }, + { input: 'Hello World', line: 4, column: 3, expected: [{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }] as Code }, + { input: 'Hello\nWorld', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [2, 1] }],indent: 0 }] as Code }, + { input: 'Hello\nWorld', line: 3, column: 4, expected: [{ linePart: [{ part: 'Hello', loc: [3, 4] },{ part: 'World', loc: [4, 4] }],indent: 0 }] as Code }, { input: 'Hello\n World\n24', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: ' World', loc: [2, 1] },{ part: '24', loc: [3, 1] }],indent: 0 }] as Code } ]) { positive(testCase.input, testCase.line, testCase.column, testCase.expected) @@ -32,8 +32,8 @@ describe('Functions Reconstruct', () => { } describe('single line merge', () => { for(const testCase of [ - { snipbit: [[{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }]] as [Code], expected: [{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }] as Code}, - { snipbit: [[{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [1, 1] }],indent: 0 }]] as [Code], expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [1, 1] }],indent: 0 }] as Code} + { snipbit: [[{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }]] as [Code], expected: [{ linePart: [{ part: 'Hello World', loc: [4, 3] }],indent: 0 }] as Code }, + { snipbit: [[{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [1, 1] }],indent: 0 }]] as [Code], expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: 'World', loc: [1, 1] }],indent: 0 }] as Code } ]) { positive(testCase.snipbit, testCase.expected) } diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index 2166c7c154..f5dcd72518 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -33,8 +33,8 @@ cat(x) assertSliced('While-Loop', shell, ` print(x) `, ['6@x'], 'x <- 1\nfor(i in 1:10) x <- x * 2\nx', { - expectedOutput: '[1] 1024' - }) + expectedOutput: '[1] 1024' + }) assertSliced(label('While-Loop', ['name-normal', 'while-loop', 'newlines', 'numbers', 'unnamed-arguments', ...OperatorDatabase['<-'].capabilities, 'function-calls', ...OperatorDatabase['*'].capabilities, 'precedence']), shell, ` x <- 1 From c4cac5450f98021f86c74e4e28cc47c79ceace90 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 31 May 2024 10:43:36 +0200 Subject: [PATCH 80/88] feat-fix(reconstruct): fixed access to undefined --- src/reconstruct/reconstruct.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index edd4ec9f75..bfcca5ce23 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -48,7 +48,7 @@ const reconstructAsLeaf = (leaf: RNodeWithParent, configuration: ReconstructionC const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location? getRangeStart(n.location) : [0, 0]) -function reconstructExpressionList(exprList: RExpressionList, _grouping: [Code, Code] | undefined, expressions: Code[], config: ReconstructionConfiguration): Code { +function reconstructExpressionList(exprList: RExpressionList, grouping: [Code, Code] | undefined, expressions: Code[], config: ReconstructionConfiguration): Code { const subExpressions = expressions.filter(e => e.length > 0) if(isSelected(config, exprList)) { @@ -62,12 +62,14 @@ function reconstructExpressionList(exprList: RExpressionList, const additionalTokens = reconstructAdditionalTokens(exprList) return merge( ...subExpressions, - ...additionalTokens + ...additionalTokens, + ...(grouping ?? []) ) } } function reconstructRawBinaryOperator(lhs: Code, n: string, rhs: Code): Code { + console.log(lhs, n, rhs) return [ // inline pretty print ...lhs.slice(0, lhs.length - 1), { linePart: [{ part: `${prettyPrintCodeToString([lhs[lhs.length - 1]])} ${n} ${prettyPrintCodeToString([rhs[0]])}`, loc: lhs[lhs.length - 1].linePart[lhs.length - 1].loc }], indent: 0 }, @@ -91,7 +93,17 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe' : n.operator, rhs) + if(lhs.length === 0) { + if(rhs.length === 0) { + return [] + } + return rhs + } else { + if(rhs.length === 0) { + return lhs + } + return reconstructRawBinaryOperator(lhs, n.type === RType.Pipe ? '|>' : n.operator, rhs) + } } function reconstructForLoop(loop: RForLoop, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { @@ -133,9 +145,9 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v //add heuristic to select needed semicollons //maybe if expr 1,5 => select next semicollon function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { - const out: Code[] = node.info.additionalTokens?.filter(t => t.lexeme && t.location) - .map(t => plain(t.lexeme as string, getRangeStart((t.location? t.location : [0, 0]) as SourceRange)) ?? ([] as Code[])).filter(t => !(t === undefined)) as Code[] - return out + const out = node.info.additionalTokens?.filter(t => t.lexeme && t.location) + .map(t => plain(t.lexeme as string, getRangeStart((t.location? t.location : [0, 0]) as SourceRange)) ?? ([] as Code[])).filter(t => !(t === undefined)) + return out ?? [] } From 31325803478274b710548762928e995eb93ef0cc Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:05:15 +0200 Subject: [PATCH 81/88] feat(reconstruct): some minor quality improvements --- src/reconstruct/helper.ts | 43 ++++++++++++++++++---------------- src/reconstruct/reconstruct.ts | 9 ++++--- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index a9137b668a..675c03c970 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -93,6 +93,9 @@ export function prettyPrintPartToString(line: PrettyPrintLinePart[],columnOffset if(line.length === 0) { return '' } + if(line.length === 1) { + return /*' '.repeat(Math.max(columnOffset - 1, 0)) + */line[0].part + } line.sort((a, b) => a.loc[1] - b.loc[1]) let result = '' for(const part of line) { @@ -162,8 +165,10 @@ function addSemis(code: Code): Code { } return false } + const line: PrettyPrintLinePart[][] = [] - const specialChar = ['+', '-', '*', '/', ':', '<-', '->', '<<-', '->>', '$', '$$', '&', '&&', '||', '?', '<', '>', '=', '<=', '>=', '==', '(', ')', '{', '}', '[', '[[', ']', ']]'] + const specialChar = ['+', '-', '*', '/', ':', '<-', '->', '<<-', '->>', '$', '$$', '&', '&&', '||', '?', '<', '>', '=', '<=', '>=', '==', '(', ')', '((', '))', '{', '}', '[', '[[', ']', ']]', 'for', ' in '] + //find a way to make this work with merge, as this is a very similar piece of code for(const elem of code) { let currLine = 1 for(const linePart of elem.linePart) { @@ -175,10 +180,12 @@ function addSemis(code: Code): Code { } } + //iterate through all elements of the code piece to search for places for semicolons for(const lineElements of line) { if(lineElements === undefined) { continue } + //create a heuristic to store information about the current search const heuristic = { assignment: false, brackets: false, lastChar: lineElements[0], statement: false, addedSemi: false, curlyBrackets: false } let possibleSemi = heuristic.lastChar.loc lineElements.splice(0, 1) @@ -194,54 +201,42 @@ function addSemis(code: Code): Code { } //check if the current element may be followed by a semicolon - if(elem.part[elem.part.length - 1] === ')') { + if((elem.loc[1] - (heuristic.lastChar.loc[1] + heuristic.lastChar.part.length)) >= 1) { //closing brackets possibleSemi = updateSemi(possibleSemi, heuristic) } else if(elem.part[elem.part.length - 1] === '}') { //closing curlyBrackets possibleSemi = updateSemi(possibleSemi, heuristic) - } else if((elem.loc[1] - (heuristic.lastChar.loc[1] + heuristic.lastChar.part.length)) >= 1) { + } else if(elem.part[elem.part.length - 1] === ')') { //large space possibleSemi = updateSemi(possibleSemi, heuristic) } - + //checking condishions for adding semicolons if((elem.part === '<-') || (elem.part === '->') || (elem.part === '<<-') || (elem.part === '->>')) { //check for assignments if(heuristic.assignment) { - if(!heuristic.addedSemi) { - code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) - heuristic.addedSemi = true - } + pushSemi(heuristic, possibleSemi) } heuristic.assignment = !heuristic.assignment } else if(elem.part[0] === '(') { //check for brackets heuristic.assignment = false if(heuristic.brackets) { - if(!heuristic.addedSemi) { - code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) - heuristic.addedSemi = true - } + pushSemi(heuristic, possibleSemi) heuristic.brackets = false } } else if(elem.part[0] === '{') { //check for curlyBrackets heuristic.assignment = false if(heuristic.curlyBrackets) { - if(!heuristic.addedSemi) { - code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) - heuristic.addedSemi = true - } + pushSemi(heuristic, possibleSemi) heuristic.curlyBrackets = false } } else if(!contains(specialChar, elem.part)) { //check for two consecutive statements if(heuristic.statement) { - if(!heuristic.addedSemi) { - code.push({ linePart: [ { part: ';', loc: possibleSemi } ], indent: 0 }) - heuristic.addedSemi = true - } + pushSemi(heuristic, possibleSemi) } } @@ -252,6 +247,13 @@ function addSemis(code: Code): Code { code = merge(code) return code + function pushSemi(heuristic: { assignment: boolean; brackets: boolean; lastChar: PrettyPrintLinePart; statement: boolean; addedSemi: boolean; curlyBrackets: boolean }, possibleSemi: SourcePosition) { + if(!heuristic.addedSemi) { + code.push({ linePart: [{ part: ';', loc: possibleSemi }], indent: 0 }) + heuristic.addedSemi = true + } + } + function updateSemi(possibleSemi: SourcePosition, heuristic: { assignment: boolean; brackets: boolean; lastChar: PrettyPrintLinePart; statement: boolean; addedSemi: boolean; curlyBrackets: boolean }) { const lastSemi: SourcePosition = [possibleSemi[0], possibleSemi[1]] const other: SourcePosition = [heuristic.lastChar.loc[0], heuristic.lastChar.loc[1] + heuristic.lastChar.part.length] @@ -261,6 +263,7 @@ function addSemis(code: Code): Code { } } + export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge(code) code = addSemis(code) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index bfcca5ce23..dc463d7252 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -30,7 +30,7 @@ import type { RFunctionDefinition } from '../r-bridge/lang-4.x/ast/model/nodes/r import type { StatefulFoldFunctions } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' import { foldAstStateful } from '../r-bridge/lang-4.x/ast/model/processing/stateful-fold' import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id' -import { getRangeStart } from '../util/range' +import { getRangeEnd, getRangeStart } from '../util/range' import type { SourcePosition, SourceRange } from '../util/range' import { autoSelectLibrary, getIndentString, indentBy, isSelected, merge, plain, prettyPrintCodeToString, prettyPrintPartToString } from './helper' import type { AutoSelectPredicate, Code, PrettyPrintLine, PrettyPrintLinePart } from './helper' @@ -123,10 +123,14 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v return [] } + const startLoc = start ?? getRangeStart(loop.location) const out = merge( - [{ linePart: [{ part: 'for', loc: start ?? getRangeStart(loop.location) }], indent: 0 }], + [{ linePart: [{ part: 'for', loc: startLoc }], indent: 0 }], + [{ linePart: [{ part: '(', loc: [startLoc[0], startLoc[1] + 3] }], indent: 0 }], [{ linePart: [{ part: getLexeme(loop.variable), loc: getRangeStart(loop.variable.location) }], indent: 0 }], + [{ linePart: [{ part: ' in ', loc: getRangeEnd(loop.variable.location) }], indent: 0 }], reconstructedVector, + [{ linePart: [{ part: ')', loc: [vectorLocation[0], vectorLocation[1] + prettyPrintCodeToString(reconstructedVector).length] }], indent: 0 }], ...additionalTokens ) //if body empty @@ -285,7 +289,6 @@ function reconstructParameter(parameter: RParameter, name: Co } } -//what are we doing here?? function reconstructFunctionDefinition(definition: RFunctionDefinition, functionParameters: readonly Code[], body: Code, config: ReconstructionConfiguration): Code { // if a definition is not selected, we only use the body - slicing will always select the definition if(!isSelected(config, definition) && functionParameters.every(p => p.length === 0)) { From 36565d0da7472b175ffc14db6269ec8fd70e901c Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:49:45 +0200 Subject: [PATCH 82/88] tests-fix(reconstruct): started fixing test --- src/reconstruct/reconstruct.ts | 19 +++++++- .../slicing/reconstruct/function-tests.ts | 2 +- .../slicing/reconstruct/simple-tests.ts | 43 ++++++++++++++----- .../static-program-slices/simple-tests.ts | 2 +- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index dc463d7252..e26f739b84 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -34,6 +34,7 @@ import { getRangeEnd, getRangeStart } from '../util/range' import type { SourcePosition, SourceRange } from '../util/range' import { autoSelectLibrary, getIndentString, indentBy, isSelected, merge, plain, prettyPrintCodeToString, prettyPrintPartToString } from './helper' import type { AutoSelectPredicate, Code, PrettyPrintLine, PrettyPrintLinePart } from './helper' +import { jsonReplacer } from '../util/json' type Selection = ReadonlySet @@ -157,7 +158,20 @@ function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { //what is happening here??? function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { + console.log(`${JSON.stringify(configuration, jsonReplacer)}\n${loop.info.id}\n${loop.body.info.id}`) + for( const id of configuration.selection) { + if(loop.body.children.filter(n => n.info.id === id).length > 0) { + return body + } + } + for( const children of loop.body.children) { + console.log(`${children.info.id}`) + } if(isSelected(configuration, loop)) { + console.log('whole loop selected') + return body + } else if(isSelected(configuration, loop.body)) { + console.log('just body selected') return body } else if(body.length === 0) { return [] @@ -171,7 +185,6 @@ function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, } } -//why is there pretty printing again?? function reconstructIfThenElse(ifThenElse: RIfThenElse, condition: Code, when: Code, otherwise: Code | undefined, configuration: ReconstructionConfiguration): Code { const startPos = getRangeStart(ifThenElse.location) //const endPos = ifThenElse.location.end @@ -212,8 +225,10 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = getRangeStart(loop.location) + //do we print the entiry while loop if the while gets selected?? if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), start) + //return plain(getLexeme(loop), start) + return merge([{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? getRangeStart(loop.location) }], indent: 0 }], reconstructExpressionList(loop.body, undefined, [body], configuration)) } const additionalTokens = reconstructAdditionalTokens(loop) const out = merge( diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index 407bed011e..acebf8bf08 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -160,7 +160,7 @@ describe('Functions Reconstruct', () => { { input: [{ part: 'Hello', loc: [0, 0] }] as [PrettyPrintLinePart],expected: 'Hello',msg: 'No Spaces anywhere', columnOffset: 0 }, { input: [{ part: 'Hello World', loc: [0, 0] }] as [PrettyPrintLinePart],expected: 'Hello World',msg: 'Spaces get preserved', columnOffset: 0 }, { input: [{ part: 'Hello', loc: [0, 0] }, { part: 'World', loc: [0, 6] }] as [PrettyPrintLinePart, PrettyPrintLinePart],expected: 'Hello World',msg: 'Spaces get added within the string', columnOffset: 0 }, - { input: [{ part: 'Hello', loc: [0, 6] }] as [PrettyPrintLinePart],expected: ' Hello',msg: 'Spaces get added at the beginning', columnOffset: 0 }, + { input: [{ part: 'Hello', loc: [0, 6] }] as [PrettyPrintLinePart],expected: 'Hello',msg: 'Spaces get added at the beginning', columnOffset: 0 }, { input: [{ part: 'World', loc: [0, 6] },{ part: 'Hello', loc: [0, 0] }] as [PrettyPrintLinePart, PrettyPrintLinePart],expected: 'Hello World',msg: 'Spaces get added within the string, wrong order', columnOffset: 0 }, ]) { positive(testCase.input, testCase.expected, testCase.msg, testCase.columnOffset) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 1eef8d2005..38f7041c01 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -20,6 +20,7 @@ describe('Simple', withShell(shell => { ['12 + (supi <- 42)', 0, '12', ['grouping', 'name-normal', ...OperatorDatabase['<-'].capabilities, ...OperatorDatabase['+'].capabilities, 'precedence']], ['y <- x <- 42', 1, 'x', ['name-normal', 'numbers', 'return-value-of-assignments', ...OperatorDatabase['<-'].capabilities, 'precedence'] ], ['y <- x <- 42', 0, 'y', ['name-normal', 'numbers', 'return-value-of-assignments', ...OperatorDatabase['<-'].capabilities, 'precedence'] ], + //this test now reconstructs the for?? ['for (i in 1:20) { x <- 5 }', 6, 'x', ['for-loop', 'name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities] ] ] as [string, number, string, SupportedFlowrCapabilityId[]][]) { assertReconstructed(label(code, caps), shell, code, id, expected) @@ -37,11 +38,17 @@ describe('Simple', withShell(shell => { }) describe('Loops', () => { - describe('repeat', () => { + describe.only('repeat', () => { const pool: [string, NodeId | NodeId[], string, SupportedFlowrCapabilityId[]][] = [ + //this test now reconstructs the repeat?? + //repeat x ['repeat { x }', 2, 'x', ['repeat-loop', 'name-normal']], + //this test now reconstructs the repeat?? + //repeat x ['repeat { x <- 5; y <- 9 }', 2, 'x', ['repeat-loop', 'name-normal', ...OperatorDatabase['<-'].capabilities, 'semicolons', 'numbers']], - ['repeat { x <- 5; y <- 9 }', [2, 4, 6], 'x <- 5\n9', ['repeat-loop', 'name-normal', ...OperatorDatabase['<-'].capabilities, 'semicolons', 'numbers']] + //this test now reconstructs the repeat?? + //repeat { x <- 5; y <- 9 } + ['repeat { x <- 5; y <- 9 }', [2, 4, 6], 'x <- 5; 9', ['repeat-loop', 'name-normal', ...OperatorDatabase['<-'].capabilities, 'semicolons', 'numbers']] ] for(const [code, id, expected, caps] of pool) { assertReconstructed(label(code, caps), shell, code, id, expected) @@ -51,21 +58,30 @@ describe('Simple', withShell(shell => { describe('while', () => { const fiveNineCaps: SupportedFlowrCapabilityId[] = ['while-loop', 'logical', 'name-normal', ...OperatorDatabase['<-'].capabilities, 'numbers', 'semicolons'] const pool: [string, NodeId | NodeId[], string, SupportedFlowrCapabilityId[]][] = [ + //this test now reconstructs the while + //while(TRUE) x ['while(TRUE) { x }', 3, 'x', ['while-loop', 'logical', 'name-normal']], + //this test now reconstructs the while + //while(TRUE) x ['while(TRUE) { x <- 5 }', 3, 'x', ['while-loop', 'logical', 'name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities]], + //this test now reconstructs the while + //while(TRUE) x ['while(TRUE) { x <- 5; y <- 9 }', 3, 'x', fiveNineCaps], - ['while(TRUE) { x <- 5; y <- 9 }', [10, 3], 'while(TRUE) x', fiveNineCaps], - ['while(TRUE) { x <- 5; y <- 9 }', [10, 3, 5], 'while(TRUE) x <- 5', fiveNineCaps], - ['while(TRUE) { x <- 5; y <- 9 }', [10, 6], 'while(TRUE) y', fiveNineCaps], - ['while(TRUE) { x <- 5; y <- 9 }', [3, 4, 6], 'x <- 5\ny', fiveNineCaps], + ['while(TRUE) { x <- 5; y <- 9 }', [10, 3], 'while(TRUE) x', fiveNineCaps], + ['while(TRUE) { x <- 5; y <- 9 }', [10, 3, 5], 'while(TRUE) x <- 5', fiveNineCaps], + ['while(TRUE) { x <- 5; y <- 9 }', [10, 6], 'while(TRUE) y', fiveNineCaps], + //this test now reconstructs the while?? + ['while(TRUE) { x <- 5; y <- 9 }', [3, 4, 6], 'x <- 5; y', fiveNineCaps], + //this test now reconstructs the while ['while(x + 2 > 3) { x <- 0 }', [7], 'x', ['while-loop', 'binary-operator', 'infix-calls', ...OperatorDatabase['+'].capabilities, 'name-normal', ...OperatorDatabase['<-'].capabilities, 'numbers']], - ['while(x + 2 > 3) { x <- 0 }', [0, 7], 'while(x + 2 > 3) x', ['while-loop', 'binary-operator', 'infix-calls', ...OperatorDatabase['+'].capabilities, 'name-normal', ...OperatorDatabase['<-'].capabilities, 'numbers']] + ['while(x + 2 > 3) { x <- 0 }', [0, 7], 'while(x + 2 > 3) x', ['while-loop', 'binary-operator', 'infix-calls', ...OperatorDatabase['+'].capabilities, 'name-normal', ...OperatorDatabase['<-'].capabilities, 'numbers']] ] for(const [code, id, expected, caps] of pool) { assertReconstructed(label(code, caps), shell, code, id, expected) } }) + //for tests will fail due to the current pretty printing describe('for', () => { const largeFor = ` for (i in 1:20) { @@ -94,9 +110,13 @@ describe('Simple', withShell(shell => { describe('function definition', () => { const testCases: {name: string, case: string, argument: string[], expected: string}[] = [ + //this test does not reconstruct anything?? { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, + //this test does not reconstruct anything?? { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, + //this test does not reconstruct anything?? { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, + //this test does not reconstruct anything?? { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } ] for(const test of testCases) { @@ -106,8 +126,10 @@ describe('Simple', withShell(shell => { describe('Branches', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '3', '0'], expected: 'if(TRUE) { x <- 3 }\nx' }, - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['10', '7', '0'], expected: 'if(FALSE) {} else { x <- 4 }\nx' } + //this test does not reconstruct anything?? [ids may be wrong] + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['5', '13', '0', '14'], expected: 'if(TRUE) { x <- 3 }\nx' }, + //this test does not reconstruct anything?? [ids may be wrong] + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['11', '0', '13', '14'], expected: 'if(FALSE) {} else { x <- 4 }\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) @@ -116,9 +138,10 @@ describe('Simple', withShell(shell => { describe('Functions in assignments', () => { const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ + //this test does not reconstruct anything?? [ids may be wrong] { name: 'Nested Side-Effect For First', case: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', - argument: ['0', '1', '2', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '20', '21', '22', '23'], + argument: ['0', '1', '2', '3', '4', '5', '6', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '20', '21', '22', '24', '25', '26', '27', '28', '29'], expected: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()' } ] for(const test of testCases) { diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index f5dcd72518..d681c61bf1 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -29,7 +29,7 @@ for(i in 1:10) { x <- x * 2 } cat(x) - `, ['6@x'], 'x <- 1\nfor(i in 1:10) {\n x <- x * 2\n}\ncat(x)') + `, ['6@x'], 'x <- 1\nfor(i in 1:10) {\n x <- x * 2\n}\nx') assertSliced('While-Loop', shell, ` print(x) `, ['6@x'], 'x <- 1\nfor(i in 1:10) x <- x * 2\nx', { From d25e46425eeb4bdc5de5de760e2f6d1c4561f992 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 27 Jun 2024 18:31:56 +0200 Subject: [PATCH 83/88] feat(reconstruct): changed reconstruction to work with new {} --- src/reconstruct/reconstruct.ts | 93 ++++++++++++------- .../slicing/reconstruct/simple-tests.ts | 50 ++++------ 2 files changed, 75 insertions(+), 68 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index e26f739b84..3480b4cabf 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -34,6 +34,7 @@ import { getRangeEnd, getRangeStart } from '../util/range' import type { SourcePosition, SourceRange } from '../util/range' import { autoSelectLibrary, getIndentString, indentBy, isSelected, merge, plain, prettyPrintCodeToString, prettyPrintPartToString } from './helper' import type { AutoSelectPredicate, Code, PrettyPrintLine, PrettyPrintLinePart } from './helper' +import { collectAllIds } from '../r-bridge/lang-4.x/ast/model/collect' import { jsonReplacer } from '../util/json' type Selection = ReadonlySet @@ -109,31 +110,39 @@ function reconstructBinaryOp(n: RBinaryOp | RPipe, variable: Code, vector: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = getRangeStart(loop.info.fullRange) //may be unnesseccary - if(isSelected(configuration, loop)) { - return plain(getLexeme(loop), start ?? getRangeStart(loop.location)) - } - if(isSelected(configuration, loop.body)) { - return merge(body) - } const additionalTokens = reconstructAdditionalTokens(loop) const vectorLocation: SourcePosition = loop.vector.location? getRangeStart(loop.vector.location) : vector[0].linePart[0].loc vectorLocation[1] -= 1 //somehow the vector is consistently one space to late const reconstructedVector = plain(getLexeme(loop.vector), vectorLocation) - - if(variable.length === 0 && vector.length === 0 && body.length === 0) { - return [] - } - const startLoc = start ?? getRangeStart(loop.location) const out = merge( [{ linePart: [{ part: 'for', loc: startLoc }], indent: 0 }], - [{ linePart: [{ part: '(', loc: [startLoc[0], startLoc[1] + 3] }], indent: 0 }], + [{ linePart: [{ part: '(', loc: [startLoc[0], startLoc[1] + 4] }], indent: 0 }], [{ linePart: [{ part: getLexeme(loop.variable), loc: getRangeStart(loop.variable.location) }], indent: 0 }], [{ linePart: [{ part: ' in ', loc: getRangeEnd(loop.variable.location) }], indent: 0 }], reconstructedVector, [{ linePart: [{ part: ')', loc: [vectorLocation[0], vectorLocation[1] + prettyPrintCodeToString(reconstructedVector).length] }], indent: 0 }], ...additionalTokens ) + console.log(JSON.stringify(body), jsonReplacer) + + if(isSelected(configuration, loop)) { + return merge(out, body) + } + if(Array.from(collectAllIds(loop.body)).some(n => configuration.selection.has(n))) { + return merge(body) + } + if(isSelected(configuration, loop.variable) || isSelected(configuration, loop.vector)) { + const hBody = out[out.length - 1].linePart + const bodyLoc = hBody[hBody.length - 1].loc + out.push({ linePart: [{ part: '{}', loc: [bodyLoc[0], bodyLoc[1] + 2] }], indent: 0 }) + return out + } + + if(variable.length === 0 && vector.length === 0 && body.length === 0) { + return [] + } + //if body empty if(body.length < 1) { // puts {} with one space separation after for(...) @@ -156,22 +165,10 @@ function reconstructAdditionalTokens(node: RNodeWithParent): Code[] { } -//what is happening here??? function reconstructRepeatLoop(loop: RRepeatLoop, body: Code, configuration: ReconstructionConfiguration): Code { - console.log(`${JSON.stringify(configuration, jsonReplacer)}\n${loop.info.id}\n${loop.body.info.id}`) - for( const id of configuration.selection) { - if(loop.body.children.filter(n => n.info.id === id).length > 0) { - return body - } - } - for( const children of loop.body.children) { - console.log(`${children.info.id}`) - } - if(isSelected(configuration, loop)) { - console.log('whole loop selected') + if(Array.from(collectAllIds(loop)).some(n => configuration.selection.has(n))) { return body - } else if(isSelected(configuration, loop.body)) { - console.log('just body selected') + } else if(isSelected(configuration, loop)) { return body } else if(body.length === 0) { return [] @@ -200,41 +197,65 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi } const additionalTokens = reconstructAdditionalTokens(ifThenElse) //console.log('additional Tokens: ', JSON.stringify(additionalTokens,jsonReplacer)) - let out = merge( ...additionalTokens, [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }], - when ) - if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { + console.log(JSON.stringify(when, jsonReplacer)) + if(when.length === 1) { + const xLoc = when[0].linePart[1].part.indexOf(when[0].linePart[0].part) + const whenLoc = when[0].linePart[0].loc + out.push(when[0].linePart.length === 2 ? { linePart: [{ part: when[0].linePart[1].part, loc: [whenLoc[0], whenLoc[1] - xLoc] }], indent: 0 }: { linePart: [when[0].linePart[0]], indent: 0 }) + } else if(when.length > 1) { + //this feels very hacky.. + //we should probably improve this + when = when.filter(n => !n.linePart[0].part.startsWith(' ')) + when = merge(when) + console.log(JSON.stringify(when, jsonReplacer)) + const secondLastLoc = when[when.length - 2].linePart[0].loc + const lastLoc = when[when.length - 1].linePart[0].loc + when[when.length - 1].linePart[0].loc = lastLoc[0] === secondLastLoc[0]? lastLoc : [secondLastLoc[0] + 1, secondLastLoc[1]] + when[when.length - 2].linePart[0].loc = [when[0].linePart[0].loc[0] - 1, when[0].linePart[0].loc[1]] //this will stick to the condition, maybe change this + out.push(...when) + } + + if(otherwise.length > 0) { + console.log(JSON.stringify(otherwise, jsonReplacer)) //console.log('we have an else-body') const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: [elsePos[0], elsePos[1] + 2] as SourcePosition }], indent: 0 }] : ([] as Code) const elseOffset = when.length === 0 ? 4 : 0 + const xLoc = otherwise[0].linePart[1].part.indexOf(otherwise[0].linePart[0].part) + const otherwiseLoc = otherwise[0].linePart[0].loc out = merge( out, fakeWhenBlock, [{ linePart: [{ part: 'else', loc: [elsePos[0], elsePos[1] + 2 +elseOffset] }], indent: 0 }], //may have to change the location - otherwise + otherwise[0].linePart.length > 1 ? [{ linePart: [{ part: otherwise[0].linePart[1].part, loc: [otherwiseLoc[0], otherwiseLoc[1] - xLoc] }], indent: 0 }]: [{ linePart: [otherwise[0].linePart[0]], indent: 0 }] ) } - return out + return merge(out) } function reconstructWhileLoop(loop: RWhileLoop, condition: Code, body: Code, configuration: ReconstructionConfiguration): Code { const start = getRangeStart(loop.location) - //do we print the entiry while loop if the while gets selected?? - if(isSelected(configuration, loop)) { - //return plain(getLexeme(loop), start) - return merge([{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? getRangeStart(loop.location) }], indent: 0 }], reconstructExpressionList(loop.body, undefined, [body], configuration)) - } const additionalTokens = reconstructAdditionalTokens(loop) const out = merge( [{ linePart: [{ part: `while(${getLexeme(loop.condition)})`, loc: start ?? getRangeStart(loop.location) }], indent: 0 }], ...additionalTokens ) + //do we print the entiry while loop if the while gets selected?? + if(isSelected(configuration, loop)) { + out.push(...reconstructExpressionList(loop.body, undefined, [body], configuration)) + return merge(out) + } else if( + !Array.from(collectAllIds(loop.condition)).some(n => configuration.selection.has(n)) + && Array.from(collectAllIds(loop.body)).some(n => configuration.selection.has(n)) + ) { + return body + } if(body.length < 1) { //this puts {} one space after while(...) const hBody = out[out.length - 1].linePart diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 38f7041c01..7aa5cf2293 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -4,7 +4,7 @@ import type { SupportedFlowrCapabilityId } from '../../../../src/r-bridge/data/g import { OperatorDatabase } from '../../../../src/r-bridge/lang-4.x/ast/model/operators' import type { NodeId } from '../../../../src/r-bridge/lang-4.x/ast/model/processing/node-id' -describe('Simple', withShell(shell => { +describe.only('Simple', withShell(shell => { describe('Constant assignments', () => { for(const [id, code, caps] of [ [0, 'x <- 5', ['name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities]], @@ -38,7 +38,7 @@ describe('Simple', withShell(shell => { }) describe('Loops', () => { - describe.only('repeat', () => { + describe('repeat', () => { const pool: [string, NodeId | NodeId[], string, SupportedFlowrCapabilityId[]][] = [ //this test now reconstructs the repeat?? //repeat x @@ -83,23 +83,15 @@ describe('Simple', withShell(shell => { //for tests will fail due to the current pretty printing describe('for', () => { - const largeFor = ` - for (i in 1:20) { - y <- 9 - x <- 5 - 12 -> x - } - ` + const largeFor = 'for (i in 1:20) {\n y <- 9\n x <- 5\n 12 -> x\n}' + const caps: SupportedFlowrCapabilityId[] = ['for-loop', 'name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities, ...OperatorDatabase['->'].capabilities, 'newlines'] const pool: [string, NodeId | NodeId[], string][] = [ - [largeFor, 0, 'for(i in 1:20) {}'], + [largeFor, [0], 'for (i in 1:20) {}'], [largeFor, 6, 'y'], - [largeFor, [6, 16], 'for(i in 1:20) y'], + [largeFor, [6, 16, 4], 'for (i in 1:20) {\n y\n}'], [largeFor, [6, 9], 'y\nx'], - [largeFor, [6, 12, 16], `for(i in 1:20) { - y - 12 -}`], + [largeFor, [6, 12, 16, 4], 'for (i in 1:20) {\n y\n 12\n}'], ] for(const [code, id, expected] of pool) { @@ -109,15 +101,11 @@ describe('Simple', withShell(shell => { }) describe('function definition', () => { - const testCases: {name: string, case: string, argument: string[], expected: string}[] = [ - //this test does not reconstruct anything?? - { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: ['0'], expected: 'a <- function (x) { x <- 2 }' }, - //this test does not reconstruct anything?? - { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: ['5'], expected: 'x <- 2' }, - //this test does not reconstruct anything?? - { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: ['0'], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, - //this test does not reconstruct anything?? - { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: ['5'], expected: 'x <- 2' } + const testCases: {name: string, case: string, argument: NodeId[], expected: string}[] = [ + { name: 'simple function', case: 'a <- function (x) { x <- 2 }', argument: [10], expected: 'a <- function (x) { x <- 2 }' }, + { name: 'function body extracted', case: 'a <- function (x) { x <- 2 }', argument: [7], expected: 'x <- 2' }, + { name: 'multi-line function', case: 'a <- function (x) { x <- 2;\nx + 4 }', argument: [13], expected: 'a <- function (x) { x <- 2;\nx + 4 }' }, + { name: 'only one function body extracted', case: 'a <- function (x) { x <- 2; x + 4 }', argument: [7], expected: 'x <- 2' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) @@ -125,11 +113,10 @@ describe('Simple', withShell(shell => { }) describe('Branches', () => { - const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ - //this test does not reconstruct anything?? [ids may be wrong] - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: ['5', '13', '0', '14'], expected: 'if(TRUE) { x <- 3 }\nx' }, - //this test does not reconstruct anything?? [ids may be wrong] - { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: ['11', '0', '13', '14'], expected: 'if(FALSE) {} else { x <- 4 }\nx' } + const testCases: {name: string, case: string, argument: NodeId|NodeId[], expected: string}[] = [ + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 3, 5, 14, 2], expected: 'if(TRUE) { x <- 3 }\nx' }, + { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 11, 14, 9, 8], expected: 'if(FALSE) {} else { x <- 4 }\nx' }, + { name: 'multi-line true if statement', case: 'if(TRUE) {\n x <- 3\n y <- 4\n} else {\n x <- 4\n y <- 3\n}\nx', argument: [0, 3, 5, 2, 20], expected: 'if(TRUE) {\n x <- 3\n}\nx' } ] for(const test of testCases) { assertReconstructed(test.name, shell, test.case, test.argument, test.expected) @@ -137,11 +124,10 @@ describe('Simple', withShell(shell => { }) describe('Functions in assignments', () => { - const testCases: {name: string, case: string, argument: string|string[], expected: string}[] = [ - //this test does not reconstruct anything?? [ids may be wrong] + const testCases: {name: string, case: string, argument: NodeId|NodeId[], expected: string}[] = [ { name: 'Nested Side-Effect For First', case: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()\n', - argument: ['0', '1', '2', '3', '4', '5', '6', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '20', '21', '22', '24', '25', '26', '27', '28', '29'], + argument: [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 24, 25, 26, 27, 28, 29], expected: 'f <- function() {\n a <- function() { x }\n x <- 3\n b <- a()\n x <- 2\n a()\n b\n}\nb <- f()' } ] for(const test of testCases) { From eb5b11e8dfd3ff450143b90fbc1cf377eebbbf6e Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:21:48 +0200 Subject: [PATCH 84/88] test(reconstruct): test for plainSplit --- src/reconstruct/reconstruct.ts | 2 +- .../slicing/reconstruct/function-tests.ts | 19 +++++++++++++++++++ .../slicing/reconstruct/simple-tests.ts | 4 ++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 3480b4cabf..9c1e38756c 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -130,7 +130,7 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v return merge(out, body) } if(Array.from(collectAllIds(loop.body)).some(n => configuration.selection.has(n))) { - return merge(body) + return merge(out, body) } if(isSelected(configuration, loop.variable) || isSelected(configuration, loop.vector)) { const hBody = out[out.length - 1].linePart diff --git a/test/functionality/slicing/reconstruct/function-tests.ts b/test/functionality/slicing/reconstruct/function-tests.ts index acebf8bf08..040fd339ff 100644 --- a/test/functionality/slicing/reconstruct/function-tests.ts +++ b/test/functionality/slicing/reconstruct/function-tests.ts @@ -23,6 +23,25 @@ describe('Functions Reconstruct', () => { positive(testCase.input, testCase.line, testCase.column, testCase.expected) } }) + describe('plainSplit', () => { + function positive(input: string, startPos: SourcePosition, expected: Code) { + it(`${input} for ${startPos[0]}:${startPos[1]}`, () => { + const result:Code = plain(input, startPos) + assert.deepStrictEqual(result,expected) + }) + } + const testCases = [ + { input: 'Hello', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] }],indent: 0 }] as Code }, + { input: 'Hello World', line: 4, column: 3, expected: [{ linePart: [{ part: 'Hello', loc: [4, 3] }, { part: 'World', loc: [4, 9] }],indent: 0 }] as Code }, + { input: 'Hello\nWorld', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] }],indent: 0 }, { linePart: [{ part: 'World', loc: [2, 1] }],indent: 0 }] as Code }, + { input: 'Hello\nWorld', line: 3, column: 4, expected: [{ linePart: [{ part: 'Hello', loc: [3, 4] }],indent: 0 }, { linePart: [{ part: 'World', loc: [4, 1] }],indent: 0 }] as Code } + //{ input: 'Hello\n World\n24', line: 1, column: 1, expected: [{ linePart: [{ part: 'Hello', loc: [1, 1] },{ part: ' World', loc: [2, 1] },{ part: '24', loc: [3, 1] }],indent: 0 }] as Code } + ] + for(const testCase of testCases) { + positive(testCase.input, [testCase.line, testCase.column], testCase.expected) + } + } + ) describe('merge', () => { function positive(snipbits: Code[],expected: Code) { it(prettyPrintCodeToString(expected),() => { diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 7aa5cf2293..cccc0c05d3 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -4,7 +4,7 @@ import type { SupportedFlowrCapabilityId } from '../../../../src/r-bridge/data/g import { OperatorDatabase } from '../../../../src/r-bridge/lang-4.x/ast/model/operators' import type { NodeId } from '../../../../src/r-bridge/lang-4.x/ast/model/processing/node-id' -describe.only('Simple', withShell(shell => { +describe('Simple', withShell(shell => { describe('Constant assignments', () => { for(const [id, code, caps] of [ [0, 'x <- 5', ['name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities]], @@ -112,7 +112,7 @@ describe.only('Simple', withShell(shell => { } }) - describe('Branches', () => { + describe.only('Branches', () => { const testCases: {name: string, case: string, argument: NodeId|NodeId[], expected: string}[] = [ { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 3, 5, 14, 2], expected: 'if(TRUE) { x <- 3 }\nx' }, { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 11, 14, 9, 8], expected: 'if(FALSE) {} else { x <- 4 }\nx' }, From 66888f51cd25d2e1be9a7e087ccf024b795d8c95 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:42:01 +0200 Subject: [PATCH 85/88] feat-fix(reconstruct): fixed grouping --- src/reconstruct/helper.ts | 1 - src/reconstruct/reconstruct.ts | 40 +++++++------------ .../slicing/reconstruct/simple-tests.ts | 2 +- test/testfiles/example.R | 2 + 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 675c03c970..42bbc7d461 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -263,7 +263,6 @@ function addSemis(code: Code): Code { } } - export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge(code) code = addSemis(code) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 9c1e38756c..454fa39ec6 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -52,7 +52,6 @@ const foldToConst = (n: RNodeWithParent): Code => plain(getLexeme(n), n.location function reconstructExpressionList(exprList: RExpressionList, grouping: [Code, Code] | undefined, expressions: Code[], config: ReconstructionConfiguration): Code { const subExpressions = expressions.filter(e => e.length > 0) - if(isSelected(config, exprList)) { const positionStart: SourcePosition = exprList.location? getRangeStart(exprList.location) : [0, 0] return plain(getLexeme(exprList), positionStart) @@ -62,10 +61,21 @@ function reconstructExpressionList(exprList: RExpressionList, return [] } else { const additionalTokens = reconstructAdditionalTokens(exprList) + const ifGrouping = exprList.grouping + let opening: Code = [] + let closing: Code = [] + if(ifGrouping) { + const openingLoc = getRangeStart(exprList.grouping?.[0].location) + const closingLoc = getRangeStart(exprList.grouping?.[1].location) + opening = plain(exprList.grouping?.[0].lexeme, openingLoc) + closing = plain(exprList.grouping?.[1].lexeme, closingLoc) + subExpressions.filter((code) => code[0].linePart[0].loc[0] > openingLoc[0] && code[0].linePart[0].loc[0] < closingLoc[0]).map(code => code[0].indent = code[0].linePart[0].loc[1] / 4) + } return merge( ...subExpressions, ...additionalTokens, - ...(grouping ?? []) + opening, + closing ) } } @@ -200,40 +210,20 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi let out = merge( ...additionalTokens, [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }], + when ) - console.log(JSON.stringify(when, jsonReplacer)) - if(when.length === 1) { - const xLoc = when[0].linePart[1].part.indexOf(when[0].linePart[0].part) - const whenLoc = when[0].linePart[0].loc - out.push(when[0].linePart.length === 2 ? { linePart: [{ part: when[0].linePart[1].part, loc: [whenLoc[0], whenLoc[1] - xLoc] }], indent: 0 }: { linePart: [when[0].linePart[0]], indent: 0 }) - } else if(when.length > 1) { - //this feels very hacky.. - //we should probably improve this - when = when.filter(n => !n.linePart[0].part.startsWith(' ')) - when = merge(when) - console.log(JSON.stringify(when, jsonReplacer)) - const secondLastLoc = when[when.length - 2].linePart[0].loc - const lastLoc = when[when.length - 1].linePart[0].loc - when[when.length - 1].linePart[0].loc = lastLoc[0] === secondLastLoc[0]? lastLoc : [secondLastLoc[0] + 1, secondLastLoc[1]] - when[when.length - 2].linePart[0].loc = [when[0].linePart[0].loc[0] - 1, when[0].linePart[0].loc[1]] //this will stick to the condition, maybe change this - out.push(...when) - } - - if(otherwise.length > 0) { - console.log(JSON.stringify(otherwise, jsonReplacer)) + if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { //console.log('we have an else-body') const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: [elsePos[0], elsePos[1] + 2] as SourcePosition }], indent: 0 }] : ([] as Code) const elseOffset = when.length === 0 ? 4 : 0 - const xLoc = otherwise[0].linePart[1].part.indexOf(otherwise[0].linePart[0].part) - const otherwiseLoc = otherwise[0].linePart[0].loc out = merge( out, fakeWhenBlock, [{ linePart: [{ part: 'else', loc: [elsePos[0], elsePos[1] + 2 +elseOffset] }], indent: 0 }], //may have to change the location - otherwise[0].linePart.length > 1 ? [{ linePart: [{ part: otherwise[0].linePart[1].part, loc: [otherwiseLoc[0], otherwiseLoc[1] - xLoc] }], indent: 0 }]: [{ linePart: [otherwise[0].linePart[0]], indent: 0 }] + otherwise ) } return merge(out) diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index cccc0c05d3..683957d31d 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -114,7 +114,7 @@ describe('Simple', withShell(shell => { describe.only('Branches', () => { const testCases: {name: string, case: string, argument: NodeId|NodeId[], expected: string}[] = [ - { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 3, 5, 14, 2], expected: 'if(TRUE) { x <- 3 }\nx' }, + { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 3, 5, 14, 2, 1], expected: 'if(TRUE) { x <- 3 }\nx' }, { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 11, 14, 9, 8], expected: 'if(FALSE) {} else { x <- 4 }\nx' }, { name: 'multi-line true if statement', case: 'if(TRUE) {\n x <- 3\n y <- 4\n} else {\n x <- 4\n y <- 3\n}\nx', argument: [0, 3, 5, 2, 20], expected: 'if(TRUE) {\n x <- 3\n}\nx' } ] diff --git a/test/testfiles/example.R b/test/testfiles/example.R index cd86f3fe93..67bcb5a65c 100644 --- a/test/testfiles/example.R +++ b/test/testfiles/example.R @@ -8,5 +8,7 @@ for (i in 1:(N-1)) { product <- product * i } +if(TRUE) {product <- sum * product} else {sum <- product + sum} + cat("Sum:", sum, "\n") cat("Product:", product, "\n") From 9261280999e7b9b7cdef391a53209191e354c487 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:55:09 +0200 Subject: [PATCH 86/88] feat-fix(reconstruct): fixed indent and for --- src/reconstruct/helper.ts | 2 +- src/reconstruct/reconstruct.ts | 20 +++++++++++++------ .../slicing/reconstruct/simple-tests.ts | 2 +- test/testfiles/example.R | 4 +--- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/reconstruct/helper.ts b/src/reconstruct/helper.ts index 42bbc7d461..a45cd114b4 100644 --- a/src/reconstruct/helper.ts +++ b/src/reconstruct/helper.ts @@ -266,7 +266,7 @@ function addSemis(code: Code): Code { export function prettyPrintCodeToString(code: Code, lf = '\n'): string { code = merge(code) code = addSemis(code) - return code.map(({ linePart, indent }) => `${getIndentString(indent)}${prettyPrintPartToString(linePart, code[0].linePart[0].loc[1])}`).join(lf) + return code.map(({ linePart, indent }) => `${getIndentString(Math.max(indent, 0))}${prettyPrintPartToString(linePart, code[0].linePart[0].loc[1])}`).join(lf) } export function removeOuterExpressionListIfApplicable(result: PrettyPrintLine[]): Code { diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 454fa39ec6..77978ae8d3 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -69,7 +69,11 @@ function reconstructExpressionList(exprList: RExpressionList, const closingLoc = getRangeStart(exprList.grouping?.[1].location) opening = plain(exprList.grouping?.[0].lexeme, openingLoc) closing = plain(exprList.grouping?.[1].lexeme, closingLoc) - subExpressions.filter((code) => code[0].linePart[0].loc[0] > openingLoc[0] && code[0].linePart[0].loc[0] < closingLoc[0]).map(code => code[0].indent = code[0].linePart[0].loc[1] / 4) + subExpressions.filter( + code => code[0].linePart[0].loc[0] > openingLoc[0] && code[0].linePart[0].loc[0] < closingLoc[0] + ).map( + code => code[0].linePart[0].part = ' '.repeat(code[0].linePart[0].loc[1] - 1).concat(code[0].linePart[0].part) + ) } return merge( ...subExpressions, @@ -134,12 +138,15 @@ function reconstructForLoop(loop: RForLoop, variable: Code, v [{ linePart: [{ part: ')', loc: [vectorLocation[0], vectorLocation[1] + prettyPrintCodeToString(reconstructedVector).length] }], indent: 0 }], ...additionalTokens ) - console.log(JSON.stringify(body), jsonReplacer) if(isSelected(configuration, loop)) { return merge(out, body) } - if(Array.from(collectAllIds(loop.body)).some(n => configuration.selection.has(n))) { + if(!isSelected(configuration, loop.variable) && Array.from(collectAllIds(loop.body)).some(n => configuration.selection.has(n))) { + body.map( prettyPrintLine => prettyPrintLine.linePart[0].part = prettyPrintLine.linePart[0].part.replaceAll(' ','')) + return body + } + if(isSelected(configuration, loop.variable) && Array.from(collectAllIds(loop.body)).some(n => configuration.selection.has(n))) { return merge(out, body) } if(isSelected(configuration, loop.variable) || isSelected(configuration, loop.vector)) { @@ -206,15 +213,16 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi return [] } const additionalTokens = reconstructAdditionalTokens(ifThenElse) - //console.log('additional Tokens: ', JSON.stringify(additionalTokens,jsonReplacer)) let out = merge( ...additionalTokens, [{ linePart: [{ part: `if(${getLexeme(ifThenElse.condition)})`, loc: startPos }], indent: 0 }], when ) + console.log(JSON.stringify(when,jsonReplacer)) if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { - //console.log('we have an else-body') + console.log('we got here') + const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: [elsePos[0], elsePos[1] + 2] as SourcePosition }], indent: 0 }] : ([] as Code) @@ -225,7 +233,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi [{ linePart: [{ part: 'else', loc: [elsePos[0], elsePos[1] + 2 +elseOffset] }], indent: 0 }], //may have to change the location otherwise ) - } + } return merge(out) } diff --git a/test/functionality/slicing/reconstruct/simple-tests.ts b/test/functionality/slicing/reconstruct/simple-tests.ts index 683957d31d..03813fc85e 100644 --- a/test/functionality/slicing/reconstruct/simple-tests.ts +++ b/test/functionality/slicing/reconstruct/simple-tests.ts @@ -112,7 +112,7 @@ describe('Simple', withShell(shell => { } }) - describe.only('Branches', () => { + describe('Branches', () => { const testCases: {name: string, case: string, argument: NodeId|NodeId[], expected: string}[] = [ { name: 'simple if statement', case: 'if(TRUE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 3, 5, 14, 2, 1], expected: 'if(TRUE) { x <- 3 }\nx' }, { name: 'false if statement', case: 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', argument: [0, 11, 14, 9, 8], expected: 'if(FALSE) {} else { x <- 4 }\nx' }, diff --git a/test/testfiles/example.R b/test/testfiles/example.R index 67bcb5a65c..1326864c6e 100644 --- a/test/testfiles/example.R +++ b/test/testfiles/example.R @@ -8,7 +8,5 @@ for (i in 1:(N-1)) { product <- product * i } -if(TRUE) {product <- sum * product} else {sum <- product + sum} - cat("Sum:", sum, "\n") -cat("Product:", product, "\n") +cat("Product:", product, "\n") \ No newline at end of file From 5b3ede2eb81f2479e42512d5e3136c61681602f0 Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Fri, 9 Aug 2024 12:11:31 +0200 Subject: [PATCH 87/88] tests-fix(reconstruct): dataflow v2 changes --- .../slicing/static-program-slices/simple-tests.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/functionality/slicing/static-program-slices/simple-tests.ts b/test/functionality/slicing/static-program-slices/simple-tests.ts index d681c61bf1..ebf680ebe3 100644 --- a/test/functionality/slicing/static-program-slices/simple-tests.ts +++ b/test/functionality/slicing/static-program-slices/simple-tests.ts @@ -11,6 +11,7 @@ describe('Simple', withShell(shell => { ) } }) + //if reconstruction needs more work to handle all cases describe('Constant conditionals', () => { assertSliced('if(TRUE)', shell, 'if(TRUE) { x <- 3 } else { x <- 4}\nx', ['2@x'], 'if(TRUE) { x <- 3 }\nx') //if reconstruction needs more work to handle this @@ -21,6 +22,8 @@ describe('Simple', withShell(shell => { assertSliced(label('if(FALSE)', ['name-normal', 'logical', 'numbers', ...OperatorDatabase['<-'].capabilities, 'newlines', 'if']), shell, 'if(FALSE) { x <- 3 } else { x <- 4 }\nx', ['2@x'], 'x <- 4\nx') }) + //for is currently pretty printing + //this does not produce a stack trace?? describe('Independent Control-Flow', () => { assertSliced(label('For-Loop', ['name-normal', 'for-loop', 'newlines', 'unnamed-arguments', 'numbers', 'built-in-sequencing', ...OperatorDatabase['<-'].capabilities, 'function-calls', ...OperatorDatabase['*'].capabilities, 'precedence']), shell, ` @@ -67,14 +70,14 @@ x`, { assertSliced(label('subset sequence', ['name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities, 'newlines', 'unnamed-arguments', 'built-in-sequencing', 'empty-arguments', 'single-bracket-access', 'subsetting']), shell, 'i <- 4\na <- list(1,2)\n b <- a[1:i,]', ['3@b'], 'i <- 4\na <- list(1,2)\nb <- a[1:i,]') assertSliced(label('range assignment', ['name-normal', 'numbers', ...OperatorDatabase['<-'].capabilities, 'newlines', 'unnamed-arguments', 'built-in-sequencing', 'empty-arguments', 'single-bracket-access', 'subsetting', 'range-assignment']), - shell, 'a <- 1:10\na[1:5] <- 3\na', ['3@a'], 'a <- 1 : 10\na[1:5] <- 3\na') + shell, 'a <- 1:10\na[1:5] <- 3\na', ['3@a'], 'a <- 1:10\na[1:5] <- 3\na') describe('Definitions', () => { describe('[[', () => { const code = '\na <- list(1,2)\na[[1]] = 2\na[[2]] = 3\nb[[4]] = 5\ncat(a)\na <- list(3,4)\ncat(a)\n' //we get an added space in front of the access - assertSliced('Repeated named access and definition', shell, code, ['6@a'], 'a <- list(1,2)\na[[1]] = 2\na[[2]] = 3\ncat(a)') + assertSliced('Repeated named access and definition', shell, code, ['6@a'], 'a <- list(1,2)\na[[1]] = 2\na[[2]] = 3\na') assertSliced('Full redefinitions still apply', shell, code, ['8@a'], `a <- list(3,4) -cat(a)`) +a`) assertSliced(label('Repeated named access and definition', ['name-normal', 'numbers', 'double-bracket-access', 'unnamed-arguments', 'function-calls', ...OperatorDatabase['<-'].capabilities, 'newlines', 'unnamed-arguments']), shell, code, ['6@a'], `a <- list(1,2) a[[1]] = 2 @@ -89,7 +92,7 @@ a`) //we get an added space in front of the access assertSliced('Repeated named access and definition', shell, codeB, ['6@a'], 'a <- list(a=1,b=2)\na$a = 2\na$b = 3\ncat(a)') assertSliced('Full redefinitions still apply', shell, codeB, ['8@a'], `a <- list(a=3,b=4) -cat(a)`) +a`) assertSliced(label('Repeated named access and definition', ['name-normal', 'function-calls', 'named-arguments', 'unnamed-arguments', 'dollar-access', ...OperatorDatabase['<-'].capabilities, 'numbers']), shell, codeB, ['6@a'], `a <- list(a=1,b=2) a$a = 2 @@ -173,7 +176,7 @@ N <- 10 for (i in 1:(N-1)) { sum <- sum + i + w } -cat("Sum:", sum, "\\n")` +sum` ) assertSliced(label('Product in call', capabilities), @@ -183,7 +186,7 @@ N <- 10 for (i in 1:(N-1)) { product <- product * i } -cat("Product:", product, "\\n")` +product` ) assertSliced(label('Top by name', capabilities), From d036b65c76120cf851f4f4a6b5d0b7d7748cd6fa Mon Sep 17 00:00:00 2001 From: Ehcsan <68707578+Ehcsan@users.noreply.github.com> Date: Tue, 13 Aug 2024 21:39:07 +0200 Subject: [PATCH 88/88] tests-fix: started fising calls-tests.ts --- src/reconstruct/reconstruct.ts | 4 +- .../static-program-slices/calls-tests.ts | 70 ++++++++----------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/src/reconstruct/reconstruct.ts b/src/reconstruct/reconstruct.ts index 77978ae8d3..6902947e4e 100644 --- a/src/reconstruct/reconstruct.ts +++ b/src/reconstruct/reconstruct.ts @@ -220,9 +220,7 @@ function reconstructIfThenElse(ifThenElse: RIfThenElse, condi ) console.log(JSON.stringify(when,jsonReplacer)) - if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { - console.log('we got here') - + if(otherwise.length > 0 && !(otherwise[0].linePart.length === 2)) { const hBody = out[out.length - 1].linePart const elsePos = hBody[hBody.length - 1].loc const fakeWhenBlock = when.length === 0 ? [{ linePart: [{ part: ' {} ', loc: [elsePos[0], elsePos[1] + 2] as SourcePosition }], indent: 0 }] : ([] as Code) diff --git a/test/functionality/slicing/static-program-slices/calls-tests.ts b/test/functionality/slicing/static-program-slices/calls-tests.ts index 1676389e65..830bb1faae 100644 --- a/test/functionality/slicing/static-program-slices/calls-tests.ts +++ b/test/functionality/slicing/static-program-slices/calls-tests.ts @@ -32,7 +32,7 @@ foo(x, 3) `, ['3@foo'], 'foo(x, 3)') assertSliced(label('Multiple unknown calls sharing known def', ['name-normal', 'resolve-arguments','formals-named', 'unnamed-arguments', 'implicit-return', 'numbers', 'call-normal', 'newlines']), shell, ` -x. <- function (x) { x } +x. <- function(x) { x } foo(x, x.(y)) foo(x, x.(3)) `, ['4@foo'], `x. <- function(x) { x } @@ -45,8 +45,8 @@ x <- 3 c <- 4 y <- 3 f2(1,x) - `, ['7@f2'], `f1 <- function(a, b) { c } -f2 <- function(...) { f1(...) } + `, ['7@f2'], `f1 <- function (a, b) { c } +f2 <- function (...) { f1(...) } x <- 3 c <- 4 f2(1,x)`) @@ -119,11 +119,9 @@ result`) f() ` assertSliced(label('Late bindings of parameter in body', ['name-normal', 'formals-promises', 'resolve-arguments', ...OperatorDatabase['<-'].capabilities, 'formals-default', 'numbers', 'implicit-return', 'binary-operator', 'infix-calls', ...OperatorDatabase['+'].capabilities, 'call-normal', 'semicolons']), - shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { - b <- 1 - a + 1 - } -f()`) + shell, lateCode, ['2@f'], `f <- function(a=b, m=3) { b <- 1; ; a + 1 } +f() +`) const lateCodeB = `f <- function(a=b, b=3) { b <- 1; a; b <- 5; a + 1 } f() ` @@ -163,16 +161,9 @@ z <- 5 u <- a() u()` assertSliced(label('Must include function shell', ['name-normal', 'closures', ...OperatorDatabase['<-'].capabilities, 'normal-definition', 'implicit-return', 'numbers', 'binary-operator', 'infix-calls', ...OperatorDatabase['+'].capabilities, 'return', 'newlines', 'call-normal', 'semicolons']), - shell, code, ['5@a'], `a <- function() { - x <- function() { } - return(x) - } + shell, code, ['5@a'], `a <- function() { x <- function() { }; return(x) } a()`) - assertSliced(label('Must include function shell on call', ['name-normal', 'closures', ...OperatorDatabase['<-'].capabilities, 'normal-definition', 'newlines', 'return', 'call-normal']), shell, code, ['6@u'], `a <- function() { - x <- function() { z + y } - y <- 12 - return(x) - } + assertSliced(label('Must include function shell on call', ['name-normal', 'closures', ...OperatorDatabase['<-'].capabilities, 'normal-definition', 'newlines', 'return', 'call-normal']), shell, code, ['6@u'], `a <- function() { x <- function() { z + y }; y <- 12; return(x) } z <- 5 u <- a() u()`) @@ -228,7 +219,7 @@ x <- a(function() 2 + 3)() + a(function() 7)()`) f <- function() { x <<- 3 } f() cat(x) - `, ['4@x'], `f <- function() x <<- 3 + `, ['4@x'], `f <- function() { x <<- 3 } f() x`) @@ -245,10 +236,10 @@ cat(x) } b <- f() `, ['8@b'], `f <- function() { - a <- function() { x } - x <- 2 - a() - } + a <- function() { x } + x <- 2 + a() +} b <- f()`) // that it contains x <- 2 is an error in the current implementation as this happens due to the 'reads' edge from the closure linking // however, this read edge should not apply when the call happens within the same scope @@ -262,12 +253,12 @@ b <- f()`) } b <- f() `, ['9@b'], `f <- function() { - a <- function() { x } - x <- 3 - b <- a() - x <- 2 - b - } + a <- function() { x } + x <- 3 + b <- a() + x <- 2 + b +} b <- f()`) }) describe('Early return of function', () => { @@ -282,12 +273,12 @@ b <- f()`) res <- x()` assertSliced(label('Double return points', ['name-normal', 'closures', ...OperatorDatabase['<-'].capabilities, 'call-anonymous', 'normal-definition', 'implicit-return', 'numbers', 'if', 'return', 'implicit-return', 'call-normal', 'newlines']), shell, code, ['9@res'], ` x <- (function() { - g <- function() { y } - y <- 5 - if(z) return(g) - y <- 3 - g - })() + g <- function() { y } + y <- 5 + if(z) return(g) + y <- 3 + g +})() res <- x()`.trim()) }) describe('Recursive functions', () => { @@ -313,10 +304,7 @@ cat(x)` const localCaps: SupportedFlowrCapabilityId[] = ['name-normal', 'lexicographic-scope', 'normal-definition', ...OperatorDatabase['='].capabilities, 'binary-operator', 'infix-calls', ...OperatorDatabase['+'].capabilities, 'semicolons', 'unnamed-arguments', 'newlines', 'call-normal', 'numbers', 'precedence'] assertSliced(label('Local redefinition has no effect', localCaps), shell, localCode, ['5@x'], `x <- 3 x`) - assertSliced(label('Local redefinition must be kept as part of call', localCaps), shell, localCode, ['4@a'], `a <- function() { - x = x + 5 - cat(x) - } + assertSliced(label('Local redefinition must be kept as part of call', localCaps), shell, localCode, ['4@a'], `a <- function() { x = x + 5; cat(x) } x <- 3 a()`) const globalCode = ` @@ -324,7 +312,7 @@ a <- function() { x <<- x + 5; cat(x) } x <- 3 a() x` - assertSliced(label('But the global redefinition remains', ['name-normal', ...OperatorDatabase['<-'].capabilities, 'numbers', 'normal-definition', 'implicit-return', 'side-effects-in-function-call', 'return-value-of-assignments', 'newlines', 'call-normal', 'unnamed-arguments', 'precedence']), shell, globalCode, ['5@x'], `a <- function() x <<- x + 5 + assertSliced(label('But the global redefinition remains', ['name-normal', ...OperatorDatabase['<-'].capabilities, 'numbers', 'normal-definition', 'implicit-return', 'side-effects-in-function-call', 'return-value-of-assignments', 'newlines', 'call-normal', 'unnamed-arguments', 'precedence']), shell, globalCode, ['5@x'], `a <- function() { x <<- x + 5 } x <- 3 a() x`) @@ -333,7 +321,7 @@ a <- function() { x <<- 5; cat(x) } x <- 3 a() x` - assertSliced(label('The local assignment is only needed if the global reads', ['name-normal', ...OperatorDatabase['<-'].capabilities, 'function-definitions', ...OperatorDatabase['<<-'].capabilities, 'numbers', 'newlines', 'call-normal', 'unnamed-arguments', 'precedence']), shell, globalCodeWithoutLocal, ['5@x'], `a <- function() x <<- 5 + assertSliced(label('The local assignment is only needed if the global reads', ['name-normal', ...OperatorDatabase['<-'].capabilities, 'function-definitions', ...OperatorDatabase['<<-'].capabilities, 'numbers', 'newlines', 'call-normal', 'unnamed-arguments', 'precedence']), shell, globalCodeWithoutLocal, ['5@x'], `a <- function() { x <<- x + 5 } a() x`) @@ -360,7 +348,7 @@ x`) y <- 5 x <- 2 a()(y) -cat(x)`, ['5@x'], `a <- function() { function(b) if(runif() > .5) { x <<- b } } +cat(x)`, ['5@x'], `a <- function() { function(b) { if(runif() > .5) { x <<- b } } } y <- 5 x <- 2 a()(y)