# tools/core/validators/literal.validator.ts

> 79 lines of code and 22 definitions.

Tree: Coordination tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-validators-literal-validator-ts
Source text: https://banes-lab.com/assets/sources/source.00c00e9e8ab7f6780bbe356931dcb8130ac772438501956f8e612de1d810338a.generated.txt

## Definitions

- `countsIn` (lexical_declaration, line 78)
- `countLiterals` (lexical_declaration, line 85, exported)
- `wordishAt` (lexical_declaration, line 40)
- `wordsIn` (lexical_declaration, line 48)
- `isLetter` (lexical_declaration, line 36)
- `isNumeral` (lexical_declaration, line 65)
- `isDerivedNoun` (lexical_declaration, line 74)
- `CountLiteral` (interface_declaration, line 4, exported)
- `FENCE` (lexical_declaration, line 10)
- `SPAN_DELIMITERS` (lexical_declaration, line 12)
- `SPAN_BREAK` (lexical_declaration, line 14)
- `DECIMAL_POINT` (lexical_declaration, line 16)
- `unspanned` (lexical_declaration, line 18)
- `span` (lexical_declaration, line 20)
- `char` (lexical_declaration, line 41)
- `text` (lexical_declaration, line 49)
- `current` (lexical_declaration, line 51)
- `index` (lexical_declaration, line 53)
- `noun` (lexical_declaration, line 80)
- `out` (lexical_declaration, line 86, exported)
- `fenced` (lexical_declaration, line 87, exported)
- `fence` (lexical_declaration, line 90, exported)

## Used by

- [tools/core/coordinators/checklist.coordinator.ts](https://banes-lab.com/source/coordination/tools/core/coordinators/checklist.coordinator.ts.md)

## Source

````typescript
import { DERIVED_NOUNS } from "../constants/checklist.constants.ts";
import { isDigit } from "../resolvers/checklist.resolver.ts";

export interface CountLiteral {
    readonly line: number;
    readonly numeral: string;
    readonly noun: string;
}

const FENCE = "```";

const SPAN_DELIMITERS = new Set(["`", '"']);

const SPAN_BREAK = " ";

const DECIMAL_POINT = ".";

const unspanned = function unspanned(line: string): string {
    let out = "";
    let span: string | null = null;

    for (const char of line) {
        if (span !== null) {
            span = char === span ? null : span;
        } else if (SPAN_DELIMITERS.has(char)) {
            span = char;
            out += SPAN_BREAK;
        } else {
            out += char;
        }
    }

    return out;
};

const isLetter = function isLetter(char: string): boolean {
    return (char >= "a" && char <= "z") || (char >= "A" && char <= "Z");
};

const wordishAt = function wordishAt(text: string, index: number): boolean {
    const char = text.charAt(index);
    if (char === DECIMAL_POINT) {
        return isDigit(text.charAt(index - 1)) && isDigit(text.charAt(index + 1));
    }
    return isDigit(char) || isLetter(char) || char === "-";
};

const wordsIn = function wordsIn(line: string): string[] {
    const text = unspanned(line);
    const out: string[] = [];
    let current = "";

    for (let index = 0; index < text.length; index += 1) {
        if (wordishAt(text, index)) {
            current += text.charAt(index);
        } else {
            out.push(...(current.length > 0 ? [current] : []));
            current = "";
        }
    }

    return [...out, ...(current.length > 0 ? [current] : [])];
};

const isNumeral = function isNumeral(word: string): boolean {
    for (const char of word) {
        if (!isDigit(char)) {
            return false;
        }
    }
    return word.length > 0;
};

const isDerivedNoun = function isDerivedNoun(word: string): boolean {
    return DERIVED_NOUNS.includes(word.toLowerCase());
};

const countsIn = function countsIn(words: readonly string[], line: number): CountLiteral[] {
    return words.slice(0, -1).flatMap((numeral, position) => {
        const noun = words[position + 1] ?? "";
        return isNumeral(numeral) && isDerivedNoun(noun) ? [{ line, noun, numeral }] : [];
    });
};

export const countLiterals = function countLiterals(lines: readonly string[]): CountLiteral[] {
    const out: CountLiteral[] = [];
    let fenced = false;

    for (const [index, line] of lines.entries()) {
        const fence = line.trimStart().startsWith(FENCE);
        if (!fence && !fenced) {
            out.push(...countsIn(wordsIn(line), index + 1));
        }
        fenced = fence ? !fenced : fenced;
    }

    return out;
};
````
