Re: PragmasHelp.md

gettimothy via Squeak-dev <[email protected]>
Newsgroups gmane.comp.lang.smalltalk.squeak.general
Message-ID <[email protected]>
I forgot to mention that the link between gptel/mcp-hub and the smalltalk image is from  John's work at   https://github.com/CorporateSmalltalkConsultingLtd/ClaudeSmalltalk#claudesmalltalk 





John, if you are reading this, I had to modify some of the classes to handle stray sockets, etc..I forget the details...



attached is the mcz file with the modifications if you are interested...



thank you for your work.








From: gettimothy via Squeak-dev <[email protected]>
To: "squeak-dev"<[email protected]>
Cc: "gettimothy"<[email protected]>
Date: Mon, 25 May 2026 12:30:40 -0400
Subject: [squeak-dev] PragmasHelp.md



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 -- mailto:[email protected]  
To unsubscribe send an email to mailto:[email protected]

Squeak-dev mailing list -- [email protected]
To unsubscribe send an email to [email protected]
MCP-tty.3.mcz (application/octet-stream, 22.3 KB) - not displayed
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.