Skip to content

Commit

Permalink
Added more docs
Browse files Browse the repository at this point in the history
  • Loading branch information
tmr232 committed Dec 4, 2024
1 parent f4d9639 commit 17e6fdc
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 15 deletions.
77 changes: 72 additions & 5 deletions src/control-flow/cfg-defs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export type NodeType =
| "SWITCH_CONDITION"
| "SWITCH_MERGE"
| "CASE_CONDITION";

export type EdgeType = "regular" | "consequence" | "alternative" | "exception";

export type ClusterType =
Expand Down Expand Up @@ -64,41 +65,107 @@ export interface GraphEdge {
type: EdgeType;
}

/**
* Represents a `goto` statement.
*/
export interface Goto {
/**
* The name of the label to jump to
*/
label: string;
/**
* The node containing the `goto` statement
*/
node: string;
}

/**
* The `BasicBlock` is used in the process of building a CFG.
* It is meant to represent a "statement" or "block" of source-code.
*
* It allows us to abstract over individual CFG nodes and work with something
* that's closer to the source code.
*
* `BasicBlocks`s can be nested, just like code structures.
* If we have a `BasicBlock` representing an `if` statement, it will have
* `BasicBlock`s for the individual statements nested within it.
*
* Each `BasicBlock` can span many CFG nodes.
*/
export interface BasicBlock {
/**
* The ID of the entry node.
*/
entry: string;
/**
* Normal exit node, if exists.
*
* This would be the next statement after the current one in the source code.
* For control-flow statements (like `return`, `break`, etc.) we won't have
* an `exit`, as the control-flow doesn't reach it.
* Similarly, blocks ending with a flow-altering statement won't have an exit.
*/
exit: string | null;
/**
* The active `continue` nodes within this block.
*
* A `continue` is active if it's target is outside the current block.
*/
continues?: string[];
/**
* The active `break` nodes within this block.
*/
breaks?: string[];
// From label name to node
/**
* All the labels within this block.
*
* The mapping is from the label's name to the labeled node.
*/
labels?: Map<string, string>;
// Target label
/**
* All the active `goto`s within this block.
*
* Active `goto`s are one that were not yet resolved.
*/
gotos?: Goto[];
// Return statements in the block. Needed for exception handling.
/**
* All the `return` statements within the block.
* As the CFG is a single-function graph, `return` statements are never
* resolved within it.
*/
returns?: string[];
}

export type CFGGraph = MultiDirectedGraph<GraphNode, GraphEdge>;

/**
* The full CFG structure.
*/
export interface CFG {
/**
* The graph itself
*/
graph: CFGGraph;
/**
* The function's entry node
*/
entry: string;
/**
* A mapping between source-code offsets and their matching CFG nodes
*/
offsetToNode: SimpleRange<string>[];
}

export class BlockHandler {
private breaks: string[] = [];
private continues: string[] = [];
private labels: Map<string, string> = new Map();
private gotos: Array<{ label: string; node: string }> = [];
private gotos: Array<Goto> = [];
/**
* All the returns encountered so far.
*
* This is needed for `finally` clauses in exception handling,
* as the return is moved/duplicated to the end of the finally clause.
* as the return is moved/duplicated to the end of the finally-clause.
* This means that when processing returns, we expect to get a new set
* of returns.
*/
Expand Down
6 changes: 3 additions & 3 deletions src/control-flow/generic-cfg-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ export interface Context {
/**
* A function that converts an AST node to a CFG basic-block.
*
* @param {Parser.SyntaxNode} syntax - The AST node to be processed
* @param {Context} ctx - The context in which the statement is being handled
* @returns {BasicBlock} A basic block representation of the AST node
* @param syntax The AST node to be processed
* @param ctx The context in which the statement is being handled
* @returns A basic block representation of the AST node
*/
export type StatementHandler = (
syntax: Parser.SyntaxNode,
Expand Down
33 changes: 29 additions & 4 deletions src/control-flow/graph-ops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,35 @@ export function distanceFromEntry(cfg: CFG): Map<string, number> {
return levels;
}

/// Can return null to indicate that the merge is not allowed.
/// The function MUST NOT modify the input arguments.
/**
* Merges the attributes of two nodes, or aborts the merge.
*
* The function MUST NOT modify the input arguments.
*
* @param nodeAttrs the node to disappear
* @param intoAttrs the node accepting the new attributes
* @returns the new node attribute if the merge is successful, or `null` to abort it.
*/
export type AttrMerger = (
nodeAttrs: GraphNode,
intoAttrs: GraphNode,
) => GraphNode | null;

/**
* Collapses one node into another, migrating all relevant edges and merging
* the node attributes.
*
* @param graph The graph to collapse in
* @param node Node to collapse
* @param into Collapse target
* @param mergeAttrs Controls the merger of attributes. Can prevent collapsing.
*/
function collapseNode(
graph: MultiDirectedGraph<GraphNode>,
node: string,
into: string,
mergeAttrs?: AttrMerger,
) {
): void {
if (mergeAttrs) {
const attrs = mergeAttrs(
graph.getNodeAttributes(node),
Expand Down Expand Up @@ -55,8 +72,10 @@ function collapseNode(
graph.dropNode(node);
}
/**
* Simplify the CFG by removing "trivial" nodes.
*
* @param cfg The graph to simplify
* Trivial nodes are nodes that do not contribute to the branching structure of
* the CFG.
*/
export function simplifyCFG(cfg: CFG, mergeAttrs?: AttrMerger): CFG {
const graph = cfg.graph.copy();
Expand Down Expand Up @@ -90,6 +109,12 @@ export function simplifyCFG(cfg: CFG, mergeAttrs?: AttrMerger): CFG {
return evolve(cfg, { graph, entry });
}

/**
* Remove all nodes not reachable from the CFG's entry
*
* @param cfg The CFG to trim
* @returns a copy of the CFG, with the unreachable nodes removed
*/
export function trimFor(cfg: CFG): CFG {
const { graph, entry } = cfg;
const reachable: string[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/control-flow/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ function renderEdge(
// For backlinks, we use `dir=back` to improve the layout.
// This tells DOT that this is a backlink, and changes the ranking of nodes.
dotAttrs.dir = "back";
// To accomodate that, we also flip the node order and the ports.
// To accommodate that, we also flip the node order and the ports.
[source, target] = [target, source];
dotAttrs.headport = "s";
dotAttrs.tailport = "n";
Expand Down
70 changes: 69 additions & 1 deletion src/control-flow/zip.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,92 @@
/**
* Zips multiple arrays together, creating tuples of elements at corresponding indices.
* Stops iterating when the shortest input array is exhausted.
*
* @template T An array of input arrays with potentially different element types
* @param arrays Multiple input arrays to be zipped together
* @returns A generator yielding tuples containing one element from each input array
*
* @example
* const nums = [1, 2, 3];
* const strs = ['a', 'b', 'c'];
* [...zip(nums, strs)] // [[1, 'a'], [2, 'b'], [3, 'c']]
*/
export function* zip<T extends unknown[]>(
...arrays: [...{ [K in keyof T]: T[K][] }]
): Generator<T, void, unknown> {
// Calculate the length of the shortest input array
const lengths = arrays.map((array) => array.length);
const zipLength = Math.min(...lengths);

// Yield tuples of elements at each index
for (let i = 0; i < zipLength; ++i) {
yield arrays.map((array) => array[i]) as T;
}
}

/**
* Zips an object of arrays, creating objects with corresponding elements.
* Stops iterating when the shortest input array is exhausted.
*
* @template T An object containing arrays of potentially different types
* @param arrays Object with array values to be zipped
* @returns A generator yielding objects with one element from each input array
*
* @example
* const data = {
* names: ['Alice', 'Bob'],
* ages: [30, 25],
* cities: ['New York', 'London']
* };
* [...structZip(data)]
* // [{ names: 'Alice', ages: 30, cities: 'New York' },
* // { names: 'Bob', ages: 25, cities: 'London' }]
*/
export function* structZip<T extends Record<string, unknown[]>>(
arrays: T,
): Generator<
{ [K in keyof T]: T[K] extends Array<infer U> ? U : never },
void,
unknown
> {
// Calculate the length of the shortest input array
const lengths = Object.values(arrays).map((array) => array.length);
const zipLength = Math.min(...lengths);

// Yield objects with elements at each index
for (let i = 0; i < zipLength; ++i) {
yield Object.fromEntries(
Object.entries(arrays).map(([name, array]) => [name, array[i]]),
) as { [K in keyof T]: T[K] extends Array<infer U> ? U : never };
}
}

/**
* Converts an optional value to an array, handling undefined cases.
*
* @template T The type of the input value
* @param value Optional input value
* @returns An array containing the value, or an empty array if undefined
*
* @example
* maybe(5) // [5]
* maybe(undefined) // []
*/
export function maybe<T>(value: T | undefined): T[] {
if (value === undefined) return [];
return [value];
}

/**
* Generates adjacent pairs from an input array.
*
* @template T The type of elements in the input array
* @param items Input array to generate pairs from
* @returns A generator yielding consecutive pairs of elements
*
* @example
* [...pairwise([1, 2, 3, 4])] // [[1,2], [2,3], [3,4]]
*/
export function* pairwise<T>(items: T[]): IterableIterator<[T, T]> {
const iterator = items[Symbol.iterator]();
let { value: a } = iterator.next();
Expand All @@ -41,7 +99,17 @@ export function* pairwise<T>(items: T[]): IterableIterator<[T, T]> {
}
}

export function* chain<T>(...arrays: T[][]): IterableIterator<T> {
/**
* Chains multiple arrays into a single iterable sequence.
*
* @template T The type of elements in the input arrays
* @param arrays Multiple input arrays to be chained together
* @returns A generator yielding all elements from input arrays in order
*
* @example
* [...chain([1, 2], [3, 4], [5, 6])] // [1, 2, 3, 4, 5, 6]
*/
export function* chain<T>(...arrays: ReadonlyArray<T[]>): IterableIterator<T> {
for (const array of arrays) {
yield* array;
}
Expand Down
3 changes: 2 additions & 1 deletion typedoc.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"hostedBaseUrl": "https://tmr232.github.io/function-graph-overview/docs",
"entryPoints": ["src/control-flow/*"],
"readme": "docs/Development.md",
"name": "Function Graph Overview"
"name": "Function Graph Overview",
"sort": ["source-order"]
}

0 comments on commit 17e6fdc

Please sign in to comment.