Our next example prints the lyrics of the whimsical song "99 Bottles of Beer", a feat popularized at this web site. If executed without any command arguments, this program prints the entire song with 99 bottles; with a positive integer argument, the song begins with that many bottles.
This uses the simpler way of introducing methods described in the IDE section; we'll use it for the remainder of this document. To review, for methods *- means static, +- is for constructors, and =- introduces instance methods. Classes are introduced with /- and a symbol giving the namespace.
SABLE assemblybeersong.exe {~ console entryClass: #BottlesOfBeer.SONG method: #main:; reference: 'mscorlib.dll'; use: #System} /-#BottlesOfBeer "Class definition(s) in this namesapce"SONG {~ object static} "Simple class which prints the bottles-of-beer song. This is not o-o, but it shows some nice features of the SABLE language." *-'entrypoint' secret "Static method definition(s) in the last-defined class"main: args {ARRAY[STRING]}. "Sing the bottles song using the command line argument as the initial number, or 99 if none is provided." "Obtain the number of bottles from the first argument, or default to 99." |bottleCount| := 99. args notEmpty then: [[bottleCount := INT32 parse: args first] try catch: [:exc {DIVIDE_BY_ZERO_EXCEPTION} | "Never hits; just showing syntax"]; catch: [:exc | bottleCount := 0]]. "Print an error, or sing the song." bottleCount <= 0 then: [CONSOLE writeLine: 'Argument must be a positive integer'] else: [THIS_CLASS sing: bottleCount]. *-'singing'sing: bottles {INT32}. {~ cilName: 'Sing'} "Allows other languages to call the method using this name." "Sing the bottles song using :bottles as the initial number. The typical value is 99." {~ require: 'bottles positive' as: [bottles > 0]} "Precondition" bottles to: 0 by: -1 do: ">#until:by:do: would not iterate with [count=0]; this will." "The grave accent character can be used to visually demarcate structures." [:count | `"When we run out of bottles, start over at the beginning." `|minus1| := count - 1 ifLess: [bottles]. ` "->#ifLess: means 'if less than zero'. Related methods are >#ifZero: and >#ifMore:." ` `CONSOLE ` write: ` "'Constant Block' structure is evaluated during type initialization." ` ##['{0} on the wall, {1}.$LINE`' "-Adjacent strings are concatenated" ` '{2}, {3} on the wall.$LINE`' "- which allows for nice formatting." ` '$LINE`' lineEscaped] "-Substitutes the platform line terminator." ` withAll: ` "'Runtime Array' structure creates a new array each time it is reached." ` #(SONG bottles: count capitalized: True, ` SONG bottles: count capitalized: False, ` "We could use a conditional, but let's show switch-case resulting in a value." ` (count switch ` case: 0 do: ['Go to the store and buy some more']; ` else: ['Take one down and pass it around'] ), ` SONG bottles: minus1 capitalized: False)]. bottles: qty {INT32} capitalized: capitalized {BOOLEAN} ^{STRING}. "A string stating the number of bottles of beer given by :qty, either '# bottles...', '1 bottle...', or 'No more bottles of beer'. When :qty is 0, 'No more' is capitalized only if :capitalized is True." ^'{0} {1} of beer' format: "'Cascaded Conditional' can result in a value." (IF test if: [qty ~= 0] then: [qty to_STRING]; if: [capitalized] then: ['No more']; else: ['no more']) and: "'Constant Array' structure is evaluated during class initialization. BOOLEAN converts to numeric types as 0 or 1 using a 'typecast' expression." (##('bottle', 'bottles') at: (qty ~= 1) ~{INT32})
Our version is not object-oriented. After all, the o-o approach is just a tool appropriate for many, but not all, tasks. Using it for this is like driving a nail with a wrench; you can do it, but it's not pretty. Still, this simple example shows some nice features of the SABLE language and class additions.
You can find shorter versions of BeerSong in other languages, but "lines of code" isn't the only, and not the best, way to make a comparison. Ask yourself, do they output the song 100% correctly? (This does.) Are they well documented? (This has comments both on the process and on language syntax.) Most importantly, is it easy to understand how they work, even if you're new to the language? I believe SABLE compares extremely well in this category. Some of the BeerSong contributions read like hieratic Egyptian.
This code contains a switch-case and SABLE's equivalent of if-then-elseif-else, both of which can optionally return a value. We also see an exception handler and a numeric loop. All of these are defined as methods.
For example, SABLE adds to the core library the interface PRIMITIVE_ITERABLE. It's implemented by the numeric classes for which such iteration makes sense, including CHAR, and defines the following looping methods. Notice how these declarations define the block argument's type using MyType, which acts like a method parameter bound to the type of the message receiver.
~[U].to: last {MyType} by: step {U} do: block {BLOCK[MyType]}. {~ primitive: #to:by:do:} "Evaluate :block with an index starting with :Me, increasing by :step, until and including when the index reaches :last. Parameter U is set by the compiler to {MyType} for signed types; for an unsigned receiver, it will be the corresponding signed integer type, e.g. for BYTE, U=SBYTE; for CHAR, U=INT16; etc." to: last {MyType} do: block {BLOCK[MyType]}. {~ primitive: #to:do:} "Evaluate :block with an index starting with :Me, increasing by 1, until and including when the index reaches :last."
PRIMITIVE_ITERABLE similarly declares >#until:by:do: and >#until:do: which stops when the #until: argument value is reached.
Our example uses a new control structure declared on the SABLE-added interface PRIMITIVE_NUMBER, implemented by the number classes. It returns the receiver or the result of evaluating a block, depending on the receiver's relationship with zero. The block is evaluated only if needed, of course.
ifLess: block {BLOCK[^MyType]} ^{MyType}. {~ primitive: #ifLess:; nonfunctional} "Myself if zero or greater, result of :block if I'm less than zero."
This and related methods >#ifMore:, >#ifZero:, and >#ifLess:ifZero:ifMore: work well with the result of COMPARABLE>~>#compareTo: and its alias >#<?>:.
Finally, the exception handling >#catch: method is defined in such a way that the given block can declare its argument's type as a subtype of {EXCEPTION}, or omit it to imply type {EXCEPTION}.
sing: bottles {INT32}. {~ require: 'bottles positive' as: [bottles > 0]} "Precondition"
The >#sing: method declares a precondition with a meaningful name and a Boolean-valued block which it expects to evaluate to True on entry. If the program was compiled with precondition checking and the block result is False, then the method will throw an exception with the message "Precondition failed: bottles positive". Other similar pragma messages are >#ensure:as: for method postconditions, >#invariant:as: for use within a class body, and >#assert:as: for use in a method body. Also, there are versions without #as: which accept only the Boolean block. A detailed description of these is beyond the scope of this document.
In our BottlesOfBeer example, we saw the following structure:
##['{0} on the wall, {1}.$LINE`' lineEscaped].
This is called a Constant Block; it evaluates its contents during type initialization and stores the result (if any) in a generated static "initonly" field. It cannot accept arguments nor reference instance fields or method local variables. This is often used to build data structures which are referenced only in one place. (>#lineEscaped doesn't need a constant block, but I wanted to show the syntax.)
We didn't have opportunity to use it here, but you probably guessed that a block structure with this form #[:arg | ...] exists. This is called a Delegate Block. It's SABLE's "lambda expression" syntax used to create delegates and other objects. Like runtime blocks, their argument and result types are usually inferred from the context where they appear. We'll see more about these when we look at the delegate class and how to extend SABLE.
|strings| := ##('bottle', 'bottles'). |colors| := ##{CONSOLE_COLOR}(#Red, #Green, #Blue). |array1| := #(anEnumerable, aCollection, aList, aDictionary). |array2| := #{ENUMERABLE}(aDictionary, aList, aCollection, anEnumerable).
Here are two structures, each shown in two forms, which we saw in our example. They all produce standard 0-based 1-dimensional arrays from a comma-separated list of expressions which make up the elements. ##() is a Constant Array; it is created when the type is initialized, and has the same natural restrictions as the Constant Block. #() is a Runtime Array; a new array is created and each element expression is reevaluated when this structure is reached. ##(...) is shorthand for ##[#(...)].
In two of the examples, a TypeRef appears after the hash marks that begin the array structure. If provided, it establishes the element type of the array. If not provided, the element type is inferred from the context.
##('bottle', 'bottles') at: (qty ~= 1) ~{INT32}
This selects one of two strings, at index 0 or 1, from a Constant Array depending on whether [qty ~= 1]. But that results in a Boolean and >#at: needs an integer. That thing at the end (~{INT32}) is a Typecast; like a unary message, it follows the expression being cast and it has the same precedence. Here it converts Boolean False to 0 and True to 1.
This line could have been a simple >#then:else: conditional, of course. The purpose here is to show you Constant Array and Typecast syntax.