Amber for Parrot
Amber for Parrot was an Eiffel-and-Ruby-flavoured scripting language for the Parrot virtual machine, built by Roger Browne in 2005 to prove that Design by Contract belonged in a dynamic scripting language.
Created by Roger Browne
Amber for Parrot was a short-lived scripting language written by Roger Browne between May 2005 and February 2006, targeting the Parrot virtual machine that the Perl 6 project was building at the time. Its premise was unusual and, in 2005, close to heretical: that a scripting language could be as convenient as Ruby while still carrying Eiffel’s Design by Contract machinery - preconditions, postconditions, class invariants, loop variants - as first-class syntax rather than as a testing library bolted on afterwards.
It never finished. The last release, 0.4.2 in February 2006, estimated its own progress towards 1.0 at 47 percent of the intended language constructs and 6 percent of the intended libraries. But Amber is a useful fossil, because it is one of the clearest surviving examples of what Parrot’s multi-language promise actually looked like when someone took it seriously.
Not to be confused with: Amber Smalltalk (a Smalltalk that compiles to JavaScript), or the modern Amber language that compiles to Bash at amber-lang.com. All three are unrelated projects that happen to share a name.
History and Origins
Roger Browne was a long-standing figure in the Eiffel community - the operator of Eiffelzone, a contributor to Eiffel tooling, and a regular at British Isles Eiffel user group meetings. Amber grew out of a specific frustration visible throughout its documentation: Eiffel had the best story in the industry about software correctness and reuse, and essentially none of the convenience that was making Perl, Python and Ruby the languages people actually reached for.
Parrot offered a way out. It was designed as a register-based virtual machine for dynamic languages, with a garbage collector, a rich object model built on PMCs (Polymorphic Containers), and an explicit ambition to host many languages at once and let them call each other. Writing a language for Parrot meant not having to write a runtime, an object system, a regular-expression engine or a GC.
The first release, 0.2.0 “Proof of concept”, appeared on 16 May 2005. Browne announced 0.2.1 “Hack Pie” to the perl6-internals mailing list on 5 June 2005, describing it as a scripting language whose syntax and semantics were largely inspired by Eiffel, with “anonymous agents” that behaved like Ruby blocks. The release was Linux-only, required Parrot 0.2.1, and was built with SmartEiffel 2.1 - the Amber compiler was itself written in Eiffel.
That last detail created an immediate distribution problem, and its solution is characteristic of the project’s practicality. Five days later, release 0.2.1b shipped the SmartEiffel-generated C code alongside the Eiffel sources, so that someone who wanted to try Amber did not first have to install an Eiffel compiler. The project’s own installation instructions ranked “install Amber from a C package” as the easiest route for most people.
The pace through the rest of 2005 was brisk - nine releases between May and December - and the version numbers were not vanity: Amber’s version tracked the Parrot version it targeted. Amber 0.4.2 was built against Parrot 0.4.2, and appeared roughly a week after it.
Design Philosophy
The project published a philosophy page, and it is worth reading as a period document of what a working Eiffel programmer thought scripting languages had got right.
Simple tasks should be easy. The page opens by conceding that scaffolding may be acceptable for a large application, but that hello world should be a one-liner:
print_line("Hello, world!")
There is no class, no main routine, no import. An Amber script file was defined as one or more instructions, optionally followed by feature declarations between private and end, optionally followed by class declarations - so a script grew into a program by accretion rather than by rewrite.
Readability over writability. “A line of code gets written once, but may need to be read and maintained many times by many people” - a straight inheritance from Eiffel’s house style, and the stated justification for keyword-heavy block structure over punctuation.
Interoperate, even at the cost of purity. Explicitly: “It’s important to be able to work with other Parrot-based languages. If this means using some standard Parrot idioms rather than a purer Amber-only solution, it’s worth it.” Very few of Parrot’s guest languages committed to this in writing, and fewer still followed through as far as Amber did.
Counting should be natural. “If you have three apples, you count them 1, 2, 3.” Amber arrays and strings were one-based - and the project’s own to-do list acknowledged the resulting friction, promising “some way … to conveniently interface Amber’s one-based arrays with languages that support only zero-based arrays.”
Freedom from semicolon slavery. Newlines terminated instructions; semicolons were optional separators for multiple instructions on one line.
Key Features
| Feature | In Amber for Parrot |
|---|---|
| Assignment | :=, pronounced “is assigned”; result is the keyword for a function’s return value |
| Contracts | Preconditions, postconditions (including Eiffel’s old from 0.4.0), class invariants, check instructions, loop variants and loop invariants |
| Assertion checking | Off by default from 0.3.1 onwards; enabled with --check, the AMBER_OPTIONS environment variable, or a #-prefixed directive at the top of a script |
| Loops | A single construct, loop ... repeat, with until clauses permitted at the top, middle or bottom - and more than one of them, giving multi-exit loops |
| Conditionals | if/elseif/else/end instructions plus if-expressions, and an inspect case instruction from 0.3.1 |
| Agents | Inline anonymous routines used as blocks and iterators, with read-only access to outer lexicals from 0.4.0 |
| Inheritance | Multiple inheritance, without Eiffel’s feature adaptation; the documentation describes using the same method resolution order as Perl 6 and Python 2.3 onwards |
| Exceptions | do ... rescue ... retry ... recover ... finally ... end; ANY.raise for custom exceptions |
| Parrot interop | external parrot / external pmc class declarations, routine bodies written in PIR, include of .pir files, load of .pbc bytecode, and dynamic library loading |
| Regular expressions | Class RULE, built on Parrot’s Perl 6 Rules engine |
| Source suffix | .am |
The loop
Amber’s most distinctive syntactic decision, and the one that drew the most argument in the comments on its own website, was the loop. Eiffel puts initialization in a from clause and the exit test at the top. Amber dropped the from clause - initialization simply happens before the loop - and let until appear anywhere in the body, as many times as needed:
loop
read_line
until end_of_input
do_something_with(last_line)
repeat
The rationale given: flexible exit positions remove code duplication and eliminate if instructions that exist only to break out of a loop. The repeat terminator - rather than Eiffel’s end - was defended on the grounds that loop ... repeat reads better for an infinite loop, and Browne noted in a 2005 reply that the keyword was “not yet cast in stone.”
Wrapping Parrot
The interoperability story is the part of Amber that still reads as ambitious. A kernel class was typically a thin Amber shell over a Parrot PMC:
class INTEGER
external pmc @@Integer
public
-- queries
abs
-- Absolute value
...
public
-- iterators
times(proc) -- proc: AGENT
...
end
The @@ prefix denoted a Parrot (non-Amber) class name and a single @ a Parrot feature name - notation that replaced an earlier, uglier {{...}} and {...} bracketing in release 0.2.3a. From version 0.3.1 the kernel classes ARRAY, BOOLEAN, CHARACTER, INTEGER, STRING and TABLE were all implemented as Parrot PMCs, which meant an Amber string and a string from another Parrot language were the same object rather than two things needing conversion. Individual routines could be written directly in PIR, or marked external parrot and loaded at runtime from Parrot bytecode.
Evolution
The arc of the release notes is a compressed portrait of building a language on a moving foundation. Early releases add language constructs at speed. Middle releases spend their effort on the runtime boundary - moving functionality out of a hand-written kernel.pir and into PMC-backed kernel classes, three alternative kernel implementations tested along the way. Later releases start reporting bugs that could be on either side of the Amber/Parrot line, which is what happens when your language’s semantics are your host VM’s semantics.
Parrot itself was the problem as much as the platform. It was under heavy, incompatible development throughout this period; the Amber download page warned that “at times, you will require the latest svn version of Parrot in order to use the latest darcs version of Amber,” and release 0.3.0 records capitalising Current, Result, Void, True and False purely to keep a SmartEiffel beta happy. Amber was chasing two moving targets - Parrot and SmartEiffel - with one part-time developer.
The unimplemented list at the end tells you where it stopped. Deferred (abstract) classes were still planned, possibly integrated with Perl 6-style roles. Floating-point and bignum support was “coming.” Multidimensional arrays were partial. Short-form and flat-form documentation tools, standard equipment for Eiffel programmers, were not written. Windows support was noted as “should not be hard” given that both SmartEiffel and Parrot were cross-platform - but according to its own documentation Amber was developed and tested only on Linux, specifically Fedora Core 4, and no Windows release ever appeared. And the kernel, in the author’s own words, was “just a skeleton. Many of the classes are only stubs, with just enough features to test interaction with the Parrot runtime.”
After 0.4.2 the project changed direction rather than continuing. By early 2007 the site had been renamed from “Amber for Parrot” to “the Amber Scripting Language,” and described the Parrot version as “an earlier version,” with development moved to a self-contained interpreter that would not depend on Parrot at all. That interpreter was never released. Snapshots of the site from 2008 and 2010 are unchanged: the same announcement of a self-contained interpreter under development, the same release history ending at 0.4.2, and the same wry status note that “it’s likely that a stable version of the Amber Scripting Language will be released this century.”
Current Relevance
Essentially none, in practical terms. The xamber.org domain no longer resolves; the darcs repository it hosted is gone; Amber does not appear among the actively maintained languages listed on Parrot’s own wiki; and Parrot itself, after reaching 8.x, ceased to be the target of the project that created it when Rakudo moved to MoarVM. Running Amber today would mean reconstructing a 2006 Parrot build, a SmartEiffel-generated C package, and the Fedora-era toolchain around both.
What survives is documentary. The release notes, the philosophy page, the interoperability guide and the language summary are all preserved in the Internet Archive, and together they form an unusually complete record of a one-person language project from the Parrot era - including the honest quarterly-style progress percentages that most language projects would never publish.
Why It Matters
Amber is worth knowing about for three reasons.
It took Parrot’s central claim literally. Parrot’s pitch was that many dynamic languages could share one runtime and interoperate freely. Most guest languages treated Parrot as a compilation target and stopped there. Amber wrote a philosophy point committing to Parrot idioms over language purity, then built its entire type system out of Parrot PMCs so that its integers and strings were everyone else’s integers and strings. That is the strong form of the interoperability argument, and it is instructive that the strong form was rare.
It asked whether contracts belong in scripting. In 2005 Design by Contract was firmly associated with large, statically typed, compiled systems, while scripting was associated with speed of writing and testing after the fact. Amber’s bet was that the two were not opposed - and its treatment of assertion checking as a runtime option, off by default, prefigures how contract-checking and validation layers are commonly deployed today. Modern languages have arrived at parts of this from other directions, but Amber argued it explicitly, from the Eiffel side, in a scripting language.
It is an honest record of why small language projects die. Nothing about Amber failed dramatically. There was no dispute, no better competitor, no abandoned funding. There was one developer, a host VM changing underneath him, a compiler toolchain also changing underneath him, a library that was 6 percent written, and a growing gap between the language’s ambition and the hours available. The release notes make that arc legible in a way that a repository of merged pull requests rarely does - which is a good reason to keep them, and the language, on the record.
Timeline
Notable Uses & Legacy
A proof of concept for Eiffel ideas on Parrot
Amber's stated purpose, in its own 0.2.0 release name and in the 2005 announcement, was to demonstrate that Eiffel concepts - contracts, class invariants, loop variants - could be carried into a dynamic scripting language running on Parrot. That was the use case, and by its author's own progress estimates it never got past roughly half of the intended language and a token library. It has no known production deployments.
Parrot interoperability demonstrations
Amber was one of the more aggressive early tests of Parrot's promise of cross-language interoperation. Its documentation shows Amber classes wrapping Parrot PMC classes, inheriting from them, extending them with new routines, calling built-in Parrot routines, writing individual Amber routines directly in PIR, calling Amber routines from PIR, and loading shared libraries - the kernel class INTEGER, for instance, was written as a wrapper around Parrot's built-in Integer PMC.
The bundled example programs
The distribution shipped a small gallery of demonstration scripts, several of which are the only surviving Amber code most people will ever see: a one-line hello world, factorial and fibonacci, a word-count clone of Unix wc, 99 bottles of beer, a number-guessing game, an interactive regular-expression explorer built on Parrot's Perl 6 Rules, Conway's Game of Life, and amber-square, a graphical example driven through Parrot's SDL wrapper.
Benchmark and test corpus
Release 0.4.0 added an Ackermann implementation to an examples/shootout directory, in the style of the Computer Language Benchmarks Game of the period, and the project accumulated a test suite that grew from 24 scripts at 0.2.0 to roughly 40 additional tests in 0.3.1 alone. No published benchmark results for Amber survive - the shootout example demonstrates that the program runs, not how fast.