Skip to content

Commit 6fc92b6

Browse files
test: parameterize pointer analysis dataflow tests
1 parent bb51df2 commit 6fc92b6

File tree

7 files changed

+316
-517
lines changed

7 files changed

+316
-517
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: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { afterAll, beforeAll, 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+
import { amendConfig, defaultConfigOptions } from '../../../../src/config';
8+
9+
describe.sequential('Container Single Index Based Access', withShell(shell => {
10+
describe.each(
11+
[
12+
{ container: ContainerType.Vector, type: AccessType.DoubleBracket, hasNamedArguments: false },
13+
{ container: ContainerType.Vector, type: AccessType.SingleBracket, hasNamedArguments: false },
14+
{ container: ContainerType.List, type: AccessType.DoubleBracket, hasNamedArguments: false },
15+
{ container: ContainerType.List, type: AccessType.SingleBracket, hasNamedArguments: false },
16+
{ container: ContainerType.List, type: AccessType.DoubleBracket, hasNamedArguments: true },
17+
{ container: ContainerType.List, type: AccessType.SingleBracket, hasNamedArguments: true },
18+
{ container: ContainerType.List, type: AccessType.Dollar, hasNamedArguments: true },
19+
]
20+
)('Access for container $container using $type and hasNamedArguments $hasNamedArguments', ({ container, type, hasNamedArguments }) => {
21+
const {
22+
acc, def, accessCapability, queryArg, queryNamedArg, queryUnnamedArg, queryAccInLine
23+
} = setupContainerFunctions(container, type, hasNamedArguments);
24+
25+
const basicCapabilities = [
26+
'name-normal',
27+
'function-calls',
28+
hasNamedArguments ? 'named-arguments' : 'unnamed-arguments',
29+
'subsetting-multiple',
30+
accessCapability,
31+
] as const;
32+
33+
beforeAll(() => {
34+
amendConfig({ solver: { ...defaultConfigOptions.solver, pointerTracking: true } });
35+
});
36+
37+
afterAll(() => {
38+
amendConfig({ solver: { ...defaultConfigOptions.solver, pointerTracking: false } });
39+
});
40+
41+
describe('Simple access', () => {
42+
assertDataflow(
43+
label('When single index is accessed, then access reads index', basicCapabilities),
44+
shell,
45+
`numbers <- ${def('1', '2')}
46+
${acc('numbers', 2)}`,
47+
(data) => emptyGraph()
48+
.readsQuery(queryAccInLine(2), queryArg(2, '2', 1), data),
49+
{
50+
expectIsSubgraph: true,
51+
resolveIdsAsCriterion: true,
52+
}
53+
);
54+
55+
describe.skipIf(container !== ContainerType.Vector)('Flattened Vectors', () => {
56+
assertDataflow(
57+
label('When single flattened index is accessed, then access reads index', basicCapabilities),
58+
shell,
59+
`numbers <- ${def('1', 'c(2, 3)', '4')}
60+
${acc('numbers', 2)}`,
61+
(data) => emptyGraph()
62+
.readsQuery(queryAccInLine(2), queryUnnamedArg('2', 1), data),
63+
{
64+
expectIsSubgraph: true,
65+
resolveIdsAsCriterion: true,
66+
}
67+
);
68+
69+
assertDataflow(
70+
label('When single named flattened list index is accessed, then access reads index', [...basicCapabilities, 'named-arguments']),
71+
shell,
72+
`numbers <- ${def('1', 'list(a = 2, b = 3)', '4')}
73+
${acc('numbers', 2)}`,
74+
(data) => emptyGraph()
75+
.readsQuery(queryAccInLine(2), queryNamedArg('a', 1), data),
76+
{
77+
expectIsSubgraph: true,
78+
resolveIdsAsCriterion: true,
79+
}
80+
);
81+
82+
assertDataflow(
83+
label('When single unnamed flattened list index is accessed, then access reads index', basicCapabilities),
84+
shell,
85+
`numbers <- ${def('1', 'list(2, 3)', '4')}
86+
${acc('numbers', 2)}`,
87+
(data) => emptyGraph()
88+
.readsQuery(queryAccInLine(2), queryUnnamedArg('2', 1), data),
89+
{
90+
expectIsSubgraph: true,
91+
resolveIdsAsCriterion: true,
92+
}
93+
);
94+
});
95+
96+
describe.skipIf(container !== ContainerType.List)('Nested Lists', () => {
97+
assertDataflow(
98+
label('When single nested index is accessed, then access reads index', basicCapabilities),
99+
shell,
100+
`numbers <- ${def('1', ['2', '3'], '4')}
101+
${acc('numbers', 2, 1)}`,
102+
(data) => emptyGraph()
103+
.readsQuery({ query: Q.varInLine(type, 2).last() }, queryArg(3, '2', 1), data),
104+
{
105+
expectIsSubgraph: true,
106+
resolveIdsAsCriterion: true,
107+
}
108+
);
109+
});
110+
});
111+
112+
describe('Access with assignment', () => {
113+
assertDataflow(
114+
label('When single index is assigned, then access reads index in assignment and definition', basicCapabilities),
115+
shell,
116+
`numbers <- ${def('1', '2', '3', '4')}
117+
${acc('numbers', 1)} <- 5
118+
${acc('numbers', 1)}`,
119+
(data) => emptyGraph()
120+
.readsQuery(queryAccInLine(3), queryArg(1, '1', 1), data)
121+
.readsQuery(queryAccInLine(3), queryAccInLine(2), data),
122+
{
123+
expectIsSubgraph: true,
124+
resolveIdsAsCriterion: true,
125+
}
126+
);
127+
128+
assertDataflow(
129+
label('When several indices are assigned, then access reads only correct index in assignment and definition', basicCapabilities),
130+
shell,
131+
`numbers <- ${def('1', '2', '3', '4')}
132+
${acc('numbers', 1)} <- 4
133+
${acc('numbers', 2)} <- 3
134+
${acc('numbers', 3)} <- 2
135+
${acc('numbers', 4)} <- 1
136+
${acc('numbers', 1)}`,
137+
(data) => emptyGraph()
138+
.readsQuery(queryAccInLine(6), queryArg(1, '1', 1), data)
139+
.readsQuery(queryAccInLine(6), queryAccInLine(2), data),
140+
// not reads other indices
141+
{
142+
expectIsSubgraph: true,
143+
resolveIdsAsCriterion: true,
144+
}
145+
);
146+
147+
assertDataflow(
148+
label('When container is self-redefined with previous assignment, then indices get passed', basicCapabilities),
149+
shell,
150+
`numbers <- ${def('1', '2')}
151+
numbers <- numbers
152+
${acc('numbers', 1)} <- 1
153+
print(${acc('numbers', 1)})`,
154+
(data) => emptyGraph()
155+
.readsQuery({ query: Q.varInLine('numbers', 2).last() }, { target: '1@numbers' }, data)
156+
.definedByQuery(
157+
{ query: Q.varInLine('numbers', 2).first() },
158+
{ query: Q.varInLine('numbers', 2).last() },
159+
data,
160+
),
161+
{
162+
expectIsSubgraph: true,
163+
resolveIdsAsCriterion: true,
164+
}
165+
);
166+
});
167+
});
168+
}));

0 commit comments

Comments
 (0)