Re: PragmasHelp.md

Eliot Miranda <[email protected]>
Newsgroups gmane.comp.lang.smalltalk.squeak.general
Message-ID <[email protected]>
It’s half right. There are glaring errors in every statement. I shall review soon. But please, drop at least half the emphasis.

You might inspect a method’s pragmas and see how they are represented. Show many inform the compiler (the primitive pragmas, the unwind protect pragmas, and the FFI pragmas) and how many do not (all the others).

And since I invented the non-primitive use of pragmas you might talk to me rather than an idiotic AI…

Eliot
_,,,^..^,,,_ (phone)

> On May 25, 2026, at 9:30 AM, gettimothy via Squeak-dev <[email protected]> wrote:
> 
> 
> Hi folks...
> 
> I am working with the LLM that shall not be named to develop PragmasHelpTopic...(currently a parser error in DOC import, I will be fixing it)
> 
> 
> Below is a draft of the PragmasHelp in Markdown format...
> 
> 
> If you are interested in this topic, have suggestions, rants, etc...please let me know.  
> 
> 
> This was generated by having Claude-Code via gptel/mcp-hub interact directly with the DOC smallalk image where I have XtreamsParsing installed...the summary document was generated by Claude.ai...
> 
> 
> I find it fascinating and , if correct, you might find it useful for in-image HelpTopic or perhaps the XTreams package would like to include it.
> 
> 
> cordially,
> 
> t
> 
> 
> # Squeak Pragmas — A Help Topic
> 
> *Part of the DOC project help system. 
> 
> ---
> 
> ## What Is a Pragma?
> 
> A pragma is a **compiler annotation** embedded directly in a Smalltalk method using
> angle-bracket syntax. Pragmas appear at the very top of a method body, before any
> executable statements:
> 
> ```smalltalk
> myMethod
>     <primitive: 'primitiveAt' module: 'CollectionsPlugin'>
>     <signals: #MyException>
>     <preference: 'Show Balloons' category: 'Interface' default: true>
>     ^ self doSomething
> ```
> 
> They are **not executable code**. The compiler reads them, creates `Pragma` objects,
> and attaches them to the resulting `CompiledMethod`. From that point on they are
> first-class objects you can query and dispatch on at runtime.
> 
> The key insight: a pragma is metadata *about* a method — not something the method
> *does*, but something the method *is* or *belongs to*.
> 
> ---
> 
> ## How the Compiler Stores Them
> 
> When the standard Squeak compiler encounters `< keyword: value ... >` at the start of
> a method body it produces `Pragma` instances and attaches them to the `CompiledMethod`:
> 
> ```smalltalk
> "Read pragmas off any compiled method:"
> (MyClass >> #myMethod) pragmas          "=> Array of Pragma objects"
> (MyClass >> #myMethod) pragmaAt: #primitive:   "=> the pragma or nil"
> ```
> 
> This works uniformly for every pragma keyword — `<primitive:>`, `<preference:>`,
> `<action:>`, and any custom keyword you invent. The compiler treats them all the same
> way. Only the *consumer* of the pragma (the VM, a framework, a parser) gives them
> meaning.
> 
> ---
> 
> ## The Pragma Families — Use Cases
> 
> ### 1. VM Dispatch — `<primitive:>`
> 
> The oldest and most common pragma. Tells the VM to attempt a C-level primitive before
> running the Smalltalk method body. If the primitive fails (returns an error code),
> execution falls through to the Smalltalk fallback below.
> 
> ```smalltalk at: index
>     <primitive: 'primitiveAt' module: 'CollectionsPlugin'>
>     ^ self basicAt: index
> ```
> 
> ```smalltalk
> "Also the numeric form — same semantics:"
> basicAt: index
>     <primitive: 60>
>     self primitiveFailed
> ```
> 
> There are two argument forms:
> 
> | Form | Example | Notes |
> |------|---------|-------|
> | Named primitive | `<primitive: 'primName' module: 'Plugin'>` | Plugin-based, most portable |
> | Numbered primitive | `<primitive: 60>` | Historical; numbered slot in VM dispatch table |
> 
> The VM intercepts these at method activation time — no Smalltalk code runs until
> the primitive returns a failure signal.
> 
> **Where you find it:** Collections, Streams, Numbers, ByteArray, String — anywhere
> performance matters. 70+ occurrences in the Tweak C* class family alone.
> 
> ---
> 
> ### 2. Contract and Documentation Pragmas
> 
> These pragmas annotate *what a method promises or warns about*. They are pure metadata —
> no runtime system acts on them automatically; they are there to be queried.
> 
> #### `<signals: #ExceptionClass>`
> 
> Declares the exception class a method may signal. Analogous to Java's `throws` clause.
> 
> ```smalltalk
> openFile: aPath
>     <signals: #FileDoesNotExist>
>     ...
> ```
> 
> ```smalltalk
> "Find all methods in a class that may signal FileDoesNotExist:"
> FileSystem methodDict select: [:m | m pragmaAt: #signals: ]
> ```
> 
> #### `<bewareOf: 'description'>`
> 
> A freeform warning to callers — documents surprising side effects, thread-safety
> concerns, or "this modifies global state" caveats.
> 
> ```smalltalk
> resetAllConnections
>     <bewareOf: 'Drops all open sockets. Do not call during active sessions.'>
>     ...
> ```
> 
> ---
> 
> ### 3. UI Wiring Pragmas (Tweak / Preferences)
> 
> These pragmas wire methods into UI frameworks at load time. The framework scans the
> image for methods carrying these pragmas and uses them to build menus, preference
> panels, and so on — no explicit registration calls needed.
> 
> #### `<preference: name category: cat default: val>`
> 
> Registers a method as a user-editable preference. The Tweak Preferences panel
> discovers all such methods and builds its UI from them automatically.
> 
> ```smalltalk
> showBalloonHelp
>     <preference: 'Show Balloon Help'
>                  category: 'Interface'
>                  default: true>
>     ^ showBalloonHelp ifNil: [true]
> ```
> 
> The pragma carries the display name, the category it belongs to, and the default value.
> The preference panel needs nothing else.
> 
> #### `<menuPriority: n>`
> 
> Controls where a method's entry appears in a dynamically-built menu. Lower numbers
> appear first. Without this pragma, items appear in selector order.
> 
> ```smalltalk
> openInspector
>     <menuPriority: 10>
>     ...
> 
> openDebugger
>     <menuPriority: 20>
>     ...
> ```
> 
> #### `<timeout: milliseconds>`
> 
> Declares a time bound on an operation. Consumers (schedulers, watchdog threads) can
> scan for these and enforce the bound externally.
> 
> ```smalltalk
> connectToServer
>     <timeout: 5000>
>     ...
> ```
> 
> ---
> 
> ### 4. XTreams PEG Parser — `<action:>` and `<action:arguments:>`
> 
> This is the pragma family most directly relevant to the DOC project.
> 
> The XTreams PEG parser (by Levente Uzonyi) uses pragmas to bind grammar rule names to
> Smalltalk methods on a `PEGActor` subclass. The binding is built **once at
> initialization time** — not on every parse — making dispatch a simple Dictionary
> lookup.
> 
> #### How `PEGActor` wires itself up — the real source
> 
> `PEGActor` has exactly three instance-side methods. The key one is `initialize`, which
> scans the subclass's compiled methods for `<action:>` and `<action:arguments:>` pragmas
> and builds a `Dictionary` named `actions` mapping rule names to blocks:
> 
> ```smalltalk
> PEGActor>>initialize
> initialize
>     super initialize.
>     actions := Dictionary new.
> 
>     "Two-argument form: <action: 'RuleName' arguments: #(1 2 ...)>"
>     (Pragma allNamed: #action:arguments: in: self class) do: [ :pragma |
>         | arguments |
>         arguments := pragma arguments.
>         actions at: arguments first put: (
>             self
>                 blockForSelector: pragma selector
>                 filterIndices: arguments second) ].
> 
>     "One-argument form: <action: 'RuleName'>"
>     (Pragma allNamed: #action: in: self class) do: [ :pragma |
>         | selector |
>         selector := pragma selector.
>         actions at: pragma arguments first
>                 put: [:object | self perform: selector with: object ] ].
> ```
> 
> At parse time, `process:object:start:stop:` does nothing but look up the rule name
> in that dictionary and call the block:
> 
> ```smalltalk
> PEGActor>>process:object:start:stop:
> process: name object: object start: start stop: stop
>     ^ ((actions at: name ifAbsent: nil) ifNil: [ ^object ]) value: object
> ```
> 
> If no action is registered for a rule, the raw match result (`object`) is returned
> unchanged. This is the entire runtime overhead — one Dictionary lookup per matched
> rule.
> 
> #### The two pragma forms, from live image inspection
> 
> **`<action: 'RuleName'>`** — single-argument form. The actor method receives the
> complete match result as one argument:
> 
> ```smalltalk
> "In PEGMarkdownActor — actual methods from DOC image:"
> 
> Paragraph: aCollection
>     <action: 'Paragraph'>
>     "aCollection is the raw match result for the Paragraph rule"
>     ...
> 
> ATXHeading: hashCollection text: aCollection
>     <action: 'ATXHeading'>
>     "With this form the full result collection is passed as one object"
>     ...
> ```
> 
> **`<action: 'RuleName' arguments: #(indices)>`** — multi-argument form. The indices
> array selects *which elements* of the match result to extract and pass as separate
> arguments. Index 1 = first sub-match, index 2 = second, etc. Missing positions
> become `nil`:
> 
> ```smalltalk
> HeadingSection: aHelpTopic body: bodyCollection subsections: subCollection
>     <action: 'HeadingSection' arguments: #(1 2 3)>
>     "sub-match 1 → aHelpTopic, sub-match 2 → bodyCollection,
>      sub-match 3 → subCollection"
>     ...
> ```
> 
> `blockForSelector:filterIndices:` handles up to 5 index positions with specialized
> fast paths (1-arg, 2-arg, … 5-arg blocks) before falling back to a general
> `perform:withArguments:` form.
> 
> #### One method, many rules — multiple pragmas on a single method
> 
> A single Smalltalk method can carry **multiple** `<action:>` pragmas. `PEGActor>>initialize`
> calls `Pragma allNamed:in:` which returns all of them, and each one gets its own entry
> in the `actions` Dictionary pointing to the same block.
> 
> `PEGMarkdownActor` uses this to handle all six ATX heading levels with a single method
> rather than six separate callbacks:
> 
> ```smalltalk
> ATXHeading: hashCollection text: aCollection
>     <action: 'ATXHeading1'>
>     <action: 'ATXHeading2'>
>     <action: 'ATXHeading3'>
>     <action: 'ATXHeading4'>
>     <action: 'ATXHeading5'>
>     <action: 'ATXHeading6'>
>     "hashCollection size gives the heading level (1-6).
>      The same method handles all six grammar rules."
>     ...
> ```
> 
> The grammar defines separate rules `ATXHeading1` through `ATXHeading6` (each matching
> the right number of `#` characters), but the actor consolidates them into one place.
> This keeps the semantic code DRY while keeping the grammar rules precise.
> 
> #### `PEGMarkdownActor` — complete instance-side method list (live image)
> 
> From `PEGMarkdownActor methodDict keys asSortedCollection`:
> 
> | Method | Role |
> |--------|------|
> | `ATXHeading:text:` | `<action:arguments:>` callback — `# Heading` syntax |
> | `SetextHeading1:` | `<action:>` callback — underline `===` heading |
> | `SetextHeading2:` | `<action:>` callback — underline `---` heading |
> | `Paragraph:` | `<action:>` callback — plain paragraph |
> | `HeadingSection:body:subsections:` | `<action:arguments:>` callback — assembles HelpTopic tree |
> | `MD:` | `<action:>` callback — top-level rule, returns final collection |
> | `flattenInto:topic:` | Helper — flattens mixed content into a HelpTopic |
> | `initialize` | Overrides `PEGActor>>initialize` to set up caches |
> | `transcription` / `transcription:` | Debug flag — when true, fires trace output |
> | `*cache` accessors | `helptopiccache`, `paragraphcache`, `pagenamecache`, etc. |
> 
> Class-side methods: `parser`, `process:`, `cleanMarkdownInline:`.
> 
> #### The entry point — `PEGMarkdownActor class>>process:`
> 
> ```smalltalk
> process: input
>     ^ self parser
>         parse: 'MD'
>         stream: input
>         actor: self new
> ```
> 
> `parser` builds a `PEGParser` from `Doc grammarMD` (10,841 characters of PEG grammar).
> `parse:stream:actor:` runs the `MD` top-level rule against the input stream, routing
> every successful rule match through the actor's `process:object:start:stop:` dispatch.
> 
> #### Live parse result — `# Hello World\n\nA paragraph here.\n`
> 
> ```
> OrderedCollection(
>     HelpTopic
>         title:    'Hello World'
>         key:      'hello-world'      ← auto-generated slug
>         priority: 1                  ← heading level
>         contents: 'A paragraph here.'
>         subtopics: OrderedCollection()
> )
> ```
> 
> With nested headings the tree deepens: H2 sections become subtopics of their H1
> parent, H3 sections become subtopics of H2, and so on. The root `OrderedCollection`
> always contains only top-level H1 topics.
> 
> ```
> [H1] Hello World
>   [H2] Section Two
>     [H3] Subsection
> ```
> 
> #### The full parsing pipeline in DOC
> 
> ```
> Doc grammarMD               (PEG grammar string — class-side on Doc)
>         ↓
> PEGMarkdownActor class>>parser
>         ↓
> PEGParser                   (compiles grammar, drives the match engine)
>         ↓
> PEGParser>>parse:stream:actor:
>         ↓
> PEGActor>>process:object:start:stop:   (one Dictionary lookup per rule match)
>         ↓
> PEGMarkdownActor callbacks  (action: pragmas wired at initialize time)
>         ↓
> HelpTopic tree              (nested in-image Squeak help objects)
>         ↓
> SeasideDoc / HelpBrowser / .texi → .info
> ```
> 
> The same `<action:>` mechanism powers all DOC actor subclasses — the Org-mode actor,
> the Markdown actor, and eventually the Wikitext actor. Only the grammar string and the
> callback methods change; the dispatch machinery in `PEGActor` is shared.
> 
> ---
> 
> ### 5. Structural / Slot Pragmas
> 
> Present in later Tweak packages (not yet loaded in the current DOC image). These
> operate at class-definition time rather than method time:
> 
> | Pragma | Purpose |
> |--------|---------|
> | `<field: #name type: #Type>` | Declares a typed instance variable |
> | `<slot: ...>` | Slot declaration variant |
> 
> Unlike the method-level pragmas above, these describe the *shape* of an object.
> They require special handling at class instantiation time, outside the normal
> method compilation pipeline. Design work deferred to Tweak-Costume / Tweak-Basic
> loading stages.
> 
> ---
> 
> ## Querying Pragmas at Runtime
> 
> Because pragmas are first-class objects on `CompiledMethod`, you can search for them
> programmatically. This is how frameworks like the Preferences panel and the XTreams
> parser discover their callbacks without any explicit registration:
> 
> ```smalltalk
> "All methods in a class that declare exception signals:"
> MyClass methodDict select: [:m | m pragmaAt: #signals: ]
> 
> "Find all preference-bearing methods system-wide:"
> Smalltalk allClasses flatCollect: [:cls |
>     cls methodDict select: [:m | m pragmaAt: #preference: ]]
> 
> "Find all XTreams action callbacks on PEGMarkdownActor:"
> PEGMarkdownActor methodDict select: [:m | m pragmaAt: #action: ]
> 
> "Find all primitive implementations, print class>>selector:"
> Smalltalk allClasses do: [:cls |
>     cls methodDict do: [:m |
>         (m pragmaAt: #primitive:module:) ifNotNil: [:p |
>             Transcript show: cls name , '>>' , m selector; cr]]]
> 
> "Inspect a pragma object directly:"
> | p |
> p := (PEGMarkdownActor >> #Paragraph:) pragmaAt: #action:.
> p keyword.    "=> #action:"
> p arguments.  "=> #('Paragraph')"
> ```
> 
> ---
> 
> ## The Compiler Pipeline — Where Pragmas Live
> 
> ```
> Source text  (method text as String)
>         ↓
> Compiler parser  (Scanner → Parser → MethodNode)
>         ↓  recognizes <keyword: ...> at method head
> Pragma objects   (attached to MethodNode)
>         ↓
> Encoder (EncoderForSistaV1)
>         ↓
> CompiledMethod   (Pragma objects stored in literals array)
>         ↓
> Runtime query:   (CompiledMethod >> pragmas)
>                  (CompiledMethod >> pragmaAt: keyword)
> ```
> 
> The pragma is stored in the literals array of the compiled method, alongside
> the actual literal constants the method uses. This is why `pragmas` is fast —
> it is a direct array lookup, not a parse of source text.
> 
> ---
> 
> ## Writing Your Own Pragma-Driven Framework
> 
> The pattern is always the same:
> 
> 1. Choose a keyword (e.g. `<myFramework: 'name'>`).
> 2. Define a method on `CompiledMethod` or use `pragmaAt:` to query for it.
> 3. At startup or load time, scan the classes you care about and collect
>    all methods carrying your pragma.
> 4. Build your data structure (menu, registry, dispatch table) from the results.
> 
> ```smalltalk
> "Skeleton of a pragma-driven registry:"
> MyRegistry class >> initialize
>     registry := Dictionary new.
>     Smalltalk allClasses do: [:cls |
>         cls methodDict do: [:m |
>             (m pragmaAt: #myFramework:) ifNotNil: [:p |
>                 registry at: (p argumentAt: 1) put: m]]]
> ```
> 
> This is exactly how `<preference:>`, `<menuPriority:>`, and `<action:>` all work.
> 
> ---
> 
> ## Further Reading
> 
> - `Pragma` class — browse its class comment in the image
> - `CompiledMethod >> pragmas`, `CompiledMethod >> pragmaAt:`
> - `PEGActor` and `PEGMarkdownActor` — live examples in the DOC image
>   (package `Doc-Xtreams-Parsing`)
> - Squeak mailing list: Eliot Miranda on CCompiler as Facade (May 2026)
> - *TimmyAndClaudeLearnSqueak* — chapters SqueakCompilerPipeline and
>   TweakCompilerAndFunctionality
> - XTreams Parsing wiki: https://code.google.com/archive/p/xtreams/wikis/Parsing.wiki
> 
> ---
> 
> *First draft — DOC project, May 2026.*
> *Source: Squeak_Pragmas_Summary.md + live image analysis.*
> 
> 
> 
> 
> Squeak-dev mailing list -- [email protected]
> To unsubscribe send an email to [email protected]

Squeak-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.