# Acurity Architect Language Specification

## Overview

The Acurity Architect language is a domain-specific scripting language used to customise behavior in the Acurity enterprise application.

The language is used to:

- Produce correspondence and report output
- Manipulate data and UI behavior
- Execute maintenance and batch processing
- Drive accounting/business transactions
- Provide application-layer behavior for integrations/services

This document is a working specification focused on practical language behavior observed in production code and test assets.

## Processing Model (Read This First)

### Line-based execution

- The language is line-based.
- Newline characters terminate statements.
- Statements do not require `;` terminators.

### HASH-ON vs HASH-OFF

This is one of the first phases of parsing/execution.

- `HASH-ON` is the default mode.
- In `HASH-ON` mode:
    - Executable lines are prefixed with `#`.
    - Unprefixed lines are output lines.
- `HASH-OFF` switches to modern mode.
- In `HASH-OFF` mode:
    - Executable lines do not require `#`.
    - Output lines are prefixed with `>>`.

Example mode switch:

```acurity
#HASH-OFF
OPTION OICC = "^", CICC = "^"
NEWINTERPRETER
```

### NEWINTERPRETER

- `NEWINTERPRETER` enables the more modern interpreter implementation.
- It is recommended for most new scripts and generally provides better performance.
- It should appear near the top of file (practical standard: within the first ~20 lines).
- Legacy scripts may intentionally omit it to preserve historical behavior.

### Debug-mode output (`??`)

- In DEBUG mode, lines prefixed with `??` are treated as output lines.
- Equivalent forms are supported across mode styles:
    - `?? ...` in HASH-OFF style
    - `#?? ...` in HASH-ON style
- In non-DEBUG mode, these lines are not output.

## Language Purpose and Scope

Within Australian superannuation administration workflows, Acurity Architect provides a configurable scripting layer to implement business and compliance-specific behavior.

The syntax is intentionally lightweight but supports substantial operational logic.

## Lexical Structure

### Case sensitivity

- Language keywords and identifiers are case-insensitive.
- File names/paths may still vary by platform-level file system behavior.
- Coding standards may enforce preferred casing conventions independent of parser behavior.

### Literals

- String literals: `'text'` or `"text"`
- Numeric literals: `42`, `3.14`
- Boolean literals: `TRUE`, `FALSE`

## Keywords

### Control and flow

- `IF`, `ELSE`, `ENDIF`
- `DO`, `WHILE`, `FOREVER`, `UNTIL`, `ENDDO`
- `BREAK`, `CONTINUE`
- `CASE`, `VALUE`, `OTHERWISE`, `ENDCASE`
- `OF` (supported in CASE syntax; considered legacy/not recommended in modern reports)

### Declarations and definitions

- `VAR`
- `FUNCTION`, `ENDFUNCTION`
- `MACRO` (compile-time text substitution; see Macros section)

### Include and execution

- `INCLUDE`
- `INCLUDEONCE`
- `INCLUDETEST` (DEBUG-mode include injection behavior)
- `END-OF-CODE`
- `EXIT`
- `REQUIRE` (similar to INCLUDE but requires file existence; behavior details TBD)

### Mode/configuration

- `HASH-ON`, `HASH-OFF`
- `OPTION`
- `NEWINTERPRETER`

### Runtime-context booleans (legacy report flow)

- `HEADER`, `BODY`, `FOOTER`
- Commonly seen as `IF HEADER`, `IF BODY`, `IF FOOTER` in legacy report assets.

### Reserved keywords (syntax unconfirmed)

- `FOR` - recognized as a keyword but not observed in sample codebase; may be reserved for loop syntax
- `COUNT` - recognized as a keyword but not observed in sample codebase; may be part of FOR syntax or loop iteration
- `OF` - used in legacy CASE syntax; modern usage patterns unclear

## Core Syntax

### Program structure

- Program termination keyword is not required.
- Script execution normally ends at end-of-file.
- `ENDPROGRAM` is not part of this language.
- `END-OF-CODE` and `EXIT` can both terminate execution early.

### Include statements

```acurity
INCLUDE some_file.TXT
INCLUDEONCE shared_constants.TXT
```

`INCLUDETEST` behaves like `INCLUDE`, but is intended for DEBUG-mode injection scenarios.

## Declarations and Types

### Variable declaration

`VAR` is the declaration keyword. `DECLARE` is not valid in this language.

```acurity
VAR sStatus : SHORT
VAR zFund, zMember : STRING
VAR dEffective : DATE
```

### Primitive types in common use

| Type | Value range | Notes |
|---|---|---|
| `STRING` | arbitrary text | |
| `SHORT` | −32,768 to 32,767 | 16-bit signed integer |
| `INTEGER` | −2,147,483,648 to 2,147,483,647 | 32-bit signed integer; synonym for LONG |
| `LONG` | −2,147,483,648 to 2,147,483,647 | 32-bit signed integer; synonym for INTEGER |
| `BIGINT` | −9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | 64-bit signed integer |
| `DOUBLE` | −1.79769313486232E+308 to 1.79769313486232E+308 | 64-bit IEEE 754 double |
| `DATE` | 00/00/0000 to 31/12/9998 | DD/MM/YYYY format |
| `TIME` | 00:00:00:00 to 29:59:59:99 | HH:MM:SS:CC format (CC = centiseconds) |
| `BOOLEAN` | `TRUE` / `FALSE` | |

Numeric widening order for binary operations: `SHORT < LONG/INTEGER < BIGINT < DOUBLE`.
INTEGER and LONG are interchangeable; the functions registry uses LONG as the canonical form.

## Assignment Semantics

- Assignment may be written with or without `SET`.
- Both forms are valid.

```acurity
SET sStatus = READ_MEMB(">=", zFundCode, zMemberNumber)
sStatus = READ_MEMB(">=", zFundCode, zMemberNumber)
```

Default plugin/coding-standard behavior should not warn either way unless a codebase explicitly opts into a style rule.

## Control Structures

### IF

```acurity
IF condition
    [statements]
ELSE
    [statements]
ENDIF
```

### DO loops

Observed forms include:

```acurity
DO WHILE condition
    [statements]
ENDDO

DO FOREVER
    [statements]
UNTIL condition
```

`BREAK` and `CONTINUE` support optional numeric levels for nested loops (for example, `BREAK 2`, `CONTINUE 2`).

### CASE

```acurity
CASE sStatus
    VALUE 0
        [statements]
    OTHERWISE
        [statements]
ENDCASE
```

`OF` may appear in legacy CASE syntax and should remain supported.

## Functions

### User-defined functions

```acurity
FUNCTION zMyFunction(pzInput: STRING, psIndex: SHORT) : STRING
    RETURN GET_TOKEN(pzInput, "-", psIndex)
ENDFUNCTION
```

### Return-value model

- Functions are expected to return a value (no `VOID` model).
- Architect patterns are heavily assignment-centric.
- Built-in and user-defined function calls are generally made through assignment and status checking.

Example pattern:

```acurity
sMemberStatus = READ_MEMB(">=", zFundCode, zMemberNumber)
IF sMemberStatus = 0
    [success path]
ENDIF
```

## Macros

Macros provide compile-time text substitution, primarily used for printer control sequences and repetitive code patterns.

### Macro definition syntax

```acurity
MACRO name body
MACRO name(parameter) body
MACRO name(param1, param2, ...) body
```

### Parameter substitution

- Single parameter macros use `?` as the placeholder in the body
- Multi-parameter macros use named parameters
- Parameters are substituted literally during expansion

### Examples

```acurity
// Simple macro (no parameters)
MACRO _reset E

// Single parameter (? is placeholder)
MACRO _copies(?) &l?X
MACRO _font_7(?) (8U(s1p?v0s0b4101T

// Multiple named parameters
MACRO _centre_at(x,y,z) *c15D z *c6F %0B IN; SP1; SC-4000,4000,10000,0; PUx,y;
MACRO _centre_this(?, p) AD4,p; FN15;SA: DT#; LO6;  LB?# %0A
```

### Macro invocation

Macros are invoked by name, with parameters in parentheses:

```acurity
_reset
_copies(3)
_font_7(12)
_centre_at(100, 200, _font_8(10))
```

### Expansion behavior

- Macros are expanded at compile/parse time (not runtime)
- This distinguishes them from functions, which execute at runtime
- Macro bodies can contain escape sequences, control characters, and nested macro calls
- Common use: printer control files (e.g., hpdriver.txt) define macros consumed by report scripts

### Coding considerations

- Macro names conventionally start with `_` to distinguish from functions
- Extensively used in HP printer driver configuration (hpdriver.txt)
- Parameter count must match between definition and invocation

## Built-in Variables and Database Field Handles

### Database field naming form

Database field handles use an `h`-prefixed structure, for example:

- `hMDz_Member`

Interpretation:

- `h`: database field handle
- `MD`: 2-character table synonym (example: Member Details)
- `z`: field type code (example: String)
- `_`: separator
- `Member`: field name

Known inventory size is large (many globals and large field catalogs), and full authoritative lists are not yet included in this document.

## Function Parameter Passing Modes

Built-in functions treat their parameters in one of four modes. The mode governs two things:
whether the caller must initialise the variable before the call, and whether the function
writes a new value back into the variable.

| Mode | Caller must initialise? | Function writes back? | Notes |
|---|---|---|---|
| `REF` | yes | no | Standard read-only input |
| `VAR` | yes | yes | Read + written; caller supplies a meaningful input value |
| `VAR_init_not_required` | no | yes | Output-only; function always writes the variable |
| `VAR_destroy` | yes | yes — invalidates | Function reads the variable then destroys its value |

### Examples

```acurity
VAR sIndexHandle : SHORT
VAR sStatus      : SHORT

// INDEX_OPEN_STANDARD: arg 2 is VAR_init_not_required — sIndexHandle need not be set first.
// After the call, sIndexHandle is a valid open handle.
sStatus = INDEX_OPEN_STANDARD("MD", 0, sIndexHandle)

// READ_MEMB: args 1 and 2 are VAR — zFundCode and zMemberNumber must be initialised
// before the call. The function uses them as search keys and may update them.
sStatus = READ_MEMB(">=", zFundCode, zMemberNumber)

// INDEX_CLOSE: arg 0 is VAR_destroy — sIndexHandle must be initialised (it holds the
// open handle), but after this call it must be treated as uninitialised.
sStatus = INDEX_CLOSE(sIndexHandle)
```

### Validator implications

- `VAR_init_not_required` parameters suppress unset-variable diagnostics for the argument
  at the call site, and mark that variable as initialised after the call.
- `VAR` parameters behave like `REF` for the purposes of initialization checking: the
  variable must already be set before being passed in.
- `VAR_destroy` marks the variable as uninitialised after the call. Any subsequent use
  without re-initialisation should produce an unset-variable diagnostic.

---

## Index Handles and the Database Cursor Model

### Index handle lifecycle

An *index handle* is a SHORT variable used to represent an open cursor into a database
table index. The lifecycle follows a strict open/use/close pattern:

```acurity
VAR sHandle : SHORT
VAR sStatus : SHORT

// 1. Open — binds the handle to a table and index number.
//    sHandle is VAR_init_not_required at this point.
sStatus = INDEX_OPEN_STANDARD("MD", 0, sHandle)

// 2. Load key — sets search criteria on the open index.
sStatus = INDEX_LOAD_KEY_STRING(sHandle, zMemberNumber)

// 3. Read — positions the DB cursor and reads the matched record.
sStatus = INDEX_READ_BY_KEY(sHandle, ">=")

// 4. Close — releases the index and invalidates the handle.
//    sHandle is VAR_destroy: treat as uninitialised after this call.
sStatus = INDEX_CLOSE(sHandle)
```

Functions in the index handle family:

| Function | Param mode for handle | Notes |
|---|---|---|
| `INDEX_OPEN_STANDARD(tableCode, indexNo, handle)` | `VAR_init_not_required` | Binds handle to table |
| `INDEX_OPEN_CUSTOM(...)` | `VAR_init_not_required` | Variant; full signature TBD |
| `INDEX_READ_BY_KEY(handle, direction)` | `REF` | Advances cursor |
| `INDEX_LOAD_KEY_STRING(handle, key)` | `REF` | Sets STRING key segment |
| `INDEX_LOAD_KEY_LONG(handle, key)` | `REF` | Sets LONG key segment |
| `INDEX_LOAD_KEY_SHORT(handle, key)` | `REF` | Sets SHORT key segment |
| `INDEX_LOAD_KEY_DATE(handle, key)` | `REF` | Sets DATE key segment |
| `INDEX_CLEAR_KEY(handle)` | `REF` | Clears loaded key segments |
| `INDEX_SAVE_POSITION(tableCode)` | `REF` | Saves cursor position (by table code, not handle) |
| `INDEX_RESTORE_POSITION(tableCode)` | `REF` | Restores saved cursor position |
| `INDEX_CLOSE(handle)` | `VAR_destroy` | Releases index; handle becomes uninitialised |

### Database cursor model

Each database table has a *cursor* — a pointer to the current active record. Read
functions position this cursor, and field-access expressions (`hMDz_Surname` etc.) read
from the record at the cursor position.

**Static cursor movement** — some functions always move specific table cursors:

- `READ_MEMB` / `READ_MEMBER` always move the cursors for tables `MD` and `D2`.

**Handle-bound cursor movement** — index read functions move the cursor for whichever
table was bound to the handle when `INDEX_OPEN_STANDARD` was called:

```acurity
// sHandle is bound to "MD" after this call:
sStatus = INDEX_OPEN_STANDARD("MD", 0, sHandle)

// This therefore advances the cursor for table MD:
sStatus = INDEX_READ_BY_KEY(sHandle, ">=")
```

Tracking the handle→table binding at analysis time is required to determine which table
cursor a given `INDEX_READ_BY_KEY` call affects.

### Position save/restore

`INDEX_SAVE_POSITION` and `INDEX_RESTORE_POSITION` take a *table code* string directly
(not a handle). They save and restore the cursor position for that table independently
of any open index handle.

---

## Arrays

Architect arrays are function-driven in-memory sorted collections, not modern literal/index syntax arrays. All operations go through built-in functions.

### Array handle

An *array handle* is a SHORT variable holding an integer slot identifier in the range 1–60. It is not a database cursor — it is simply a number that identifies which in-memory array to operate on. Unlike an index handle, the value of the SHORT variable remains valid after the array is freed; only the in-memory array it referred to is gone.

The preferred way to obtain a handle is `GET_UNUSED_ARRAY_HANDLE`, which finds the first available slot. Avoid hardcoding integer literals (e.g. `sHandle = 1`) because they can collide when other code requests the same slot.

```acurity
VAR sHandle : SHORT

sHandle = 0                                      // initialise before querying
sStatus = GET_UNUSED_ARRAY_HANDLE(sHandle)       // preferred: finds first free slot
```

### Array lifecycle

The standard lifecycle is: **acquire handle → INIT → record operations → FREE**.

```acurity
VAR sHandle : SHORT
VAR sStatus : SHORT
VAR zKey    : STRING

// 1. Acquire a free slot.
sHandle = 0
sStatus = GET_UNUSED_ARRAY_HANDLE(sHandle)

// 2. Initialise: nShorts column(s), nDoubles column(s), nStrings column(s).
sStatus = INIT_SORTED_ARRAY(sHandle, 0, 0, 1)

// 3. Write records.
sStatus = ADD_ARRAY_REC(sHandle, "KEY001")
sStatus = PUT_STRING_SAY(sHandle, 1, "Value1")

// 4. Read records.
zKey = ""
sStatus = READ_ARRAY_REC(">=", sHandle, zKey)
DO WHILE sStatus = 0
    zValue = GET_STRING_SAY(sHandle, 1)
    sStatus = READ_ARRAY_REC("+", sHandle, zKey)
ENDDO

// 5. Release the array from memory.
sStatus = FREE_SORTED_ARRAY(sHandle)
// sHandle still holds its integer value — the SHORT variable is not invalidated.
// INIT_SORTED_ARRAY on the same sHandle is valid again after FREE.
```

### Array function reference

| Function | Description |
|---|---|
| `GET_UNUSED_ARRAY_HANDLE(handle)` | Sets `handle` to the first available slot (1–60). Preferred over hardcoding. |
| `INIT_SORTED_ARRAY(handle, nShorts, nDoubles, nStrings)` | Allocates the array. Errors if the slot is already occupied (not yet freed). |
| `ADD_ARRAY_REC(handle, key)` | Adds a record identified by a unique string key. |
| `PUT_STRING_SAY(handle, col, value)` | Writes a STRING value to the given column of the current record. `col` must be ≤ `nStrings` from INIT. |
| `PUT_DOUBLE_SAY(handle, col, value, precision)` | Writes a DOUBLE value to the given column. |
| `GET_STRING_SAY(handle, col)` | Reads a STRING value from the given column of the current record. |
| `GET_DOUBLE_SAY(handle, col)` | Reads a DOUBLE value from the given column. |
| `READ_ARRAY_REC(direction, handle, key)` | Positions the cursor on the matching record. Returns 0 on success. |
| `FREE_SORTED_ARRAY(handle)` | Releases the array from memory. The SHORT variable retains its integer value. |
| `FIN_SORTED_ARRAY()` | Frees **all** active arrays unconditionally. No handle argument required. |
| `NUM_SORTED_ARRAYS(n)` | Sets the maximum number of available slots. Defaults to 60. Avoid use — reducing this provides no performance benefit and can cause unexpected failures. |

### Validator lifecycle checks

The following warnings are emitted when the standard array lifecycle is not followed:

| Rule | Condition | Severity |
|---|---|---|
| `ACU-PAT-007` | Array record operations (`ADD_ARRAY_REC`, `READ_ARRAY_REC`, `PUT/GET_*_SAY`) in scope without any `INIT_SORTED_ARRAY`. | warning |
| `ACU-PAT-008` | `INIT_SORTED_ARRAY` in scope without a corresponding `FREE_SORTED_ARRAY` or `FIN_SORTED_ARRAY`. | warning |
| `ACU-PAT-009` | `FREE_SORTED_ARRAY` occurs before the last array record operation in scope. | warning |

`FIN_SORTED_ARRAY` satisfies the free requirement for `ACU-PAT-008` because it unconditionally releases all active arrays. It is not subject to `ACU-PAT-009` ordering checks (it has no per-handle argument).

## Output Lines

### Plain text vs inline command mode

Output lines have two forms:

**Plain text** — the line content is emitted literally (no evaluation):
```acurity
>>This text is printed exactly as written, including spaces.
```

**Inline commands** — parts of the line between OICC/CICC delimiter pairs are evaluated
as expressions and their results are emitted:
```acurity
>>^sName^ earns ^STR(fSalary)^ per annum.
>>@FROM(5, zName)@, @TO(40, STR(fBalance))@
```

### OICC / CICC — Inline Command Characters

**OICC** (Opening In-line Command Character) and **CICC** (Closing In-line Command Character)
define the delimiter pair that marks evaluated expressions within output lines.

- **Default:** `@` for both OICC and CICC.
- **Common override:** `^` for both (set by `OPTION OICC="^", CICC="^"` — very common in
  practice, as it avoids `@` which appears in email addresses and SQL strings).
- OICC and CICC may be set to **different** characters.
- Allowed characters (shared with LCC): `!`, `@`, `#`, `%`, `^`, `|`, `~`, `?`

```acurity
OPTION OICC = "^", CICC = "^"   // override both to caret
OPTION OICC = "@", CICC = "@"   // explicit default (rarely written)
```

Inside a delimiter pair, the content is a **comma-separated list** of expressions.
Each expression is evaluated and its string representation is emitted in sequence:

```acurity
>>@TAB(10),"Heading 1",TAB(15),"Heading 2",TAB(5),"Heading 3"@
// Output: "          Heading 1               Heading 2     Heading 3"
```

Variable interpolation (`^sVar^` or `@sVar@`) is the most common single-expression form:
```acurity
>>^sFirstName^ ^sLastName^ - Balance: ^STR(fBalance)^
```

### Output-positioning directives (section 0111)

Five special directives control cursor positioning within output lines.
They are **only valid inside OICC/CICC delimiters on output lines**.
Using them in a code expression (assignment, function argument, condition) is a
runtime error and fires `ACU-OUT-001`.

| Directive | Signature | Description |
|---|---|---|
| `CENTER` | `CENTER(col: SHORT, expression)` | Centers the expression around the given column. e.g. `CENTER(40, "Title")` centers on col 40 of an 80-column page. |
| `COL` | `COL(col: SHORT)` | Moves the output cursor to the given column. If the cursor is past that column, output may overwrite preceding text. |
| `FROM` | `FROM(col: SHORT, expression)` | Starts outputting the expression at the given column position. |
| `TAB` | `TAB(count: SHORT)` | Outputs `count` spaces, advancing the cursor. |
| `TO` | `TO(col: SHORT, expression)` | Ends outputting the expression at the given column (i.e. right-aligns to that column). |

**Examples:**
```acurity
>>@CENTER(40, "Report Heading")@
>>@FROM(5, sName)@
>>@TO(60, STR(fBalance))@
>>@COL(10)@, ^sLabel^
>>@TAB(10),"Column1",TAB(20),"Column2"@
```

**Invalid — fires ACU-OUT-001:**
```acurity
zResult = CENTER(40, "text")   // ERROR: output directive used in code
COL(10)                        // ERROR: output directive used in code
```

## Comments and Documentation Markup

- `//` line comments are supported.
- `#NOTE` style comment lines are a legacy HASH-ON comment directive and are still supported.
- Preferred modern style is `//` comments in all modes.
- In HASH-OFF mode, `#NOTE` is not treated as a comment (it is invalid executable text).
- Coding standards warning `ACU-SYN-003` recommends replacing `#NOTE` with `//`.
- Documentation tags such as `//@docStart` / `//@docEnd` are used in practice.

## Formatting and Indentation

- Indentation is not syntactically enforced.
- Tabs/spaces/width conventions are codebase-specific standards, not parser rules.

## OPTION Directive

The `OPTION` directive sets interpreter-level configuration. Multiple settings may appear
on one line, comma-separated:

```acurity
OPTION OICC = "^", CICC = "^"
OPTION DATEFORM = DMY, PAGEWIDTH = 132
```

### Known OPTION settings

All of OICC, CICC, and LCC share the same allowed character set: `!`, `@`, `#`, `%`, `^`, `|`, `~`, `?`

| Setting | Default | Valid values | Description |
|---|---|---|---|
| `OICC` | `@` | `!`, `@`, `#`, `%`, `^`, `\|`, `~`, `?` | Opening In-line Command Character for output lines |
| `CICC` | `@` | `!`, `@`, `#`, `%`, `^`, `\|`, `~`, `?` | Closing In-line Command Character for output lines |
| `DATEFORM` | `DMY` | `DMY`, `MDY`, `YMD` | Order of day/month/year components in date literals and output |
| `TIMEFORM` | `HMSC` | `HMSC`, `HMS`, `HM` | Components included in time literals and output (H=hour, M=minute, S=second, C=centisecond) |
| `PAGEWIDTH` | `16000` | `1`–`16000` | Output page width in characters; used by output-positioning directives (CENTER, COL, FROM, TAB, TO) |
| `LCC` | `#` | `!`, `@`, `#`, `%`, `^`, `\|`, `~`, `?` | Line Command Character — the prefix character for executable lines in HASH-ON mode |

### OICC / CICC
See the Output Lines section for full documentation.

The allowed character set is shared across OICC, CICC, and LCC. Any of the 8 characters
(`!`, `@`, `#`, `%`, `^`, `|`, `~`, `?`) may be used for any of the three settings,
and they may overlap (e.g. LCC=`@` and OICC=`@` is valid — the lexer must resolve
ambiguity by treating a line-leading instance as LCC in HASH-ON mode).

### DATEFORM
Affects the order of components in date literals and formatted date output.
- `DMY` (default) — `dd/mm/yyyy`, e.g. `25/12/2024`
- `MDY` — `mm/dd/yyyy`, e.g. `12/25/2024`
- `YMD` — `yyyy/mm/dd`, e.g. `2024/12/25`

**Language server implication (future work):** The lexer DATE_LITERAL token pattern
(`\d{1,2}/\d{1,2}/\d{4}`) is form-independent for DMY/MDY but would need adjustment
for YMD. Date literal validation (e.g. day/month range checks) depends on DATEFORM.
Whether DATEFORM affects *input* parsing (not just output) is unconfirmed.

### TIMEFORM
Affects the number of components in time literals and formatted time output.
- `HMSC` (default) — four components: `hh:mm:ss:cc` (hours, minutes, seconds, centiseconds)
- `HMS` — three components: `hh:mm:ss`
- `HM` — two components: `hh:mm`

**Language server implication (future work):** The lexer TIME_LITERAL pattern
(`\d{1,2}:\d{1,2}:\d{1,2}:\d{1,2}`) matches HMSC only. TIME_LITERAL matching and
time literal validation must become TIMEFORM-aware.
Whether TIMEFORM affects *input* parsing (not just output) is unconfirmed.

### PAGEWIDTH
Sets the page width used for output-positioning column arithmetic.
- Default: `16000`
- Range: `1`–`16000`
- Center column `CENTER(col, ...)` and similar directives use this as the effective line width.

**Language server implication (future work):** Column arguments to CENTER, COL, FROM, TAB, TO
should be validated against PAGEWIDTH. Currently the registry descriptions say "max page width"
without enforcing it.

### LCC — Line Command Character
In HASH-ON mode, executable lines are prefixed with the LCC character (default `#`).
Overriding LCC changes which character acts as the line prefix.

- Default: `#`
- Allowed overrides: `!`, `@`, `#`, `%`, `^`, `|`, `~`, `?`

**Language server implication (future work):** The lexer hardcodes `#` as the HASH-ON
code-line prefix. If LCC is overridden, the lexer's line classification logic would need
to use the LCC value instead. In practice this override is extremely rare.

## Error and Validation Considerations

### Naming standards

- `ACU-NAM-003` (`namingReservedPrefix`) should treat single-character `h` variable prefixes as reserved for database field handles.

### Keyword/style rules

- `SET` usage should be style-configurable and disabled by default.
- Legacy constructs (for example `OF`) may be supported while optionally discouraged via standards warnings.
- `#NOTE` comment directives are supported for compatibility, but coding standards should prefer `//` comments (`ACU-SYN-003`).

## Open Items

- `FOR`, `COUNT`, `REQUIRE`: supported but syntax/details TBD.
- `MACRO`: supported but detailed syntax/behavior TBD.
- `OPTION` settings documented in the OPTION Directive section. DATEFORM, TIMEFORM, PAGEWIDTH, and LCC all have language server implications deferred to future work.
- Full built-in/global/database field catalogs: TBD.

## References

- `vs-code-acurity/syntaxes/acurity.tmLanguage.json`
- `vs-code-acurity/test/syntax-tests/do/0001.TXT`
- `vs-code-acurity/test/syntax-tests/do/0002.TXT`
- `vs-code-acurity/test/syntax-tests/do/0003.TXT`

*Last Updated: March 9, 2026*
*Status: Work in Progress*
