# core/parsers/field.parser.ts

> 116 lines of code and 31 definitions.

Tree: GovLab Patterns
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/patterns#file-patterns-core-parsers-field-parser-ts
Source text: https://banes-lab.com/source/patterns/core/parsers/field.parser.ts.txt

Listed in [core/parsers](https://banes-lab.com/api/source/patterns/core/parsers.md), after [core/parsers/dependency.parser.ts](https://banes-lab.com/source/patterns/core/parsers/dependency.parser.ts.md) and before [core/parsers/schema.parser.ts](https://banes-lab.com/source/patterns/core/parsers/schema.parser.ts.md).

## Definitions

- `scanFloatFields` (lexical_declaration, line 120, exported)
- `stepScan` (lexical_declaration, line 109)
- `handleNumber` (lexical_declaration, line 64)
- `numberTokenIsFloat` (lexical_declaration, line 31, exported)
- `appendChar` (lexical_declaration, line 40)
- `handleString` (lexical_declaration, line 75)
- `stepStructural` (lexical_declaration, line 86)
- `readString` (lexical_declaration, line 47)
- `FLOAT_MARKERS` (lexical_declaration, line 1)
- `DIGITS` (lexical_declaration, line 2)
- `NUMBER_START` (lexical_declaration, line 3)
- `NUMBER_BODY` (lexical_declaration, line 4)
- `WHITESPACE` (lexical_declaration, line 5)
- `STRUCTURE_OPEN` (lexical_declaration, line 6)
- `STRUCTURE_CLOSE` (lexical_declaration, line 7)
- `KEY_SEPARATOR` (lexical_declaration, line 8)
- `ITEM_SEPARATOR` (lexical_declaration, line 9)
- `QUOTE` (lexical_declaration, line 10)
- `ESCAPE` (lexical_declaration, line 11)
- `ESCAPE_SKIP` (lexical_declaration, line 12)
- `StringSpan` (interface_declaration, line 14)
- `LineState` (interface_declaration, line 19)
- `ScanContext` (interface_declaration, line 25)
- `value` (lexical_declaration, line 49)
- `readNumber` (lexical_declaration, line 56)
- `{ state, floats, recordDepth }` (lexical_declaration, line 65)
- `{ value, next }` (lexical_declaration, line 76)
- `symbol` (lexical_declaration, line 110)
- `floats` (lexical_declaration, line 121, exported)
- `context` (lexical_declaration, line 122, exported)
- `index` (lexical_declaration, line 123, exported)

## Contained in

- [core/parsers](https://banes-lab.com/anatomy/patterns/folder-patterns-core-parsers.md)

## Used by

- [core/loaders/record.loader.ts](https://banes-lab.com/source/patterns/core/loaders/record.loader.ts.md)

## Linked from

- [core/loaders](https://banes-lab.com/anatomy/patterns/folder-patterns-core-loaders.md)
- [core/parsers](https://banes-lab.com/anatomy/patterns/folder-patterns-core-parsers.md)

## Source

```typescript
const FLOAT_MARKERS: ReadonlySet<string> = new Set([".", "e", "E"]);
const DIGITS: readonly string[] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
const NUMBER_START: ReadonlySet<string> = new Set(["-", ...DIGITS]);
const NUMBER_BODY: ReadonlySet<string> = new Set(["-", "+", ".", "e", "E", ...DIGITS]);
const WHITESPACE: ReadonlySet<string> = new Set([" ", "\t", "\r", "\n"]);
const STRUCTURE_OPEN: ReadonlySet<string> = new Set(["{", "["]);
const STRUCTURE_CLOSE: ReadonlySet<string> = new Set(["}", "]"]);
const KEY_SEPARATOR = ":";
const ITEM_SEPARATOR = ",";
const QUOTE = '"';
const ESCAPE = "\\";
const ESCAPE_SKIP = 2;

interface StringSpan {
    value: string;
    next: number;
}

interface LineState {
    depth: number;
    pendingKey: string | null;
    expectValue: boolean;
}

interface ScanContext {
    state: LineState;
    floats: Set<string>;
    recordDepth: number;
}

export const numberTokenIsFloat = function numberTokenIsFloat(token: string): boolean {
    for (const symbol of token) {
        if (FLOAT_MARKERS.has(symbol)) {
            return true;
        }
    }
    return false;
};

const appendChar = function appendChar(line: string, index: number, value: string): StringSpan {
    if (line[index] === ESCAPE) {
        return { next: index + ESCAPE_SKIP, value: value + (line[index + 1] ?? "") };
    }
    return { next: index + 1, value: value + (line[index] ?? "") };
};

const readString = function readString(line: string, start: number): StringSpan {
    let index = start + 1;
    let value = "";
    while (index < line.length && line[index] !== QUOTE) {
        ({ next: index, value } = appendChar(line, index, value));
    }
    return { next: index < line.length ? index + 1 : index, value };
};

const readNumber = function readNumber(line: string, start: number): StringSpan {
    let index = start;
    while (index < line.length && NUMBER_BODY.has(line[index] ?? "")) {
        index += 1;
    }
    return { next: index, value: line.slice(start, index) };
};

const handleNumber = function handleNumber(line: string, index: number, context: ScanContext): number {
    const { state, floats, recordDepth } = context;
    const { value, next } = readNumber(line, index);
    if (state.depth === recordDepth && state.pendingKey !== null && numberTokenIsFloat(value)) {
        floats.add(state.pendingKey);
    }
    state.expectValue = false;
    state.pendingKey = null;
    return next;
};

const handleString = function handleString(line: string, index: number, state: LineState): number {
    const { value, next } = readString(line, index);
    if (state.expectValue) {
        state.expectValue = false;
        state.pendingKey = null;
    } else {
        state.pendingKey = value;
    }
    return next;
};

const stepStructural = function stepStructural(symbol: string, state: LineState, index: number): number {
    if (symbol === KEY_SEPARATOR) {
        state.expectValue = true;
        return index + 1;
    }
    if (STRUCTURE_OPEN.has(symbol)) {
        state.depth += 1;
        state.expectValue = false;
        return index + 1;
    }
    if (STRUCTURE_CLOSE.has(symbol)) {
        state.depth -= 1;
        return index + 1;
    }
    if (symbol === ITEM_SEPARATOR) {
        state.pendingKey = null;
    }
    if (!WHITESPACE.has(symbol)) {
        state.expectValue = false;
    }
    return index + 1;
};

const stepScan = function stepScan(text: string, index: number, context: ScanContext): number {
    const symbol = text[index] ?? "";
    if (symbol === QUOTE) {
        return handleString(text, index, context.state);
    }
    if (context.state.expectValue && NUMBER_START.has(symbol)) {
        return handleNumber(text, index, context);
    }
    return stepStructural(symbol, context.state, index);
};

export const scanFloatFields = function scanFloatFields(text: string, recordDepth: number): Set<string> {
    const floats = new Set<string>();
    const context: ScanContext = { floats, recordDepth, state: { depth: 0, expectValue: false, pendingKey: null } };
    let index = 0;
    while (index < text.length) {
        index = stepScan(text, index, context);
    }
    return floats;
};
```
