# tools/core/analyzers/fence.analyzer.ts

> 118 lines of code and 38 definitions.

Tree: Coordination tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-analyzers-fence-analyzer-ts
Source text: https://banes-lab.com/source/coordination/tools/core/analyzers/fence.analyzer.ts.txt

Listed in [tools/core/analyzers](https://banes-lab.com/api/source/coordination/tools/core/analyzers.md), after [tools/core/analyzers/converge.analyzer.ts](https://banes-lab.com/source/coordination/tools/core/analyzers/converge.analyzer.ts.md) and before [tools/core/analyzers/graph.analyzer.ts](https://banes-lab.com/source/coordination/tools/core/analyzers/graph.analyzer.ts.md).

## Definitions

- `delimitersIn` (lexical_declaration, line 67, exported)
- `runEnd` (lexical_declaration, line 28)
- `spansOf` (lexical_declaration, line 81)
- `itemSpanFlags` (lexical_declaration, line 119, exported)
- `OPEN_MARKER` (lexical_declaration, line 4)
- `CLOSE_MARKER` (lexical_declaration, line 5)
- `MARKER_AGENT` (lexical_declaration, line 6)
- `MARKER_END` (lexical_declaration, line 7)
- `ORDINAL_SEPARATOR` (lexical_declaration, line 8)
- `Span` (interface_declaration, line 10)
- `isUpper` (lexical_declaration, line 16)
- `isLower` (lexical_declaration, line 20)
- `isDigit` (lexical_declaration, line 24)
- `cursor` (lexical_declaration, line 29)
- `agentAfter` (lexical_declaration, line 36)
- `at` (lexical_declaration, line 37)
- `start` (lexical_declaration, line 41)
- `upperEnd` (lexical_declaration, line 42)
- `nameEnd` (lexical_declaration, line 46)
- `name` (lexical_declaration, line 47)
- `ordinalEnd` (lexical_declaration, line 51)
- `delimiterOf` (lexical_declaration, line 55)
- `agent` (lexical_declaration, line 61)
- `out` (lexical_declaration, line 68, exported)
- `fenced` (lexical_declaration, line 69, exported)
- `delimiter` (lexical_declaration, line 72, exported)
- `open` (lexical_declaration, line 83)
- `from` (lexical_declaration, line 90)
- `enclosingRecord` (lexical_declaration, line 100, exported)
- `spans` (lexical_declaration, line 101, exported)
- `held` (lexical_declaration, line 104, exported)
- `width` (lexical_declaration, line 105, exported)
- `size` (lexical_declaration, line 108, exported)
- `flags` (lexical_declaration, line 120, exported)
- `openAt` (lexical_declaration, line 121, exported)
- `openKey` (lexical_declaration, line 122, exported)
- `isItem` (lexical_declaration, line 125, exported)
- `closesOpen` (lexical_declaration, line 126, exported)

## Uses

- [tools/core/runners/fixture.runner.ts](https://banes-lab.com/source/coordination/tools/core/runners/fixture.runner.ts.md)

## Used by

- [tools/core/analyzers/snapshot.analyzer.ts](https://banes-lab.com/source/coordination/tools/core/analyzers/snapshot.analyzer.ts.md)
- [tools/core/formatters/board.formatter.ts](https://banes-lab.com/source/coordination/tools/core/formatters/board.formatter.ts.md)
- [tools/core/inspectors/fence.inspector.ts](https://banes-lab.com/source/coordination/tools/core/inspectors/fence.inspector.ts.md)
- [tools/core/validators/venue.validator.ts](https://banes-lab.com/source/coordination/tools/core/validators/venue.validator.ts.md)

## Source

```typescript
import type { Delimiter } from "../types/board.types.ts";
import { fencedFlags } from "../predicates/fence.predicate.ts";

const OPEN_MARKER = "┌";
const CLOSE_MARKER = "└";
const MARKER_AGENT = "AGENT ";
const MARKER_END = "END AGENT ";
const ORDINAL_SEPARATOR = "-";

interface Span {
    readonly agent: string;
    readonly from: number;
    readonly to: number;
}

const isUpper = function isUpper(char: string): boolean {
    return char >= "A" && char <= "Z";
};

const isLower = function isLower(char: string): boolean {
    return char >= "a" && char <= "z";
};

const isDigit = function isDigit(char: string): boolean {
    return char >= "0" && char <= "9";
};

const runEnd = function runEnd(text: string, from: number, accepts: (char: string) => boolean): number {
    let cursor = from;
    while (cursor < text.length && accepts(text.charAt(cursor))) {
        cursor += 1;
    }
    return cursor;
};

const agentAfter = function agentAfter(text: string, marker: string): string | null {
    const at = text.indexOf(marker);
    if (at === -1) {
        return null;
    }
    const start = at + marker.length;
    const upperEnd = runEnd(text, start, isUpper);
    if (upperEnd === start) {
        return null;
    }
    const nameEnd = runEnd(text, upperEnd, isLower);
    const name = text.slice(start, nameEnd);
    if (text.charAt(nameEnd) !== ORDINAL_SEPARATOR) {
        return name;
    }
    const ordinalEnd = runEnd(text, nameEnd + 1, isDigit);
    return ordinalEnd === nameEnd + 1 ? name : `${name}${ORDINAL_SEPARATOR}${text.slice(nameEnd + 1, ordinalEnd)}`;
};

const delimiterOf = function delimiterOf(line: string, index: number): Delimiter | null {
    if (line.startsWith(OPEN_MARKER)) {
        const agent = agentAfter(line, MARKER_AGENT);
        return agent === null ? null : { agent, line: index + 1, open: true };
    }
    if (line.startsWith(CLOSE_MARKER)) {
        const agent = agentAfter(line, MARKER_END);
        return agent === null ? null : { agent, line: index + 1, open: false };
    }
    return null;
};

export const delimitersIn = function delimitersIn(source: string): Delimiter[] {
    const out: Delimiter[] = [];
    const fenced = fencedFlags(source);

    for (const [index, line] of source.split("\n").entries()) {
        const delimiter = fenced[index] === true ? null : delimiterOf(line.trim(), index);
        if (delimiter !== null) {
            out.push(delimiter);
        }
    }

    return out;
};

const spansOf = function spansOf(source: string): Span[] {
    const spans: Span[] = [];
    const open = new Map<string, number>();

    for (const delimiter of delimitersIn(source)) {
        if (delimiter.open) {
            open.set(delimiter.agent, delimiter.line);
            continue;
        }
        const from = open.get(delimiter.agent);
        if (from !== undefined) {
            open.delete(delimiter.agent);
            spans.push({ agent: delimiter.agent, from, to: delimiter.line });
        }
    }

    return spans;
};

export const enclosingRecord = function enclosingRecord(source: string): (line: number) => string | null {
    const spans = spansOf(source);

    return (line: number): string | null => {
        let held: string | null = null;
        let width = Number.MAX_SAFE_INTEGER;

        for (const span of spans) {
            const size = span.to - span.from;
            if (line >= span.from && line <= span.to && size < width) {
                width = size;
                held = span.agent;
            }
        }

        return held;
    };
};

export const itemSpanFlags = function itemSpanFlags(source: string): boolean[] {
    const flags = source.split("\n").map(() => false);
    let openAt = -1;
    let openKey = "";

    for (const mark of delimitersIn(source)) {
        const isItem = mark.agent.includes(ORDINAL_SEPARATOR);
        const closesOpen = isItem && !mark.open && openAt !== -1 && mark.agent === openKey;
        if (isItem && mark.open && openAt === -1) {
            openAt = mark.line;
            openKey = mark.agent;
        }
        if (closesOpen) {
            flags.fill(true, openAt, mark.line - 1);
            openAt = -1;
            openKey = "";
        }
    }

    return flags;
};
```
