Skip to content

Commit 92932de

Browse files
test: parameterize pointer analysis dataflow tests
1 parent a456886 commit 92932de

File tree

7 files changed

+307
-508
lines changed

7 files changed

+307
-508
lines changed

src/dataflow/graph/dataflowgraph-builder.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export class DataflowGraphBuilder extends DataflowGraph {
206206
fromId = from.nodeId;
207207
} else {
208208
const result = runSearch(from.query, data);
209-
guard(result.length == 1, 'from query result should yield only one node');
209+
guard(result.length === 1, `from query result should yield exactly one node, but yielded ${result.length}`);
210210
fromId = result[0].node.info.id;
211211
}
212212

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import type { SupportedFlowrCapabilityId } from '../../../src/r-bridge/data/get';
2+
import { RType } from '../../../src/r-bridge/lang-4.x/ast/model/type';
3+
import { Q } from '../../../src/search/flowr-search-builder';
4+
5+
export const enum ContainerType {
6+
List = 'list',
7+
Vector = 'c',
8+
}
9+
10+
export const enum AccessType {
11+
DoubleBracket = '[[',
12+
SingleBracket = '[',
13+
Dollar = '$',
14+
}
15+
16+
function getClosingBracket(type: AccessType): string {
17+
switch(type) {
18+
case AccessType.DoubleBracket:
19+
return ']]';
20+
case AccessType.SingleBracket:
21+
return ']';
22+
case AccessType.Dollar:
23+
return '';
24+
}
25+
}
26+
27+
function getAccessCapability(type: AccessType): SupportedFlowrCapabilityId {
28+
switch(type) {
29+
case AccessType.DoubleBracket:
30+
return 'double-bracket-access';
31+
case AccessType.SingleBracket:
32+
return 'single-bracket-access';
33+
case AccessType.Dollar:
34+
return 'dollar-access';
35+
}
36+
}
37+
38+
/**
39+
* Creates access string.
40+
*
41+
* Example for name='numbers', index=1 and type=AccessType.DoubleBracket:
42+
* ```r
43+
* numbers[[1]]
44+
* ```
45+
*/
46+
function createAccess(type: AccessType, name: string, ...indices: number[]): string {
47+
const closingBracket = getClosingBracket(type);
48+
49+
let result = name;
50+
let indexSum = 0;
51+
for(const index of indices) {
52+
// Named arguments are indexed starting from 1 so we need to sum the indices to access them
53+
// [2, 1] -> arg3 or [2][1]
54+
indexSum += index;
55+
const indexString = type === AccessType.Dollar ? `arg${indexSum}` : index.toString();
56+
result += `${type}${indexString}${closingBracket}`;
57+
}
58+
return result;
59+
}
60+
61+
/**
62+
* Creates definition string.
63+
*
64+
* Example for values=['1', '2', '3', '4'], container='list' and hasNamedArguments=true:
65+
* ```r
66+
* list(arg1 = 1, arg2 = 2, arg3 = 3, arg4 = 4)
67+
* ```
68+
*/
69+
function createDefinition(type: ContainerType, hasNamedArguments: boolean, ...values: (string | string[])[]): string {
70+
return definitionHelper(type, hasNamedArguments, 1, ...values);
71+
}
72+
73+
/**
74+
* Helper function for createDefinition with start index.
75+
*/
76+
function definitionHelper(
77+
type: ContainerType,
78+
hasNamedArguments: boolean,
79+
index: number,
80+
...values: (string | string[])[]
81+
): string {
82+
let i = index;
83+
const parameterList = values
84+
.map((value) => {
85+
let valueString: string;
86+
const thisIndex = i++;
87+
if(Array.isArray(value)) {
88+
valueString = definitionHelper(type, hasNamedArguments, i, ...value);
89+
i += value.length;
90+
} else {
91+
valueString = value;
92+
}
93+
94+
if(hasNamedArguments) {
95+
return `arg${thisIndex} = ${valueString}`;
96+
} else {
97+
return valueString;
98+
}
99+
})
100+
.join(', ');
101+
return `${type}(${parameterList})`;
102+
}
103+
104+
function queryArgument(hasNamedArguments: boolean, index: number, value: string, line: number) {
105+
if(hasNamedArguments) {
106+
return queryNamedArgument(`arg${index}`, line);
107+
} else {
108+
return queryUnnamedArgument(value, line);
109+
}
110+
}
111+
112+
function queryNamedArgument(name: string, line: number) {
113+
return { query: Q.varInLine(name, line).filter(RType.Argument) };
114+
}
115+
116+
function queryUnnamedArgument(value: string, line: number) {
117+
return { query: Q.varInLine(value, line).filter(RType.Number) };
118+
}
119+
120+
function queryAccessInLine(type: AccessType, line: number) {
121+
return { query: Q.varInLine(type, line) };
122+
}
123+
124+
export function setupContainerFunctions(
125+
containerType: ContainerType,
126+
accessType: AccessType,
127+
hasNamedArguments: boolean
128+
) {
129+
const acc = (name: string, ...indices: number[]) => createAccess(accessType, name, ...indices);
130+
const def = (...values: (string | string[])[]) => createDefinition(containerType, hasNamedArguments, ...values);
131+
const accessCapability = getAccessCapability(accessType);
132+
const queryArg = (index: number, value: string, line: number) =>
133+
queryArgument(hasNamedArguments, index, value, line);
134+
const queryNamedArg = (name: string, line: number) => queryNamedArgument(name, line);
135+
const queryUnnamedArg = (value: string, line: number) => queryUnnamedArgument(value, line);
136+
const queryAccInLine = (line: number) => queryAccessInLine(accessType, line);
137+
return { acc, def, accessCapability, queryArg, queryNamedArg, queryUnnamedArg, queryAccInLine };
138+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { describe } from 'vitest';
2+
import { AccessType, ContainerType, setupContainerFunctions } from '../../_helper/pointer-analysis';
3+
import { assertDataflow, withShell } from '../../_helper/shell';
4+
import { label } from '../../_helper/label';
5+
import { emptyGraph } from '../../../../src/dataflow/graph/dataflowgraph-builder';
6+
import { Q } from '../../../../src/search/flowr-search-builder';
7+
8+
describe.sequential('Container Single Index Based Access', withShell(shell => {
9+
describe.each(
10+
[
11+
{ container: ContainerType.Vector, type: AccessType.DoubleBracket, hasNamedArguments: false },
12+
{ container: ContainerType.Vector, type: AccessType.SingleBracket, hasNamedArguments: false },
13+
{ container: ContainerType.List, type: AccessType.DoubleBracket, hasNamedArguments: false },
14+
{ container: ContainerType.List, type: AccessType.SingleBracket, hasNamedArguments: false },
15+
{ container: ContainerType.List, type: AccessType.DoubleBracket, hasNamedArguments: true },
16+
{ container: ContainerType.List, type: AccessType.SingleBracket, hasNamedArguments: true },
17+
{ container: ContainerType.List, type: AccessType.Dollar, hasNamedArguments: true },
18+
]
19+
)('Access for container $container using $type and hasNamedArguments $hasNamedArguments', ({ container, type, hasNamedArguments }) => {
20+
const {
21+
acc, def, accessCapability, queryArg, queryNamedArg, queryUnnamedArg, queryAccInLine
22+
} = setupContainerFunctions(container, type, hasNamedArguments);
23+
24+
const basicCapabilities = [
25+
'name-normal',
26+
'function-calls',
27+
hasNamedArguments ? 'named-arguments' : 'unnamed-arguments',
28+
'subsetting-multiple',
29+
accessCapability,
30+
] as const;
31+
32+
describe('Simple access', () => {
33+
assertDataflow(
34+
label('When single index is accessed, then access reads index', basicCapabilities),
35+
shell,
36+
`numbers <- ${def('1', '2')}
37+
${acc('numbers', 2)}`,
38+
(data) => emptyGraph()
39+
.readsQuery(queryAccInLine(2), queryArg(2, '2', 1), data),
40+
{
41+
expectIsSubgraph: true,
42+
resolveIdsAsCriterion: true,
43+
}
44+
);
45+
46+
describe.skipIf(container !== ContainerType.Vector)('Flattened Vectors', () => {
47+
assertDataflow(
48+
label('When single flattened index is accessed, then access reads index', basicCapabilities),
49+
shell,
50+
`numbers <- ${def('1', 'c(2, 3)', '4')}
51+
${acc('numbers', 2)}`,
52+
(data) => emptyGraph()
53+
.readsQuery(queryAccInLine(2), queryUnnamedArg('2', 1), data),
54+
{
55+
expectIsSubgraph: true,
56+
resolveIdsAsCriterion: true,
57+
}
58+
);
59+
60+
assertDataflow(
61+
label('When single named flattened list index is accessed, then access reads index', [...basicCapabilities, 'named-arguments']),
62+
shell,
63+
`numbers <- ${def('1', 'list(a = 2, b = 3)', '4')}
64+
${acc('numbers', 2)}`,
65+
(data) => emptyGraph()
66+
.readsQuery(queryAccInLine(2), queryNamedArg('a', 1), data),
67+
{
68+
expectIsSubgraph: true,
69+
resolveIdsAsCriterion: true,
70+
}
71+
);
72+
73+
assertDataflow(
74+
label('When single unnamed flattened list index is accessed, then access reads index', basicCapabilities),
75+
shell,
76+
`numbers <- ${def('1', 'list(2, 3)', '4')}
77+
${acc('numbers', 2)}`,
78+
(data) => emptyGraph()
79+
.readsQuery(queryAccInLine(2), queryUnnamedArg('2', 1), data),
80+
{
81+
expectIsSubgraph: true,
82+
resolveIdsAsCriterion: true,
83+
}
84+
);
85+
});
86+
87+
describe.skipIf(container !== ContainerType.List)('Nested Lists', () => {
88+
assertDataflow(
89+
label('When single nested index is accessed, then access reads index', basicCapabilities),
90+
shell,
91+
`numbers <- ${def('1', ['2', '3'], '4')}
92+
${acc('numbers', 2, 1)}`,
93+
(data) => emptyGraph()
94+
.readsQuery({ query: Q.varInLine(type, 2).last() }, queryArg(3, '2', 1), data),
95+
{
96+
expectIsSubgraph: true,
97+
resolveIdsAsCriterion: true,
98+
}
99+
);
100+
});
101+
});
102+
103+
describe('Access with assignment', () => {
104+
assertDataflow(
105+
label('When single index is assigned, then access reads index in assignment and definition', basicCapabilities),
106+
shell,
107+
`numbers <- ${def('1', '2', '3', '4')}
108+
${acc('numbers', 1)} <- 5
109+
${acc('numbers', 1)}`,
110+
(data) => emptyGraph()
111+
.readsQuery(queryAccInLine(3), queryArg(1, '1', 1), data)
112+
.readsQuery(queryAccInLine(3), queryAccInLine(2), data),
113+
{
114+
expectIsSubgraph: true,
115+
resolveIdsAsCriterion: true,
116+
}
117+
);
118+
119+
assertDataflow(
120+
label('When several indices are assigned, then access reads only correct index in assignment and definition', basicCapabilities),
121+
shell,
122+
`numbers <- ${def('1', '2', '3', '4')}
123+
${acc('numbers', 1)} <- 4
124+
${acc('numbers', 2)} <- 3
125+
${acc('numbers', 3)} <- 2
126+
${acc('numbers', 4)} <- 1
127+
${acc('numbers', 1)}`,
128+
(data) => emptyGraph()
129+
.readsQuery(queryAccInLine(6), queryArg(1, '1', 1), data)
130+
.readsQuery(queryAccInLine(6), queryAccInLine(2), data),
131+
// not reads other indices
132+
{
133+
expectIsSubgraph: true,
134+
resolveIdsAsCriterion: true,
135+
}
136+
);
137+
138+
assertDataflow(
139+
label('When container is self-redefined with previous assignment, then indices get passed', basicCapabilities),
140+
shell,
141+
`numbers <- ${def('1', '2')}
142+
numbers <- numbers
143+
${acc('numbers', 1)} <- 1
144+
print(${acc('numbers', 1)})`,
145+
(data) => emptyGraph()
146+
.readsQuery({ query: Q.varInLine('numbers', 2).last() }, { target: '1@numbers' }, data)
147+
.definedByQuery(
148+
{ query: Q.varInLine('numbers', 2).first() },
149+
{ query: Q.varInLine('numbers', 2).last() },
150+
data,
151+
),
152+
{
153+
expectIsSubgraph: true,
154+
resolveIdsAsCriterion: true,
155+
}
156+
);
157+
});
158+
});
159+
}));

test/functionality/dataflow/pointer-analysis/list-name-based-access.test.ts

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)