SABLE defines four kinds of literals: numbers, characters, strings, and symbols.
Number literals have the form shown below, where each word represents a sequence of digits, the brackets enclose optional elements, and the slash means "OR". Other characters represent themselves.
[+/-] [ BASE # ] VALUE [ . FRACTION ] [ ^ [+/-] EXPONENT ] [ $ [+/-] WIDTH ]
The meaning of each element is:
| BASE | The number base in range 2-36 for the digits in VALUE and FRACTION. If not provided, the base is 10. E.g. 16#B and 2#1011 both equal decimal 11. |
| VALUE | The integer part of the number. For number bases over 10, A-Z represent digits 10-35. (Upper case is recommended.) |
| FRACTION | The fractional part, also using BASE digits. E.g. 16#0.C equals (12 / 16), or 0.75. |
| EXPONENT | If present, VALUE.FRACTION is multiplied by BASE to the EXPONENT power, which is the same as shifting the decimal point to the right by EXPONENT number of BASE digits, or left if EXPONENT's sign is negative. E.g. 16#0.4^2 equals 16#40, or 64. |
| WIDTH | If present, it explicitly specifies the literal number's type. (See below.) |
BASE, EXPONENT, and WIDTH are always expressed in base 10.
If either FRACTION or EXPONENT is present, then the literal represents a floating-point number, otherwise it's an integer. For a float with no WIDTH, the type defaults to {FLOAT32}. For an integer, then the type is either {INT32}, {INT64}, or {UINT64}, the first of which can represent the integer value.
Use WIDTH to specify a literal number's type when declaring a variable or when it's a message receiver. WIDTH gives the number of bits. Types default to signed except for WIDTH 08; the WIDTH sign can alter the type, where "-" means a signed type and "+" makes it unsigned.
|byteval| := 253$08. "{BYTE}" |ulongOne| := 1$+64. "{UINT64}" 0$+16 until: 256 do: [:chval "{UINT16}" | ...].
A character literal begins with a dollar sign and has one of these forms:
| Form | Examples | Description |
|---|---|---|
| Character | $( $) $. $$ $` $9 $X | Any single printable character can define a character literal as itself. For whitespace and control characters, use either of the following two forms. |
| Integer | $07 $32 $8#60 $16#431 | A multi-character number, which must be an integer 0..16#FFFF without a WIDTH specifier, defines a character by numeric value. Note: $7 = $55. |
| Word | $NUL $LF $ESC | A multi-character Word names a character from the list below. |
The set of character literals currently defined are:
| Char | Value | Description |
|---|---|---|
| $NUL | 0 | Often ends a platform string |
| $ALERT | 7 | Usually rings a bell when output |
| $BACK | 8 | Backspace |
| $TAB | 9 | Horizontal tab |
| $LF | 10 | Line feed |
| $VTAB | 11 | Vertical tab |
| $FF | 12 | Form feed |
| $CR | 13 | Carriage return |
| $ESC | 27 | Escape character |
| $SPACE | 32 | Simple space |
| $DEL | 127 | Delete |
| $BOM | 16#FFFE | Byte Order Marker |
| $END | 16#FFFF | End of stream |
String literals are delimited by single-quote ($') characters. Contained quotes are doubled, as in ['Isn''t this fun?']. Strings may span lines, but this is not recommended. The reasons are that it doesn't format well, and the embedded linefeeds depend on the platform where the code was compiled, when they should vary according to the runtime platform. Use instead string chains, character escapes, and line escapes, described next.
This example demonstrates how we represent strings that are large, contain linefeeds, and/or contain arbitrary characters:
CONSOLE writeLine: 'Usage: copy [-n] [-e] {source-dir} {target-dir}$LINE' '$TAB`Copy {source-dir} directory and all contents to {target-dir}.$LINE' '$TAB`Create directory {target-dir} if it does not already exist.$LINE`' 'Options:$LINE`' '$TAB`-n$TAB`Copy (n)ested subdirectories also, unless they are empty.$LINE' '$09`-e$09`Copy (e)mpty subdirectories also.$LINE$LINE' escaped lineEscaped.
One string can appear immediately after another forming a String Chain. These are simply concatenated by the compiler into a single literal. Long strings can therefore be broken up to appear on multiple lines.
SABLE has no syntax that supports arbitrary characters within a string literal. Instead, class STRING defines these two instance methods:
escaped ^{STRING}. {~ primitive: #escaped} "The receiver string literal with SABLE-style escape sequences replaced." lineEscaped ^{STRING}. {~ primitive: #lineEscaped; nonfunctional} "The receiver string with '$LINE`' replaced by the platform newline character(s)."
>#escaped requires a string literal as receiver. The compiler converts character literals within the string into their values. The only Character-Form char recognized is '$$'; there is no reason to use any other. An Integer and Word-Form character may end with a grave accent character to separate it from the following text. Additionally, '$LINE' (plus the optional grave accent) is converted to '$LINE`', preparing the string to receive >#lineEscaped. Unrecognized sequences result in a compiler error.
>#lineEscaped requires a literal string, including an escaped string. During type initialization, it replaces '$LINE`' with the platform newline sequence. (The BeerSong example placed this message in a constant block; it wasn't needed because this message already includes such behavior, but I wanted to demonstrate constant block syntax.)
The Symbol is our most versatile literal, having several different uses.
|bigint| {INT32} := #MaxValue. |adderBldr| := myTypeBuilder defineMethodNamed: 'DoAdd' attributes: #Public | #Static "<----" returnType: INT32 argumentTypes: #(INT32, INT32). |colors| := ##{CONSOLE_COLOR}(#Red, #Green, #Blue).
For many expressions, the expected type is known. This is the case when assigning to a typed declaration or a variable, passing message arguments, populating the elements of an array structure, and elsewhere. The compiler looks in the class of the expected type for a literal, constant field, or static property with the symbol's name and a compatible type. This is most commonly used to retrieve enumeration values, but as the first example above shows, that usage is not exclusive.
One class can be defined as an extension to another, in which case its literals, constants, and static properties are effectively added into the extended class. E.g. in #System.Drawing, class PENS is defined as an extension of PEN; thus, [|blk| {PEN} := #Black] retrieves the PENS>~>#Black property value.
SymbolExample.exe
{~ console
entryClass: #Examples.SYMBOLS method: #main;
reference: 'mscorlib.dll';
use: #System;
use: #System.Collections.Generic.DICTIONARY`2 as: #MAPPING`2;
import: 'mymod.netmodule';
use: #'' as: #MyMod withNested: True}
Various pragma and primitive messages accept the SABLE-added type {System.SYMBOL} to represent a namespace or qualified global class name; therefore a symbol can use a dotted word. To represent a generic class, it can end with a grave accent and the number of parameters. To represent the root namespace, #'' is a symbol with no characters.
+: other {MyType} ^{MyType}. {~ primitive: #+:} escaped ^{STRING}. {~ primitive: #escaped} compareTo_object: other {OBJECT?} ^{INT32}. {~ override: COMPARABLE >~> #compareTo:}
Various pragma and primitive messages accept the type {System.SYMBOL} to represent a method name; therefore a symbol can be any of the four message name patterns, preceded by a hash character.
Class SYMBOL contains a few methods of its own. One such usage is to have compile-time settable defined values. An assembly can initialize "defines" using a pragma.
NetworkManager.dll {~ library define: #SupportXINS; define: #MaxNodeCount as: '512'; reference: 'mscorlib.dll'; use: #System}
The development environment will provide some way to set defined values as well. In the simplest case, a command line compiler can accept switch values to set them:
sable -DSupportCorba -DSupportXINS=0 -DMaxNodeCount=2048 NetworkManager.dll.sab
Finally, the code will test and retrieve the defined values:
|allNodes| := {ARRAY[NETWORK_NODE]} new: #MaxNodeCount definedInteger. #SupportCorba isDefined then: ["..."]. #SupportXINS definedBoolean ifTrue: ["..."].