PragmasHelp.md
gettimothy via Squeak-dev <[email protected]>
| Newsgroups | gmane.comp.lang.smalltalk.squeak.general |
|---|---|
| Message-ID | <[email protected]> |
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]