|
| 1 | +# Advanced FileMaker Scripting Syntax |
| 2 | + |
| 3 | +Status: authoritative reference for all script-step POCOs. |
| 4 | + |
| 5 | +## Purpose |
| 6 | + |
| 7 | +SharpFM renders FileMaker script XML as editable display text. Some XML |
| 8 | +state doesn't naturally appear in FileMaker Pro's own display (object |
| 9 | +IDs, flags, embedded newlines, etc.). To round-trip that state faithfully |
| 10 | +through display-text editing, SharpFM extends FM Pro's display grammar |
| 11 | +with a small set of named conventions — collectively, **Advanced |
| 12 | +FileMaker Scripting Syntax**. |
| 13 | + |
| 14 | +The extensions are safe because **FileMaker Pro never consumes SharpFM's |
| 15 | +display text**. FM Pro reads only the binary `Mac-XMSS` clipboard |
| 16 | +payload; `POCO.ToXml()` always emits pure FM Pro XML regardless of |
| 17 | +whatever extensions are present in the display. The display text is a |
| 18 | +SharpFM-internal surface for user editing, and our display parser only |
| 19 | +ever reads our own extended output. |
| 20 | + |
| 21 | +## Core invariant |
| 22 | + |
| 23 | +> `ToDisplayLine()` and `FromDisplayParams()` together are lossless for |
| 24 | +> every piece of state the POCO carries. XML state that is dropped is |
| 25 | +> dropped because it carries no information, not because the display |
| 26 | +> can't express it. |
| 27 | +
|
| 28 | +Every POCO's docstring contains a **zero-loss audit** (see below) that |
| 29 | +enumerates XML state explicitly and marks each item as either rendered |
| 30 | +by FM Pro natively, covered by an advanced-syntax extension, or |
| 31 | +intentionally dropped with a written rationale. |
| 32 | + |
| 33 | +## The three extension forms |
| 34 | + |
| 35 | +Pick the form that matches the shape of the hidden state: |
| 36 | + |
| 37 | +| Shape of state | Syntax | Example | Used for | |
| 38 | +|---|---|---|---| |
| 39 | +| Id annotation on a named reference | `(#id)` suffix after the name | `"Find Customer" (#7)` | Script, Layout, TableOccurrence, Field id round-trip | |
| 40 | +| Boolean / enum flag already in FM Pro's grammar | word token inline, matching FM Pro's wording | `Exit after last: On` | Flags FM Pro itself renders; we mirror its wording | |
| 41 | +| Bulk invisible state (multi-slot / structured) | trailing `; Kind: [...]` block, word-token values | `; Buttons: ["OK" commit; "Cancel" nocommit]` | Button configurations, input-field metadata, any future bulk state | |
| 42 | + |
| 43 | +### Form 1 — `(#id)` suffix |
| 44 | + |
| 45 | +Appended to a quoted or `Table::Field` name: |
| 46 | + |
| 47 | +- `FieldRef` emits `People::FirstName (#7)` when an id is known; plain |
| 48 | + `People::FirstName` otherwise. |
| 49 | +- `PerformScriptStep` emits `"Find Customer" (#42)`. |
| 50 | +- `GoToLayoutStep` emits `"Invoices Detail" (#11)`. |
| 51 | + |
| 52 | +Omitted when the id is zero or unknown — `(#0)` would be visual noise |
| 53 | +for unresolved references. |
| 54 | + |
| 55 | +### Form 2 — inline word tokens |
| 56 | + |
| 57 | +Parsed at a specific named prefix, matching FM Pro's own rendering of |
| 58 | +the same flag: |
| 59 | + |
| 60 | +- `Exit after last: On` on found-set iterators. |
| 61 | +- `With dialog: Off` on steps that can suppress their confirmation UI. |
| 62 | +- `Restore: On|Off` — **reserved**. Not currently emitted by any POCO; |
| 63 | + see "What to drop vs. surface" below for the rationale. |
| 64 | + |
| 65 | +### Form 3 — trailing `; Kind: [...]` blocks |
| 66 | + |
| 67 | +Bulk or structured state that doesn't reduce to a single flag: |
| 68 | + |
| 69 | +- `; Buttons: ["OK" commit; "Cancel" nocommit; "" nocommit]` — used by |
| 70 | + Show Custom Dialog for its button configuration. |
| 71 | +- Future bulk state (Input Field specs, etc.) takes this form. |
| 72 | + |
| 73 | +## Parsing precedence |
| 74 | + |
| 75 | +Named-prefix inline tokens (Form 2) are parsed before trailing |
| 76 | +`Kind: [...]` blocks (Form 3). A display line like |
| 77 | + |
| 78 | +``` |
| 79 | +Go to Record/Request/Page [ Next ; Exit after last: On ; Buttons: [...] ] |
| 80 | +``` |
| 81 | + |
| 82 | +is tokenized as: |
| 83 | +1. Positional `Next` (Form 2 equivalent — fixed-position enum). |
| 84 | +2. Named inline `Exit after last: On` (Form 2). |
| 85 | +3. Named block `Buttons: [...]` (Form 3). |
| 86 | + |
| 87 | +Form 1 (`(#id)`) is applied inside each name-bearing token — e.g. inside |
| 88 | +the bracketed name-and-id of a named ref — and does not collide with |
| 89 | +Form 2 or Form 3 separators. |
| 90 | + |
| 91 | +## Zero-loss audit requirement |
| 92 | + |
| 93 | +Every POCO author must complete this audit in the class XML doc |
| 94 | +comment. Template: |
| 95 | + |
| 96 | +``` |
| 97 | +/// <summary> |
| 98 | +/// Zero-loss audit for StepName: |
| 99 | +/// <list type="bullet"> |
| 100 | +/// <item><Step> attributes (enable/id/name) — round-tripped.</item> |
| 101 | +/// <item><Calculation> CDATA — round-tripped via Calculation.</item> |
| 102 | +/// <item><SomeElement state="..."/> — Form 2 token "Some: On|Off".</item> |
| 103 | +/// <item><Dropped/> — intentionally dropped; rationale: ...</item> |
| 104 | +/// </list> |
| 105 | +/// </summary> |
| 106 | +``` |
| 107 | + |
| 108 | +Items fall into exactly one of these buckets: |
| 109 | + |
| 110 | +1. **Rendered natively by FM Pro** — FM Pro's display grammar already |
| 111 | + covers it; SharpFM mirrors the wording. |
| 112 | +2. **Covered by an extension form** — one of the three above. |
| 113 | +3. **Intentionally dropped** — rationale required. See next section for |
| 114 | + how to judge. |
| 115 | + |
| 116 | +Omitting the audit is a review blocker. |
| 117 | + |
| 118 | +## What to drop vs. surface |
| 119 | + |
| 120 | +Hidden state is surfaced only when a user could meaningfully change it. |
| 121 | +Some state is structurally present in XML but semantically fixed — FM |
| 122 | +Pro never alters it, never emits it in clipboard output, and no user |
| 123 | +workflow produces a different value. Round-tripping such state adds |
| 124 | +visual noise for zero information. |
| 125 | + |
| 126 | +### Canonical drop: `<Restore state="False"/>` on `If` |
| 127 | + |
| 128 | +Upstream `agentic-fm` snippets include the element; FM Pro's own |
| 129 | +clipboard output never does; no FM Pro user interaction produces |
| 130 | +`state="True"`. `IfStep` drops it on both read and write. The audit |
| 131 | +entry documents the drop: |
| 132 | + |
| 133 | +> <Restore state="False"/> — intentionally dropped. FM Pro never |
| 134 | +> changes the value and never emits the element in clipboard output; it |
| 135 | +> carries no information worth round-tripping. |
| 136 | +
|
| 137 | +### Canonical surface: field `id` via `(#id)` suffix |
| 138 | + |
| 139 | +`<Field table="T" id="12" name="F"/>` is a real identity — the id |
| 140 | +selects which field is referenced, and two fields named `F` in different |
| 141 | +tables are not interchangeable. Dropping the id would change semantics. |
| 142 | +`FieldRef` always emits `(#12)` when an id is available. |
| 143 | + |
| 144 | +### The heuristic |
| 145 | + |
| 146 | +- If two valid FM Pro script states would be visually identical under |
| 147 | + the display grammar without the extension, **surface** the state. |
| 148 | +- If the state has a fixed value that no user can change, **drop** it. |
| 149 | +- If you're not sure which, **surface**. Reversing a surface → drop |
| 150 | + later is non-breaking; reversing a drop → surface may break tests |
| 151 | + users wrote against the earlier display. |
| 152 | + |
| 153 | +## Adjacent convention: `//` disabled-step prefix |
| 154 | + |
| 155 | +Disabled steps are prefixed with `//` in display text: |
| 156 | + |
| 157 | +``` |
| 158 | +// Set Error Capture [ On ] |
| 159 | +``` |
| 160 | + |
| 161 | +Parsing strips the `//` and sets `ScriptStep.Enabled = false`. This is |
| 162 | +a document-level convention (applied to any step line) rather than a |
| 163 | +per-step extension. Covered here for completeness. |
| 164 | + |
| 165 | +## Implementation touch points |
| 166 | + |
| 167 | +- `FieldRef.ToDisplayString` / `FieldRef.FromDisplayToken` |
| 168 | + (`src/SharpFM.Model/Scripting/Values/FieldRef.cs`) — Form 1 reference |
| 169 | + implementation. |
| 170 | +- `CommentStep.ReturnGlyph` |
| 171 | + (`src/SharpFM.Model/Scripting/Steps/CommentStep.cs`) — the `⏎` |
| 172 | + (U+23CE) glyph for single-line rendering of multi-line comment text. |
| 173 | + An idiom adjacent to the three forms but specific to Comment. |
| 174 | +- `ScriptLineParser.ParseLine` |
| 175 | + (`src/SharpFM.Model/Scripting/ScriptLineParser.cs`) — disabled-step |
| 176 | + prefix and bracket tokenization. |
| 177 | +- `PerformScriptStep.FromDisplayParams`, |
| 178 | + `GoToLayoutStep.FromDisplayParams` — Form 1 regex parsers for named |
| 179 | + refs with `(#id)` suffixes. |
| 180 | + |
| 181 | +## Change log |
| 182 | + |
| 183 | +- **2026-04** — Extracted from `docs/step-definitions.md:44-69` into |
| 184 | + its own document as part of the POCO big-bang migration. Rationale |
| 185 | + section ("what to drop vs. surface") added with `Restore` on `If` as |
| 186 | + the canonical drop example. |
0 commit comments