import { Location, Module, parse } from './python-parser'; import { ControlFlowGraph } from './control-flow'; import { DataflowAnalyzer } from './data-flow'; import { NumberSet, range, Set } from './set'; function lineRange(loc: Location): NumberSet { return range(loc.first_line, loc.last_line + (loc.last_column ? 1 : 0)); } export class LocationSet extends Set { constructor(...items: Location[]) { super( l => [l.first_line, l.first_column, l.last_line, l.last_column].toString(), ...items ); } } function within(inner: Location, outer: Location): boolean { let leftWithin = outer.first_line < inner.first_line || (outer.first_line == inner.first_line && outer.first_column <= inner.first_column); let rightWithin = outer.last_line > inner.last_line || (outer.last_line == inner.last_line && outer.last_column >= inner.last_column); return leftWithin && rightWithin; } function isPositionBetween( line: number, column: number, start_line: number, start_column: number, end_line: number, end_column: number ) { let afterStart = line > start_line || (line == start_line && column >= start_column); let beforeEnd = line < end_line || (line == end_line && column <= end_column); return afterStart && beforeEnd; } function intersect(l1: Location, l2: Location): boolean { return ( isPositionBetween( l1.first_line, l1.first_column, l2.first_line, l2.first_column, l2.last_line, l2.last_column ) || isPositionBetween( l1.last_line, l1.last_column, l2.first_line, l2.first_column, l2.last_line, l2.last_column ) || within(l1, l2) || within(l2, l1) ); } export enum SliceDirection { Forward, Backward } /** * More general slice: given locations of important syntax nodes, find locations of all relevant * definitions. Locations can be mapped to lines later. * seedLocations are symbol locations. */ export function slice( ast: Module, seedLocations?: LocationSet, dataflowAnalyzer?: DataflowAnalyzer, direction = SliceDirection.Backward ): LocationSet { dataflowAnalyzer = dataflowAnalyzer || new DataflowAnalyzer(); const cfg = new ControlFlowGraph(ast); const dfa = dataflowAnalyzer.analyze(cfg).dataflows; // Include at least the full statements for each seed. let acceptLocation = (loc: Location) => true; let sliceLocations = new LocationSet(); if (seedLocations) { let seedStatementLocations = findSeedStatementLocations(seedLocations, cfg); acceptLocation = loc => seedStatementLocations.some(seedStmtLoc => intersect(seedStmtLoc, loc)); sliceLocations = new LocationSet(...seedStatementLocations.items); } let lastSize: number; do { lastSize = sliceLocations.size; for (let flow of dfa.items) { const [start, end] = direction === SliceDirection.Backward ? [flow.fromNode.location, flow.toNode.location] : [flow.toNode.location, flow.fromNode.location]; if (acceptLocation(end)) { sliceLocations.add(end); } if (sliceLocations.some(loc => within(end, loc))) { sliceLocations.add(start); } } } while (sliceLocations.size > lastSize); return sliceLocations; } function findSeedStatementLocations(seedLocations: LocationSet, cfg: ControlFlowGraph) { let seedStatementLocations = new LocationSet(); seedLocations.items.forEach(seedLoc => { for (let block of cfg.blocks) { for (let statement of block.statements) { if (intersect(seedLoc, statement.location)) { seedStatementLocations.add(statement.location); } } } }); return seedStatementLocations; } /** * Slice: given a set of lines in a program, return lines it depends on. * OUT OF DATE: use slice() instead of sliceLines(). */ export function sliceLines(code: string, relevantLineNumbers: NumberSet) { const ast = parse(code); const cfg = new ControlFlowGraph(ast); let dataflowAnalyzer = new DataflowAnalyzer(); const dfa = dataflowAnalyzer.analyze(cfg).dataflows; let lastSize: number; do { lastSize = relevantLineNumbers.size; for (let flow of dfa.items) { const fromLines = lineRange(flow.fromNode.location); const toLines = lineRange(flow.toNode.location); const startLines = toLines; const endLines = fromLines; if (!relevantLineNumbers.intersect(startLines).empty) { relevantLineNumbers = relevantLineNumbers.union(endLines); } } } while (relevantLineNumbers.size > lastSize); return relevantLineNumbers; }