Headless Pharo, GNU Smalltalk & Deployment

41.1 Command-line Pharo

A script (script.st):

Stdio stdout, Stdio stderr, and Stdio stdin give access to the standard streams; inside a command-line handler, self arguments answers the command-line arguments.

./pharo Pharo.image --help                                  # list command-line handlers
./pharo Pharo.image eval "(1 to: 10) inject: 0 into: [:a :b | a + b]"     # → 55
./pharo Pharo.image eval --save "Metacello new baseline: 'Bank'; repository: 'github://me/bank:main/src'; load"
./pharo Pharo.image st --quit script.st                     # run a script file and quit
./pharo Pharo.image test "Bank-.*"                          # run tests for matching packages
./pharo Pharo.image save BankDev                            # save a copy as BankDev.image
./pharo-ui Pharo.image                                      # open with the UI
| total |
total := (1 to: 100) inject: 0 into: [ :a :b | a + b ].
Stdio stdout
    nextPutAll: 'Sum: ' , total printString;
    lf;
    flush.
Smalltalk snapshot: false andQuit: true.

41.2 Custom command-line handlers

Recent Pharo also includes CLAP for declarative command-line parsing.

CommandLineHandler << #GreetCommandLineHandler slots: {}; package: 'Bank-CLI'.

GreetCommandLineHandler class >> commandName
    ^ 'greet'                          "./pharo Pharo.image greet Ada  → Hello, Ada"

GreetCommandLineHandler >> activate
    Stdio stdout nextPutAll: 'Hello, ' , (self arguments ifEmpty: [ #('world') ]) first; lf; flush.
    self exitSuccess

41.3 Deploying a server image

Start from a clean, versioned image (zeroconf or CI).

Load your baseline; run tests.

Register startup code so the server starts when the image launches:

Save the image and ship it with the VM (or build a container).

Run headless under a supervisor (systemd, Docker, Kubernetes); log to stdout.

Dockerfile sketch:

Other dialects: VisualWorks *Runtime Packager*, VA Smalltalk *Packager* (stripped images), Dolphin Smalltalk produces Windows executables, GemStone deploys code into the shared repository.

BankApp class >> startUp: resuming
    resuming ifTrue: [ self start ]

SessionManager default registerUserClassNamed: #BankApp.
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl unzip ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN curl -L https://get.pharo.org/64/130+vm | bash
COPY src /app/src
RUN ./pharo Pharo.image eval --save "Metacello new baseline: 'Bank'; repository: 'tonel:///app/src'; load"
EXPOSE 8080
CMD ["./pharo", "Pharo.image", "eval", "--no-quit", "BankApp start"]

41.4 GNU Smalltalk: file-based scripting

GNU Smalltalk (gst) runs plain text files, with a bracketed class syntax:

Reading input: stdin linesDo: [ :line | line displayNl ]. GNU Smalltalk also includes packages for sockets, XML, databases, and a Seaside port (older), but development has been largely inactive since 3.2.5.

#!/usr/bin/gst -f
"animals.st — run with: gst animals.st"

Object subclass: Animal [
    | name |
    <comment: 'An animal with a name'>

    Animal class >> named: aString [
        <category: 'instance creation'>
        ^ self new setName: aString
    ]

    setName: aString [ name := aString ]
    name [ ^ name ]
    sound [ ^ self subclassResponsibility ]
    speak [ ^ name , ' says ' , self sound ]
]

Animal subclass: Dog [
    sound [ ^ 'Woof' ]
]

| animals |
animals := OrderedCollection new.
animals add: (Dog named: 'Rex'); add: (Dog named: 'Fido').
animals do: [ :a | a speak displayNl ].
(3/4) printNl.                        "printNl: printString + newline"
'Hello from GNU Smalltalk' displayNl.  "displayNl: displayString + newline"
Smalltalk arguments printNl.           "command-line arguments (gst -f file.st a b c)"
gst animals.st
gst -f animals.st arg1 arg2
gst                                    # interactive REPL

41.5 Chunk format (file-out)

The traditional interchange format, readable by Squeak, Pharo (FileStream fileIn:/drag-and-drop), VisualWorks, and GNU Smalltalk:

Chunks are separated by !; !Class methodsFor: 'protocol'! starts a series of method chunks terminated by ! !.

Object subclass: #Greeter
    instanceVariableNames: 'name'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Demo'!

!Greeter methodsFor: 'greeting'!
greet
    ^ 'Hello, ' , name! !

41.6 Dialect differences cheat sheet

VisualWorks / VA Object << #Foo slots: {...}; package: 'P'

Object subclass: #Foo instanceVariableNames: '...'... category: 'P' Object subclass: Foo [ | a |... ]

Smalltalk defineClass: #Foo superclass:... (VW) / subclass:... (VA) Git + Tonel (Iceberg)

Monticello .mcz (Squeak), Git .pck.st (Cuis) Store / ENVY (VA), parcels new calls initialize no — call it yourself Morphic + Spec2 Morphic (and MVC) Blox (Tk/GTK, old)

VisualWorks UI painter / VA Composition Editor yes (VW namespaces)

🧪 Part III Exercises

Write a method that, given a class, generates accessors for all its slots at runtime with compile:classified:; then remove them again.

Use thisContext to implement Object>>callerName, answering the selector of the method that called the current method.

Instrument OrderedCollection>>add: temporarily with a MetaLink that counts calls while opening a Playground; uninstall it afterwards.

Create a TComparableByKey trait providing <, <=, >, >=, and between:and: from a required key method; use it in two unrelated classes.

Implement the Visitor pattern for an arithmetic expression tree with Evaluator, Printer, and Simplifier visitors.

Add Announcements to a TodoList model (ItemAdded, ItemRemoved, ItemCompleted) and write a Transcript logger and a counter that subscribe to them.

Write a producer/consumer pipeline with three processes and two SharedQueues that reads lines from a file, uppercases them, and writes them to another file; make it terminate cleanly.

Build a Spec2 application for the TodoList with a list, a text input, Add/Remove/Complete buttons, and live updates via Announcements.

Write a Teapot REST API for the todo list with JSON output (STONJSON or NeoJSON) and test it with ZnClient.

Call qsort (or strlen, getpid, time) from libc with UFFI; then profile (1 to: 1000000) asArray shuffled sorted with the Time Profiler.