# FMScriptBridge — readable-grammar specification **Grammar version:** 1.103 This describes the readable text FileMaker Script Workspace steps are rendered into, so a human or an AI assistant can edit that text and have it convert back cleanly. **You work in TEXT ONLY.** The plugin converted FileMaker's clipboard into this text and will convert your edited text back again. You never see, produce or need FileMaker's XML — emitting any would be an error. Your entire job is to keep the text conformant to what follows. ## 1. Core principles - **Names are what matter.** FileMaker re-resolves field, script, layout and table references by name when the text is pasted back, so a name you write is the name that binds. Rename carefully and spell exactly; there are no hidden ids for you to preserve. A name that does not EXIST in the target file pastes as a broken `` reference — never invent a layout, field or script name the user has not given you. - **Calculations are carried, not compiled.** The converter treats formula text as opaque data; FileMaker validates it at paste, and a calculation that does not compile there (a misspelled function, wrong argument count) is silently commented out as `/*…*/` in the pasted step. Use real FileMaker functions with their real signatures. - **Preserve, never guess.** A step is pretty-rendered only if its encoder understands every child and attribute; otherwise it is preserved verbatim in a `>>> preserved-fmxml` block. Coverage gaps degrade to "not-yet-pretty", never to corruption. - **A raw CR (`\r`) never reaches a text file.** Bodies whose breaks are all CR render in the ` ```cr ` fence dialect (real lines, rejoined with CR on parse); mixed CR+LF bodies preserve verbatim. - **Omitted options take FileMaker's factory defaults — on the documented step families.** For the families in §5b (Send Mail, New Window, Sort Records, Show Custom Dialog, the data-file family, the window family (Select / Move/Resize / Close Window), Open URL, Go to Field, Insert Text, Perform Script on Server, and the short-form list in §5b), a hand- or AI-written step may carry only the options it means; each absent option line reconstructs to the untouched-step state FileMaker itself would create (corpus-witnessed, never guessed). For any OTHER step, copy the full canonical shape — an omitted option there refuses loudly (never corrupts). Written lines always win and stay strict — see §5b. - **Fence bodies carry a structural base indent (v1.88).** The renderer emits every non-empty fence-body line at the opener line's indent, so a multi-line calc aligns under its own fence; EMPTY body lines stay empty. On parse, that opener indent is structural WHEREVER PRESENT: each body line starting with exactly the opener's leading whitespace loses one copy of it (per line); lines without it — hand-typed at column 0, or empty — are taken byte-verbatim. Content whitespace is whatever EXCEEDS the fence's visible margin (the block-scalar convention). Consequence for authors and generators: to put intentional leading whitespace in a calc line, write it BEYOND the fence indent; a line whose data whitespace sits exactly AT the margin is read as aligned-not-indented. Canonical renderer output always carries the base indent, so machine-rendered content round-trips byte-exactly. ## 2. What you must never change Four things are load-bearing. Break one and the conversion back either refuses or silently means something else. - **Never touch a `>>> preserved-fmxml` block.** It is a step the converter could not render, carried through byte-for-byte. Copy it exactly, including its fence. **Nothing downstream will catch a wrong edit here** — only the XML's well-formedness is checked, never its meaning, so this is the one place in the format where you are the last line of defence. The only way to change a preserved step is in FileMaker itself: edit it in the Script Workspace and copy again. Delete the whole step only if the user asked for that step to be removed. - **Never invent a label, an enum spelling or a step name.** Every one is FileMaker's own wording. If you have not seen the exact spelling in this document or in the text you were given, you do not know it — keep what is there rather than guessing a plausible variant. (On the §5b families, OMITTING an option is safe; a small set of FileMaker's own display spellings is also accepted — §5b. Inventing is what refuses.) - **Never change fence markers or fence-body indentation to taste.** Both are structural (see §1 and §3): reflowing a fence body changes the calculation, and shortening or lengthening a fence breaks it. Indentation on ordinary step lines, by contrast, is display-only — the converter normalizes it (canonical output indents 4 spaces per If/Loop level). - **Never reorder the items of an INLINE bracket body.** Order is part of the grammar there (block-step option lines are the documented exception — §5b). If you cannot express an edit within these rules, say so and leave the step unchanged. An honest refusal costs a round trip; a plausible guess costs the user's script. ## 3. Structural-core grammar (EBNF) ````ebnf (* ---- STRUCTURAL CORE ----------------------------------------------------- *) (* The readable text is line-oriented. A snippet is a sequence of step *) (* renderings (and optional whole-script wrappers). Item separators and *) (* labels below are LITERAL — note the surrounding spaces. *) (* DIALECT: this is W3C-style EBNF — concatenation is JUXTAPOSITION (no comma *) (* concatenate-symbol), unlike strict ISO 14977. The grammar is DESCRIPTIVE: *) (* it documents what the converter accepts and emits, and the worked examples *) (* below are authoritative wherever the two could be read differently. *) snippet = { rendering } ; rendering = step-rendering | whole-script ; step-rendering = comment | disabled | preserve | step ; step = bare-step | inline-step | block-step | predicate-step | trailing-fence-step ; bare-step = step-name | step-name " [ " toggle " ]" ; (* bare On/Off toggle *) inline-step = step-name " [ " bracket-body " ]" ; block-step = step-name " [" NEWLINE { block-item } NEWLINE "]" ; (* block-in-brackets *) (* Find-query family (Perform Find, Constrain / Extend Found Set, Enter Find *) (* Mode): an inline-step FOLLOWED on the next line by a standalone "{ }" block *) (* of find requests. The trailing brace is a SEPARATE line group from the *) (* inline "[ ]" — distinct from block-step's brace-directly-after-the-name. *) predicate-step = inline-step NEWLINE "{" NEWLINE { request-block } NEWLINE "}" ; request-block = " " request-label ":" NEWLINE { criterion } ; (* Include: / Omit: *) criterion = " " field-ref " → " operator-value ; (* "→" delimits find *) (* criteria here and sort items inside a SortList sub-block ("field → Ascending") *) bracket-body = item { " ; " item } ; item = positional-item | label ": " value | label " = " value | inline-calc-field ; (* A POSITIONAL item carries no label — it is the step's subject (the field *) (* being set, the script being called). HOW MANY a step takes, and whether it *) (* takes any, is per-step and is NOT derivable from this grammar. Copy the *) (* shape from a rendering of that same step; do not construct one. *) positional-item = value ; (* A block-item is one logical item; items are separated by a trailing " ;" on *) (* the last line of the item (mirroring bracket-body's " ; "). *) (* INLINE AND BLOCK FORM ARE NOT INTERCHANGEABLE. Which form a step uses is *) (* fixed per step: some render only inline, some only as a block. Rewriting one *) (* into the other does not parse. Stay in the shape you were given. *) (* A sub-block groups nested items under a labeled brace. *) block-item = ( " " item ) | fenced-calc | sub-block ; sub-block = " " sub-label " {" NEWLINE { block-item } NEWLINE " }" ; (* sub-label is per-step (e.g. "Target fields", "SortList(value=1)", a Query); *) (* its inner item grammar is per-step, specified by example in the fixtures. *) (* An inline calc/value field is OPAQUE: it runs to the line's TERMINAL "]". *) (* Any " ; ", "]", "{" or "}" INSIDE it is literal calc data. A parser MUST *) (* right-anchor (scan to the last "]", peeling a trailing rep suffix) — never *) (* split on the first " ; " or end at the first "]". *) inline-calc-field = label ": " calc-text-to-terminal-bracket ; fenced-calc = label ":" NEWLINE open-fence NEWLINE calc-body NEWLINE close-fence ; (* THE COMMON MULTI-LINE SHAPE. When a multi-line calc is one item of an *) (* INLINE bracket body, the step does NOT become a block. The header keeps *) (* every earlier item and ends in " ;" (or a bare "label:"); the fence opens *) (* on the NEXT line; and the CLOSING fence carries the step's terminal " ]". *) (* This is what you will see most often — see the worked example below. *) trailing-fence-step = step-name " [ " { item " ; " } [ label ":" ] NEWLINE open-fence NEWLINE calc-body NEWLINE close-fence " ]" ; (* WRONG: a bare close-fence with the "]" on its own next line — that shape *) (* refuses ("Unrecognized step body"). RIGHT: the closer line IS "``` ]". *) (* Set Field [ T::F ; Set Field [ T::F ; *) (* ``` ``` *) (* $x & "!" $x & "!" *) (* ``` <- WRONG ``` ] <- RIGHT *) (* ] *) (* A fence is THREE OR MORE backticks; the closer repeats the opener's run. *) (* The renderer widens the fence past any backtick run inside the body (a *) (* real corpus calc renders with ````), so never shorten or lengthen one you *) (* were given. The cr marker appears ONLY on the opener — a symmetric *) (* "```cr" closer does not parse. Fences are STRUCTURE, not *) (* formatting: they exist only in the two positions above (after a label: *) (* header / after a lone "#"). A bare fence line anywhere else — e.g. a *) (* markdown fence a chat UI added around part of the script — refuses. *) open-fence = backtick-run [ "cr" ] ; (* cr dialect rejoins lines with CR *) close-fence = backtick-run ; backtick-run = "```" { "`" } ; comment = "# " text (* single line *) | "#" NEWLINE open-fence NEWLINE text NEWLINE close-fence ; (* A multi-line comment uses the PLAIN fence — in comment context the plain *) (* fence already rejoins lines with CR (FileMaker's own normalization); *) (* "#" followed by a ```cr opener does not parse. Write the space: *) (* a space-less "#comment" still parses as a comment but is rewritten to *) (* "# comment" and draws the understood-differently notice. *) disabled = "// " rendering ; (* a step the user turned off *) preserve = [ step-name " " not-yet-pretty-marker NEWLINE ] preserve-block ; (* The decorative marker line heads a demoted STEP's block; a demoted Script/ *) (* Folder WRAPPER (an attribute the header grammar can't carry) emits the BARE *) (* preserve-block with NO header line. The parser accepts both forms. *) preserve-block = ">>> preserved-fmxml" NEWLINE raw-fm-xml NEWLINE "<<<" ; (* Whole-script wrappers. The flag suffix is BRACKETED and " ; "-separated *) (* (the labels below are the complete set), and the opening "{" always stands *) (* ALONE on the line AFTER the header — never on the header line itself; "}" *) (* alone on a line closes the body. The renderer emits a flag only when it *) (* differs from the default (Script: all three default Off; Folder: in menu *) (* defaults On, collapsed defaults Off); the parser accepts every listed flag *) (* as On or Off. Blank lines between the header and "{" and at body item *) (* boundaries are parse-tolerated, but never emitted. A Script body holds *) (* step renderings only (wrappers never nest inside a Script); a Folder body *) (* holds Scripts, nested Folders, and bare preserve-blocks (demoted nested *) (* wrappers). *) whole-script = script-block | folder-block | preserve-block ; script-block = "Script " quoted-name [ script-flags ] NEWLINE "{" NEWLINE { step-rendering } NEWLINE "}" ; folder-block = "Folder " quoted-name [ folder-flags ] NEWLINE "{" NEWLINE { script-block | folder-block | preserve-block } NEWLINE "}" ; script-flags = " [ " script-flag { " ; " script-flag } " ]" ; script-flag = ( "in menu" | "Siri visible" | "full access" ) ": " toggle ; folder-flags = " [ " folder-flag { " ; " folder-flag } " ]" ; folder-flag = ( "in menu" | "collapsed" ) ": " toggle ; toggle = "On" | "Off" ; step-name = catalog-name ; (* an exact catalog name; see the list below *) (* Opaque nonterminals — their content is calc/reference DATA or is specified *) (* per-step by example, so they are not expanded here: catalog-name, label, *) (* sub-label, request-label, field-ref, operator-value, value, text, *) (* quoted-name, calc-body, raw-fm-xml, calc-text-to-terminal-bracket, *) (* not-yet-pretty-marker. One quoted-name rule worth stating: an embedded *) (* quote is escaped BACKSLASH-style — Script "Say "Hi"" — and a literal *) (* backslash as \. SQL-style doubling of the quote character instead of *) (* backslash-escaping it refuses. *) ```` ### The step-name catalog Step names are exactly FileMaker's own Script Workspace step names — an unknown or misspelled name refuses loudly. The current catalog (216 names, generated from the engine's own table so this list cannot drift): # (comment) · AVPlayer Play · AVPlayer Set Options AVPlayer Set Playback State · Add Account · Adjust Window Allow Formatting Bar · Allow User Abort · Append PDF · Arrange All Windows Beep · Cancel PDF · Change Password · Check Found Set · Check Record Check Selection · Clear · Close Data File · Close File · Close PDF Close Popover · Close Window · Commit Records/Requests · Commit Transaction Configure AI Account · Configure Local Notification Configure Machine Learning Model · Configure NFC Reading Configure Persistent Data · Configure Prompt Template Configure RAG Account · Configure Region Monitor Script Configure Regression Model · Constrain Found Set · Convert File · Copy Copy All Records/Requests · Copy Record/Request · Correct Word Create Data File · Create PDF · Cut · Delete Account · Delete All Records Delete File · Delete Portal Row · Delete Record/Request · Dial Phone Duplicate Record/Request · Edit User Dictionary · Else · Else If Enable Account · Enable Touch Keyboard · End If · End Loop Enter Browse Mode · Enter Find Mode · Enter Preview Mode Execute FileMaker Data API · Execute SQL · Exit Application · Exit Loop If Exit Script · Export Field Contents · Export Records · Extend Found Set Find Matching Records · Fine-Tune Model · Flush Cache to Disk Flush Web Viewer Cookies · Freeze Window · Generate Response from Model Get Data File Position · Get File Exists · Get File Size · Get Folder Path Go to Field · Go to Layout · Go to List of Records · Go to Next Field Go to Object · Go to Portal Row · Go to Previous Field Go to Record/Request/Page · Go to Related Record · Halt Script · If Import Records · Insert Audio/Video · Insert Calculated Result Insert Current Date · Insert Current Time · Insert Current User Name Insert Embedding · Insert Embedding in Found Set · Insert File Insert Image Caption · Insert Image Captions in Found Set · Insert PDF Insert Picture · Insert Text · Insert from Device · Insert from Index Insert from Last Visited · Insert from URL · Install Menu Set Install OnTimer Script · Install Plug-In File · Loop · Modify Last Find Move/Resize Window · New File · New Record/Request · New Window Omit Multiple Records · Omit Record · Open Data File · Open Edit Saved Finds Open Favorites · Open File · Open File Options · Open Find/Replace Open Help · Open Hosts · Open Manage Containers · Open Manage Data Sources Open Manage Database · Open Manage Layouts · Open Manage Themes Open Manage Value Lists · Open PDF · Open Record/Request Open Script Workspace · Open Settings · Open Sharing · Open Transaction Open URL · Open Upload to Host · Paste · Pause/Resume Script Perform AppleScript · Perform Find · Perform Find by Natural Language Perform Find/Replace · Perform JavaScript in Web Viewer · Perform Quick Find Perform RAG Action · Perform SQL Query by Natural Language · Perform Script Perform Script on Server · Perform Script on Server with Callback Perform Semantic Find · Print · Print PDF · Print Setup · Re-Login Read from Data File · Recover File · Refresh Object · Refresh Portal Refresh Window · Relookup Field Contents · Rename File Replace Field Contents · Reset Account Password · Revert Record/Request Revert Transaction · Save Records as Excel · Save Records as JSONL Save Records as PDF · Save Records as Snapshot Link · Save a Copy as Save a Copy as Add-on Package · Save a Copy as XML · Scroll Window Select All · Select Dictionaries · Select Window · Send DDE Execute Send Event · Send Mail · Set AI Call Logging · Set Data File Position Set Dictionary · Set Error Capture · Set Error Logging · Set Field Set Field By Name · Set Layout Object Animation · Set Multi-User Set Next Serial Value · Set Revert Transaction on Error · Set Selection Set Session Identifier · Set Use System Formats · Set Variable Set Web Viewer · Set Window Title · Set Zoom Level · Show All Records Show Custom Dialog · Show Omitted Only · Show/Hide Menubar Show/Hide Text Ruler · Show/Hide Toolbars · Sort Records Sort Records by Field · Speak · Spelling Options Trigger Claris Connect Flow · Truncate Table · Undo/Redo · Unsort Records View As · Write to Data File ## 4. Reserved tokens Every literal below is **structural** — treat it as grammar, never as field/calc data. An encoder that would emit a real value equal to a collision-guarded sentinel refuses and preserves verbatim instead. | Token | Meaning | |---|---| | ```` ``` ```` | calc / preserve fence delimiter (``` ; the ```cr opener rejoins CR) | | `>>> preserved-fmxml` | opens a preserve-verbatim raw-FM-XML block | | `<<<` | closes a preserve-verbatim block | | `[ ## not-yet-pretty: preserved verbatim ## ]` | decorative header above a demoted step's preserve block | | `→` | find-criteria delimiter in a predicate block (`field → operator-value`) | | `` | unresolved table reference | | `` | unresolved field reference | | `` | unresolved layout reference | | `` | unresolved script reference | | `` | missing by-calculation script name (Perform Script) | | `` | missing external file reference (Perform Script) | | `` | missing Claris Connect flow | | `` | missing variable name (Set Variable) | | `` | Set Field / Set Selection with no target field | | `` | Import Records target slot with no bound field | | `` | Truncate Table: the current table (no explicit reference) | | `` | Perform Script on Server with Callback: no callback script | | `` | FileMaker's own literal reference text | | `` | Truncate Table: FileMaker's literal current-table name | | `[File Default]` | Install Menu Set: use the file-default menu set | | `(collect across found set)` | Send Mail To/Cc/Bcc: collect addresses across found set | | `[no condition]` | If / Else If with no Calculation (empty condition) | | `#!#REDACTED#!#` | one-way redaction placeholder (blocks paste-back unless flagged) | | `#! fmsb-missing-ref:` | missing-reference advisory line prefix (stripped on parse) | Authoring caveats for three of these: an EMPTY If/Else If condition is written `If [ no condition ]` — the table's bracketed spelling is how the token is LISTED, not how it is written inside the step's own brackets (writing `If [ [no condition] ]` makes the literal text the formula, and the converter warns). `[File Default]` is written inside quotes AND ALONE in an Install Menu Set line — exactly `Install Menu Set [ "[File Default]" ]`; the `Use as file default:` option belongs only beside a NAMED menu set (`Install Menu Set [ "My Menus" ; Use as file default: On ]`), and combining it with `"[File Default]"` builds a menu-set reference FileMaker cannot bind. And `→` delimits sort items inside a `SortList { … }` sub-block exactly as it delimits find criteria. `` is written with its label in a Truncate Table line (`Truncate Table [ With dialog: Off ; Table: ]`). ## 5. Per-step options are bespoke-by-example The structural core is stable and specified above. The per-step *option* grammars (labels, enum spellings, flag polarity) are **not** enumerated as EBNF — there are 216 step types, and each mirrors its FileMaker dialog. They are specified BY EXAMPLE — and that includes each step's SHAPE, not only its labels. Some steps render inline (`Name [ … ]`), some only as a block (`Name [` on its own line, then ` Label: value ;` items indented two spaces, closed by `]` alone). Inline and block form are not interchangeable per step. The rule that replaces an enumeration: **every label and enum spelling is the converter's canonical one — copy it from a rendering or from the examples here, character for character.** Most labels match the step's own FileMaker dialog verbatim; a few are the converter's shorter spellings (`for:` for the pause duration, `by name:` for a window-name calc, `text:` / `Target:` on Insert Text, `Option:` on Open URL — FM's dialog calls that checkbox `In external browser`), and for the documented cases FileMaker's own display spelling is ALSO accepted and echoed back in canonical form (`Duration (seconds):`, New Window's `Name:` / `Style:` / `Using layout:`, `Text Result:`, `Current Window`). Never invent, translate or normalize a label yourself — a guessed spelling refuses loudly (which is the safe outcome). Boolean labels differ per step — most write `With dialog: On/Off`, but the Insert family writes `Select entire contents:` and others differ again; copy the exact label from a rendering or the examples. ### 5a. Editing grabbed text When you are EDITING text the converter produced, the shapes are already in front of you: keep each step in the form you were given and change only the values you mean to change. For a step type whose options you have not seen rendered, prefer leaving it alone over reshaping it. ### 5b. Generating new steps Writing NEW steps from scratch is supported. On the families documented here, **write only the options you mean; every option line you omit takes FileMaker's own factory default for that step** (the untouched-step state — corpus-witnessed, never guessed). What you DO write always wins, and stays strict: a typo'd label, a bad enum value or a duplicated option line refuses loudly, naming the offending line or its block — an omission can default, a mistake never can. For steps outside these families, write the full canonical shape from a rendering or an example; an omission there refuses (loudly, never a corrupt guess). - Common steps parse from their natural short forms: `Perform Find`, `Show All Records`, `Commit Records/Requests [ With dialog: Off ]`, `Enter Find Mode [ Pause: Off ]`, `Go to Layout [ "Name" ]`, `Go to Record/Request/Page [ First ]`, `Set Variable [ $x ; Value: … ]`, `Set Field [ Table::Field ; ]`, `Set Field By Name [ ; ]` (the two calc slots split at the top-level ` ; ` — a calc's own separators inside parentheses or quotes are data), `Perform Script [ "Name" ; Parameter: … ]`, `Exit Script [ Result: … ]`, `Open URL [ "https://…" ]`, `Go to Field [ Table::Field ]`, `Pause/Resume Script [ Duration (seconds): 2 ]`, `Select Window [ Current window ]` / `Select Window [ by name: "Name" ]`, `Perform Script on Server [ "Name" ; Parameter: … ; Wait for completion: Off ]` (the wait item may sit first or last, and omitted means On) and the whole If/Else If/Else/Loop family. - Option-heavy steps are BLOCK form and may be written minimally — see the minimal-form examples below (Send Mail, New Window, Sort Records, Show Custom Dialog, the data-file family, Insert Text, Move/Resize Window). - The converter also accepts FileMaker's Script-Workspace display spellings where they differ from this grammar (`Exit Script [ Text Result: … ]`, New Window's `Name:` / `Style:` / `Using layout:`, `Current Window` capital-W on the window steps, `Duration (seconds):` on Pause/Resume Script, Open URL's bare `In external browser` token (= `Option: On`; omit it for Off, exactly as FileMaker displays it), a leading bare `Select` token on the select-family steps (= `Select entire contents: On`), a `Specified: From list` item on the Perform Script family (discarded — a quoted script name already says it), Send Mail's `Send via E-mail Client` line (the factory mode; it refuses only if a written `Via SMTP/OAuth: On` contradicts it), `Perform Find [ ]`) and echoes the canonical form. Lines inside a BLOCK step may be written in any order and at any step-line indentation; the converter normalizes both — a sub-block such as Sort Records' `SortList { … }` may sit anywhere among the option lines, and a dialog-only sort may be written as a block or inline (`Sort Records [ With dialog: On ]`). Reordering block lines is normalized silently, and it composes with the display spellings and separators above — a reordered, aliased, separator-light block still verifies without comment. INLINE `[ … ]` items are order-fixed: on a step whose final slot is calculation or path data (Set Variable's `Value:`, Set Field's calc, `Target file:`, `by name:` …), everything after that label up to the terminal `]` is literal data — an option written there is read as part of the value (the converter refuses or warns on the shapes it can recognize — but do not rely on that; keep inline items in the shown order). Canonical output is what the examples show. - To disable a step, prefix ONLY its first line with `// ` — a block step's inner lines and closer stay unprefixed. - `Set Field` takes its calculation POSITIONALLY — `Set Field [ Table::F ; ]`, no `Value:` label (that label belongs to Set Variable; writing it here makes it part of the formula, and the converter warns). - An EMPTY If/Else If condition is written `If [ no condition ]` — no inner brackets (`[no condition]` in brackets becomes a literal formula, and the converter warns). - Quote literal text values with `"…"` (FileMaker calc syntax — backslash escapes for embedded quotes); field references and variables are bare. - If a step you need is NOT covered by a rendering, these examples, or the short-form list above, do not invent its options: emit a comment step instead — `# TODO: - add by hand in FileMaker` — and say so. An honest placeholder costs a moment; a guessed option costs the user's script. ### Worked examples ### Inline step (bracket body, ` ; `-separated items) ``` Set Variable [ $total ; Value: $x + 1 ] ``` ### Inline calc is opaque to the terminal `]` (embedded ` ; ` is data) ``` Set Variable [ $v ; Value: List ( 1 ; 2 ; 3 ) ] ``` ### Fenced multi-line calc (``` opener; body verbatim, block-in-brackets) ```` If [ ``` a and b ``` ] ```` ### Fenced multi-line calc as ONE ITEM of an inline body (the common shape) ```` Set Field [ Invoice::Total ; ``` Let ( [ base = 1 ; tax = base * 2 ] ; base + tax ) ``` ] ```` ### Comment — single line (the multi-line form uses a plain fence; see the EBNF) ``` # set up the loop ``` ### Disabled step (`// ` prefix) ``` // Perform Script [ "Child" ; Parameter: "param" ] ``` ### Whole-script wrapper (`Script "name" { … }`) ``` Script "MyScript" { # set up Set Variable [ $i ; Value: 1 ] } ``` ### Preserve-verbatim (an unmodeled step degrades, never corrupts) ``` Some Future Step [ ## not-yet-pretty: preserved verbatim ## ] >>> preserved-fmxml data <<< ``` ### Minimal-form worked examples (generation) Each pair below is verified against the live converter when this document is built: the minimal text parses cleanly and the second block is the converter's own canonical echo of it. ### Send Mail — recipient/subject/message is enough (e-mail client mode; configure the account in FileMaker) You may write: ``` Send Mail [ To: "ops@example.com" ; Subject: "Nightly import" ; Message: "See attached" ] ``` The converter understands it as: ``` Send Mail [ With dialog: Off ; Multiple emails: Off ; Via SMTP: Off ; Via OAuth: Off ; SMTP encryption: SMTPEncryptionNone ; SMTP authentication: SMTPAuthenticationNone ; OAuth provider: OAuthProviderGoogle ; To: "ops@example.com" ; Subject: "Nightly import" ; Message: "See attached" ] ``` ### New Window — name and layout are enough (Document style, standard controls) You may write: ``` New Window [ Layout: "Orders" ; Window Name: "Report" ] ``` The converter understands it as: ``` New Window [ Layout: "Orders" ; Window Name: "Report" ; Window Style: Document ; Close: Yes ; Minimize: Yes ; Maximize: Yes ; Resize: Yes ] ``` ### Sort Records — the sort order is enough (no dialog, order restored) You may write: ``` Sort Records [ SortList { Orders::Customer → Ascending Orders::Total → Descending } ] ``` The converter understands it as: ``` Sort Records [ With dialog: Off ; Restore: On ; SortList { Orders::Customer → Ascending Orders::Total → Descending } ] ``` ### Show Custom Dialog — block form, any Button 1..3 subset You may write: ``` Show Custom Dialog [ Title: "Delete?" ; Message: "This cannot be undone" ; Button 1 (commit): "Delete" ; Button 2: "Cancel" ] ``` The converter understands it as: ``` Show Custom Dialog [ Title: "Delete?" ; Message: "This cannot be undone" ; Button 1 (commit): "Delete" ; Button 2: "Cancel" ; Button 3: (empty) ] ``` ### Data files — one omitted option each (Create folders / Write as / Read as take factory values); Open Data File is a block You may write: ``` Create Data File [ Target file: $path ] Open Data File [ Source file: $path ; Target: $fileID ] Write to Data File [ File ID: $fileID ; Data source: $text ] Read from Data File [ File ID: $fileID ; Target: $out ] Close Data File [ File ID: $fileID ] ``` The converter understands it as: ``` Create Data File [ Create folders: On ; Target file: $path ] Open Data File [ Source file: $path ; Target: $fileID ] Write to Data File [ File ID: $fileID ; Data source: $text ; Write as: UTF-16 ] Read from Data File [ File ID: $fileID ; Target: $out ; Read as: Bytes ] Close Data File [ File ID: $fileID ] ``` ### Control flow — conditions inline, bodies indented 4 per level (the converter normalizes indentation on step lines) You may write: ``` If [ Get ( FoundCount ) > 0 ] Loop [ Flush: Always ] Set Field [ Invoices::Status ; "Overdue" ] Go to Record/Request/Page [ Next ; Exit after last: On ] End Loop End If ``` The converter understands it as: ``` If [ Get ( FoundCount ) > 0 ] Loop [ Flush: Always ] Set Field [ Invoices::Status ; "Overdue" ] Go to Record/Request/Page [ Next ; Exit after last: On ] End Loop End If ``` ### Find pattern — enter find mode, set criteria as data, perform You may write: ``` Enter Find Mode [ Pause: Off ] Set Field [ Invoices::Status ; "Open" ] Set Field [ Invoices::Due_Date ; "<" & Get ( CurrentDate ) ] Perform Find ``` The converter understands it as: ``` Enter Find Mode [ Pause: Off ] Set Field [ Invoices::Status ; "Open" ] Set Field [ Invoices::Due_Date ; "<" & Get ( CurrentDate ) ] Perform Find ``` ### Subscripts and results You may write: ``` Perform Script [ "Refresh_Tokens" ; Parameter: "force" ] If [ Get ( ScriptResult ) = -1 ] Exit Script [ Result: -1 ] End If ``` The converter understands it as: ``` Perform Script [ "Refresh_Tokens" ; Parameter: "force" ] If [ Get ( ScriptResult ) = -1 ] Exit Script [ Result: -1 ] End If ``` ### Everyday one-liners — the URL / field target / duration slot is enough (omitted options take factory values) You may write: ``` Open URL [ "https://status.example.com" ] Go to Field [ Invoices::Status ] Pause/Resume Script [ Duration (seconds): 2 ] ``` The converter understands it as: ``` Open URL [ With dialog: Off ; Option: Off ; "https://status.example.com" ] Go to Field [ Select/perform: Off ; Field: Invoices::Status ] Pause/Resume Script [ for: 2 ] ``` ### Windows — select by name, resize with just the dimensions you mean (Current window is the factory target) You may write: ``` Select Window [ by name: "Reports" ] Move/Resize Window [ Height: 600 ; Width: 900 ] ``` The converter understands it as: ``` Select Window [ by name: "Reports" ; Current file only: On ] Move/Resize Window [ Current file only: On ; Current window ; Height: 600 ; Width: 900 ] ``` ### Perform Script on Server — FileMaker's own display order; omit the wait item for the factory On You may write: ``` Perform Script on Server [ "Nightly Rebuild" ; Parameter: "full" ; Wait for completion: Off ] ``` The converter understands it as: ``` Perform Script on Server [ Wait for completion: Off ; "Nightly Rebuild" ; Parameter: "full" ] ``` ### Insert Text — BLOCK form; `text:` is LITERAL text (no quotes, not a calculation), `Target:` is a field or variable You may write: ``` Insert Text [ text: Reviewed - do not edit ; Target: Orders::Note ] ``` The converter understands it as: ``` Insert Text [ Select entire contents: On ; text: Reviewed - do not edit ; Target: Orders::Note ] ``` ## 6. Conformance — and what the converter will tell you You do not have to verify anything yourself, and you cannot — the check needs the converter. When your text is pasted back, the plugin converts it and verifies that every **pretty-rendered** step round-trips; if one does not, it REFUSES and reports the failing line rather than writing something wrong. So for ordinary steps the failure mode of a bad edit is a clear error, not a corrupted script. **The exception is a `>>> preserved-fmxml` block**, whose contents are passed through unchecked — see §2. The feedback you (or the user relaying it) may get, and how to react: - **A refusal names one line** (`push failed at line N: …`). Fix that line and resubmit — everything before it was fine. Wrapper mistakes get their own messages (a Script body opens with `{` alone on the next line and closes with `}` alone — there is no `End Script`). - **Warnings do not block** — the text WAS converted. `line N was understood differently` / `line N is not in the converted result` / `near line N the result carries an extra line` mean the parse read something other than what that line looks like — compare against the canonical forms here and correct if unintended. A long divergence list is capped (`only the first 20 differences are listed …`). Advisory warnings (`push-verify: line N - …`) flag text that parsed but plausibly means something you did not intend (see §5b's Set Field and `[no condition]` notes). - **A round-trip refusal names no line** (`push refused — the converted XML did not survive FMSB's own round-trip check …`): that is the converter refusing its OWN output, not a problem with your text — the user should report it via Report bug, and your text is unchanged. - **Silence is the normal case.** Omitted-option defaults, FM-display spellings, step-line indentation, block-line reordering and a single-line fenced value the converter shows inline all normalize without comment — alone or composed. - **`#!#REDACTED#!#`** in text you were given marks a redacted secret. Never replace it with an invented value — leave it (the push will guide the user through restoring real values in FileMaker). **Return the text raw, and ONLY the text.** A prose preamble or trailing explanation is not script text and refuses the whole push. **Backtick fences are STRUCTURE in this grammar, never formatting.** A ````` line is the calculation-fence token: it may open a multi-line value only directly after a `label:` header (or a lone `#` for a multi-line comment), and its closer belongs to that same value. Do not wrap your whole answer in a markdown code fence, and do not fence SECTIONS of the script for readability either — a bare ````` line anywhere a step should be refuses the push (the message says which line). Backticks inside quoted text or a calculation are data and are fine; it is only the bare fence LINE that is structural. What that means for you: it is always better to leave a step alone than to produce text you are unsure of. This document is version `1.103` — if you are shown text produced by a different version, prefer the shapes you can see in it over the shapes described here.