# Core — Context

The core is the pure engine of the language server. It has **no dependency on VS Code**.
It can run in any Node.js environment — the IDE extension and the CLI both consume it.

---

## What Lives Here

```text
core/src/
├── parser.js                  ← Tokenises Architect files into a structured document model
├── blockValidator.js          ← Validates block-level structure
├── plainTextDocument.js       ← Adapter: presents a plain string as a parseable document
├── suppressionParser.js       ← Reads // @rule-ignore ACU-XXX-NNN inline comments
├── constants.js               ← Shared string constants (token types, section names, etc.)
├── logger.js                  ← Simple file/console logger (no vscode.window dependency)
├── telemetry.js               ← Usage event collection (no vscode dependency)
├── analysis/
│   ├── executionContext.js    ← Builds scope/execution context from parsed document
│   ├── functionAnalysis.js    ← Analyses function definitions and call sites
│   ├── functionResolution.js  ← Resolves function references across includes
│   └── macroAnalysis.js       ← Analyses macro declarations and invocations
├── builtins/
│   ├── constantsRegistry.js   ← Registry of Architect built-in constants
│   ├── functions.js           ← Raw function definitions (names, params, return types)
│   ├── functionsRegistry.js   ← Queryable wrapper over functions.js
│   └── helpers.js             ← Shared utilities for registry lookups
├── diagnostics/
│   ├── diagnosticCatalog.js   ← Master list of all rule IDs, severities, descriptions
│   ├── diagnosticFactory.js   ← Constructs Diagnostic objects (uses vscode-compatible types)
│   └── quickFixCatalog.js     ← Maps rule IDs to available quick fix strategies
├── includeResolver/
│   ├── cache.js               ← Parsed document cache keyed by file path
│   ├── graph.js               ← Dependency graph of INCLUDE relationships
│   └── traversal.js           ← Walks the include graph for cross-file lookups
├── includeResolver.js         ← Public facade over the includeResolver/ internals
├── robodoc/
│   └── robodocUtils.js        ← Parses Robodoc-style documentation comment blocks
├── types/
│   └── acurityTypes.js        ← Type constants (STRING, LONG, DATE, etc.)
└── validators/                ← 15 independent rule engines (see below)
```

---

## The Validators

Each validator is a self-contained module that receives a parsed document and returns
an array of diagnostics. They do not call VS Code APIs directly — they use
`diagnosticFactory.js` which produces plain objects compatible with vscode types.

| File | Rule domain | ID prefix |
| --- | --- | --- |
| `functionValidator.js` | Function call resolution, scope, case, declarations | `ACU-FNC` |
| `functionReturnTypeValidator.js` | Return type correctness for user-defined functions | `ACU-FNC` |
| `argumentTypeValidator.js` | Argument type matching for built-in and user-defined functions | `ACU-FNC` |
| `functionWhitespaceValidator.js` | Whitespace rules at function call sites | `ACU-FNC` |
| `outputDirectiveValidator.js` | Output-positioning directives (CENTER, COL, FROM, TAB, TO) | `ACU-OUT` |
| `optionValidator.js` | Option directive validation (ICC, OICC, CICC characters) | `ACU-OPT` |
| `typeValidator.js` | Assignment and expression type mismatches | `ACU-TYPE` |
| `typeKeywordCapsValidator.js` | Type keyword capitalisation (STRING, SHORT, etc.) | `ACU-TYPE` |
| `discouragedTypeValidator.js` | Discouraged type usage | `ACU-TYPE` |
| `variableValidator.js` | Undeclared variables, scope violations, field handles | `ACU-VAR` |
| `interpolationValidator.js` | `^variable^` syntax and referenced variable existence | `ACU-INT` |
| `namingValidator.js` | Variable prefix and casing conventions | `ACU-NAM` |
| `keywordCapsValidator.js` | Language keyword capitalisation | `ACU-KWD` |
| `includeValidator.js` | Missing include files, circular dependencies, REQUIRE | `ACU-INC` |
| `macroValidator.js` | Macro declarations and call-site validation | `ACU-MAC` |
| `argumentStringValidator.js` | Literal string arguments (dates, cursor directions, table synonyms) | `ACU-ARG` |
| `indexUsageValidator.js` | Index function usage patterns and SQL index behaviours | `ACU-PAT` |
| `arrayUsageValidator.js` | Array lifecycle and usage patterns | `ACU-PAT` |
| `robodocValidator.js` | Robodoc comment completeness | `ACU-RBD` |
| `hashModeValidator.js` | HASH-ON / HASH-OFF state and executable line classification | `ACU-SYN` |
| `doIteratorValidator.js` | DO loop iterator variable rules | `ACU-SYN` |
| `eolRequiredValidator.js` | End-of-line requirements | `ACU-SYN` |
| `indentationValidator.js` | Block indentation alignment | `ACU-BLK` |
| `operatorSpacingValidator.js` | Spacing around operators | `ACU-STY` |
| `discouragedLiteralValidator.js` | Discouraged literal values | `ACU-STY` |
| `exitStatementValidator.js` | EXIT statement usage | `ACU-STY` |
| `todoValidator.js` | TODO / FIXME / HACK annotations | `ACU-TODO` |

---

## When to add a new validator vs. extend an existing one

**A validator's scope is defined by the language construct it targets, not the syntax
it parses.** If a construct has its own identity in the language — its own name, its own
registry entry, its own diagnostic ID namespace — it gets its own validator file. A
validator should never contain logic for two distinct constructs, even if they share
syntactic surface.

**Add a new validator when:**

- The construct has its own name and conceptual identity (e.g. output directives are not
  functions, even though they use `name(args)` syntax).
- The diagnostics naturally form their own `ACU-XXX` prefix group.
- The logic would make an existing validator responsible for two unrelated things.

**Extend an existing validator when:**

- The new rule targets the same construct (e.g. a new function-call check belongs in
  `functionValidator.js` or one of the `function*` files).
- The new diagnostic fits naturally within the existing `ACU-XXX` prefix.
- The shared context (document scan, catalog lookups) is already being built by the
  existing validator, and duplicating it would be wasteful.

**Prefix assignment:** each new validator gets its own `ACU-XXX` prefix in the diagnostic
catalogue. Validators that share a semantic domain (e.g. the four `function*` files all
use `ACU-FNC`) may share a prefix, but only if they genuinely operate on the same
language construct.

---

## Rules for Working Here

- **Never `import` from `vscode`** — if you need a vscode type (Diagnostic, Range), it must
  come through `diagnosticFactory.js` which uses plain objects. The CLI shim provides
  compatible implementations at runtime.
- **Adding a new validator:** create `validators/yourValidator.js`, register it in
  `collectDiagnostics.js`, add entries to the diagnostic catalogue with a new `ACU-XXX`
  prefix, and write a syntax test before implementing. Update this file's validator table.
- **Diagnostic IDs are permanent** — once an ID is in the catalogue it cannot be renumbered.
  Retired rules should be marked deprecated, not deleted.
- **No new npm dependencies** — the core has zero runtime deps and that must stay true.

---

## Key Interfaces

**Parser output** — `parser.js` returns a document object with tokens, sections, variable
declarations, function definitions, and include directives. All validators receive this.

**Diagnostic shape:**

```js
{ range: { start: { line, character }, end: { line, character } },
  message: string,
  severity: 0|1|2|3,   // Error=0, Warning=1, Information=2, Hint=3
  code: 'ACU-XXX-NNN',
  source: 'acurity' }
```

**Adding to the built-in functions registry** — add an entry to `builtins/functionsRegistry.js`
following the existing shape, then verify with `tools/validate-diagnostic-coverage.js`.

**Built-in function registry entry shape:**

```js
FUNCTION_NAME: {
    // Required
    params:  [AcurityTypes.STRING, AcurityTypes.SHORT],   // positional param types
    returns: AcurityTypes.SHORT,

    // Optional — parallel array to params; defaults to all "REF" if omitted.
    // Valid values: "REF" | "VAR" | "VAR_init_not_required" | "VAR_destroy"
    //   REF                  — read-only input; caller must initialise
    //   VAR                  — read + written; caller must initialise
    //   VAR_init_not_required — output-only; caller need not initialise; function writes it
    //   VAR_destroy          — function reads then invalidates; treat as uninitialised after call
    paramModes: ["REF", "VAR_init_not_required"],

    // Optional — for varArgs functions, paramModes covers only the known positional
    // parameters. Remaining varArgs arguments implicitly default to "REF".
    varArgs: true,

    // Optional — static list of table codes whose DB cursors this function advances.
    // Use for functions like READ_MEMB where the affected tables are always the same.
    cursorTables: ["MD", "D2"],

    // Optional — index (0-based) of the handle argument whose bound table determines
    // which DB cursor is advanced. Use for INDEX_READ_BY_KEY and similar.
    cursorTableFromHandle: 0,

    // Optional — describes the handle-binding operation performed by open functions
    // (e.g. INDEX_OPEN_STANDARD). handleParam receives the new handle; tableCodeParam
    // supplies the table code string that the handle becomes bound to.
    bindsHandle: { handleParam: 2, tableCodeParam: 0 },

    description: "Human-readable summary",
}
```
