picma

Contents

Command line

picma [options] [file...]

Options include:

-h
Print help text and exit
-d types
Generate debug output of various types:
lAssembler listing
pPasses
tCode tree
aCode allocation
fCode freespace map
sStruct freespace map
bStack backtrace
-f type
Select output format, type is either 'hex', 'ptf', or 'hp'.
-m type
Write a HTML memory map to stdout, of the given type. Type should be 'C', 'R', 'D', or 'P'.
-o file
Output code to the given file instead of stdout.
-r
On UNIX, swap '.' and '/' in included names. Ignored on RISC OS.
-t
Enable throwback (ignored on UNIX)
-u
Same as -r, except this is active on RISC OS and ignored on UNIX.
-v
Turns on an increasing number of -d options.
-I path
Defines or replaces the default search path when looking for included files. On RISC OS, the default is 'picma:', on UNIX it is the contents of 'PICMA_PATH'.

General syntax

Whitespace in the input is generally optional, and can be any mix of CR, LF, SPC, and TAB. Anything between /* and */, and anything from // until the end of the line is ignored. /*...*/ comments can also be nested.

Entities

program
A complete program consists of 1 or more files. The filenames can come from the command line, where the files will be concatenated and treated as one big file. Other files can be included with #include. The syntax of #include depends on the build environment:

RISC OS

Directories are separated with '.', and the path is a ','-separated list of prefixes.
#include "[path.]wildmask"
This searches in the directory holding the current file, or in a subdirectory if the optional path is given. All the files that match the given wildmask are included as they are found.
#include <[path.]wildmask>
This searches the include path (see -I above), adding the optional path given, and finds the first directory that contains one or more files matching the wildmask. All the matching files in this directory are then included.

UNIX

Directories are separated with '/', and the path is a ':'-separated list of directories.
#include "[path/]wildmask"
This searches in the directory holding the current file, or in a subdirectory if the optional path is given. All the files that match the given wildmask are included as they are found.
#include <[path/]wildmask>
This searches the include path (see -I above), adding the optional path given, and finds the first directory that contains one or more files matching the wildmask. All the matching files in this directory are then included.

Windows

This is apparently a relatively new OS used by an unknown number of people. No build is planned for that OS until it matures.

In all cases the files are inserted in place of the include command.
Another relevant command is #include_only_once. If this is encountered in a file that has already been included, the parsing stops as if the file ended at that point.

All this produces one long sequence of statements.
These statements are implicitly inside an unseen block, which is in turn attached to a global code statement. This is important because that global code statement has the base = P0 option set, which means that any instructions present in the top level will be stored from P0.

block
A block is 0 or more statements enclosed in { }. Every block has its own local name scope, and variables are looked up by working outwards from the current block. Each block also has local begin and end labels, pointing to the first word and the one after the last, respectively. These are constants, and cannot be changed.
statement
There are several types of statements:
expr ;
This simply evaluates the given expr. Since the result is thrown away, this is most useful for expressions that end up changing variables.

There is a special case of this statement:
varref ( [ expr ] [ , [ expr ] ... ] ] ) ;
This would normally call the named function and throw the result away. Instead it invokes the named macro with the given argument list. If there are more args than the macro takes, the rest are ignored. If there are less, the missing macro parametres will exist, but be unassigned.

#prefix name1 = name2 ;
This defines a prefix in the current block called name1 with the value name2. See below for more information on prefixes.
block
This creates a block containing the enclosed statements.
instr [ expr [ , expr ... ] ] ;
This stores the given instruction at the current word.

A valid instruction is one from the following list, or their uppercase versions:
addlw, addwf, andlw, andwf, bcf, bsf, btfsc, btfss, call, clrf, clrw, clrwdt, comf, decf, decfsz, goto, incf, incfsz, iorlw, iorwf, movf, movlw, movwf, nop, retfie, retlw, return, rlf, rrf, sleep, sublw, subwf, swapf, xorlw, xorwf.
Or any of the aliases:
addwl, andwl, iorwl, movwl, retwl, subwl, xorwl.

name:
This defines a label (an area in the program memory) called name.
area expr , [ newvar [ := expr ] ] [ , [ newvar [ := expr ] ] ... ] ;
This defines a number of vars, based on the area given. If any of the the newvars are given values, the relevant areas are initialised too.
The first var defined will have the area value given. The next will have the same width, but placed just after the previous var, and so on. You can also omit both the newvar and the value, which will just skip the area that you've come to.
alloc varref , { members } ;
This creates a local copy of the named area, if it doesn't exist already, and allocates a number of subset areas within it. The members have the same syntax and meaning as structure members. In this case they are created as constants, in the scope of the alloc statement.
When the names go out of scope, so does the local copy of varref, and the areas allocated are in effect freed for reuse by subsequent alloc statements. Once you have alloc'ed from an area, it (locally) shrinks by the amount used, and any other references to that area within that scope will use the new, smaller, area. If this is done in the top level (or varref starts with '?'), it will already exist, so there will be no local copy, it will never go out of scope, and you never get the allocated space back.
chip string ;
This statement predefines all the chip\* variables to standard values for the given chip. There can be more than one, but they must all use the same chip name.
The following chip names are recognised:
12F629, 12F675, 16F84, 16F627, 16F628, 16F872, 16F873, 16F874, 16F876, 16F877.
code varref options block
This defines a code block with the options given. It creates a constant word area as varref which points to the start of the code block, and has the same width.
Note that creating a code block (rather than just a block), also declares that the code is relocatable, and it may be placed anywhere (unless there is a link statement for it).

The possible code options are listed below. The options are given as name/value pairs enclosed in ( ). The list can be empty, and the brackets may then be omitted.

base = expr
Force the start of the code block to be at the address specified (which must be a program area).
nowrap [ = expr ]
Prevent the code block from crossing a block boundary, with the block size given (or 256 if none is specified). If this option is not present, the chip still has a default block size that will apply to the code block.
optional
This declares the code block optional. If it ends up unused, it will not be included in the output. There are certain rules that must be followed to use this feature safely:
  • A block is considered "in use" if its name-label is used in a call, goto, link, or uses statement. This includes inside the block itself, so you should use "begin" instead, if you want to refer to the start.
  • Any other jump to the block, e.g. using a calculated address, will not be spotted. Hence, if you have optional code blocks that might be called in this way, you should add appropriate uses statements to make sure they are not removed.
  • When a block is removed, it generates a warning. Use option\warning\cr to turn it off.
  • Don't make the reset code optional (or any other code on a hardware vector). Chances are it is not called (by you), and hence will be removed. Bad thing.
  • Note that the statements inside a removed block will still be executed, it just won't generate any code or labels. If these have global effects, they will still happen.

You can have several code blocks of the same name in the same scope. There will only be one label, and it points to the last block defined. This requires that all the other blocks are optional, otherwise it results in an error about reassigning that label. The last block (which is the only one not removed) may also be optional, but that is not required.

For each code block, a local variable called "!name" is created, containing the fully expanded name of the code block (i.e. without prefixes). This is an internal variable, and it can only be accessed via the @ operator, as in @"!name".

def newvar [ = expr ] [ , newvar [ = expr ] ... ] ;
This defines one or more variables, with optional initial values. When creating constants, giving intitial values is mandatory.
do block while expr ;
This will execute the block, and keep doing that until the expr is 0.
enum [ varref [ = expr ] ] [ , [ varref [ = expr ] ] ... ] ;
This is another way of creating areas, where each of the varrefs can define its own area. The first area will have the value 0 if none is specified. The next will increase like in the area object, as long as no specific value is given. If both name and value is omitted, the value is increased like normal, but not assigned to anything. All variables created will be constants.
error expr [ , expr ... ] ;
This makes the assembler output the given exprs as an error, and stop.
every expr , ( [ expr ] [ , [ expr ] ... ] ] ) ;
This expands all macros that match the given wildcarded expr (which must produce a string), passing the arguments to each of them. The matches are expanded in alphabetical order. If no macros match the expr, nothing happens (it is not an error).
for ( expr ; expr ; expr ) block
This will evaluate the first expr, and while the second expr is non-0, it will execute the given block and then evaluate the third expr after each execution.
foreach ( varref = expr ) block
This will expand the given block once for each variable that matches the wildcarded string expression. The matches are processed in alphabetical order. For each expansion, varref is set to the name of the matched variable. The value can be extracted using @varref.
Make sure you only match variables that are defined at the point of the foreach statement. If more matching names are created later, the next pass may create more code, and you will get an error about that.
func name ( [ varref [ , varref ] ... ] ) block
This defines a function of the given name, taking the listed parameters. The code in block is executed when the function is evaluated. There are certain statements you can't use in the block, namely all the ones that can generate data to go in the output. This includes all PIC instructions, label definitions, macro calls, area, init, and alloc.
There is an exception to this rule: Inside functions return expr ; is allowed, but instead of generating a return instruction, it makes the function return with the given expr as result.
if expr block
if expr block else block
This evaluates the expr as a scalar, and if non-0, executes the first block given. Otherwise the second block is executed if it is present.
init expr := expr [ , expr := expr ... ] ;
This initialises the given areas.
link expr ;
expr is evaluated, and the result must be a string. This will ensure that the named codeblock is assembled at the current position. There are rules:
  • The name must match an existing code block label (i.e. no forward references).
  • There can be 0 or 1 link statements for each code block.
  • Linked blocks are always optional, and will be removed if the enclosing block is removed.
  • The base and nowrap options can also be used for the linked block. The enclosing block will not be moved because of these, they will just generate errors if they end up wrong.
macro name ( [ varref [ , varref ] ... ] ) block
This defines a macro of the given name, taking the listed parameters. The code in block is expanded when the macro is invoked. The macro name can be used according to the same rules that apply to variables, except you can use forward references to macros.
print expr [ , expr ... ] ;
This will print (to stdout) all the exprs given, evaluating each one. It finishes by printing a newline.
return expr ;
This is valid inside functions only. The function returns with the given value.
switch expr { cases }
The expr is evaluated, and compared with the cases until a match is found. That block is then executed.

There are two types of cases:

case expr block
The block is executed if the expr matches the one in the enclosing switch.
default block
The block is executed if none of the other cases matched.
There can be 0 or more case blocks, and 0 or 1 default block.
uses expr ;
This statement takes a string, which must be a code block name. The named code block is then considered "in use", and won't be removed if it is optional.
warning expr [ , expr ... ] ;
This makes the assembler output the given exprs as a warning.
while expr block
This will evaluate the expr, and while it is non-0, execute the block.
newvar
This is a variable that will be created. It is an varref, optionally prefixed by const (which creates a constant variable).
varref
This is an entity that can be written to, i.e. a variable reference. It can be either a name or @( expr ). The latter will evaluate the expression, and use the result as the name (which must become a string). Writing @(" name ") is the same as writing name.
name
An identifier used for variables, macro names, etc. It is a string of 1 char from the set [A-Za-z_\], followed by 0 or more from [0-9A-Za-z_\]. names are case sensitive.
There are certain reserved words and constructs that cannot be used for a name. Here is a list of reserved names: The easiest way to avoid reserved names is to use mixed case, e.g. 'Output'. If short enumerated names are desired, use lowercase like d0, d1, d2, etc. to avoid making an area instead.
Global names
By prepending '?' to a name, you can refer to the global version of that variable. This works everywhere a name is acceptable, but it doesn't make sense in all cases. You can for example create a global var in a loop, but if it runs more than once, you get an error trying to create it the second time. The solution is of course to create it outside the loop, and only assign it inside.
Named prefixes
A block can have any number of prefixes defined, using the #prefix statement. You can then use names like foo#bar, where 'foo' is the name of a prefix. The value of this prefix will then be used instead of 'foo#' when looking up the variable. This works for variables, macro names, function names, and code block names. The prefix name must be a plain name, and can't contain '?' or '#'. The value can contain a prefix itself, but not '?'.

When searching for the named prefix, the source structure is used (rather than the execution structure used for everything else). This means that the parent of a macro definition is the block where it is defined, and not the block where it is invoked.

Example:

#prefix foo = baz;
def foo#bar = 5; // this creates a variable called "bazbar"

Note: When using '?' in combination with prefixes, the prefix has higher precedence. This means that ?foo#bar will search for the 'foo' prefix in the current block (and then the parent, etc.), but the resulting name is then looked up globally.

expr
An expression, containing 1 or more names, literals, or exprs in combination with operators. Valid expressions, highest precedence first:
Precedence expr Effect/Result
17 ( expr ) expr
16 expr -> expr Structure operator, see structures
15 expr [ expr ] Indexed subset of area, indexed character from string, or indexed sub-structure. See also structures.
15 name++ Return name, then increase it
15 name-- Return name, then decrease it
15 varref ( [ expr ] [ , [ expr ] ... ] ] ) Return value after calling the named function with the given arguments
15 bytes( expr ) Size of an area, a string, or a structure, rounded up to a whole number of bytes. For a scalar it is (scalar + 7) / 8.
15 bits( expr ) Exact size of an area, a string, or a structure, in bits. Returns a scalar unchanged.
15 asc( expr ) ASCII value of the first character of the string expr. Returns 0 if given a null string.
15 chr( expr ) A string containing the character that has an ASCII value of expr. Returns a null string if expr is 0.
15 now() The current date and time as a string.
15 def( expr ) True if @(expr) exists
15 type( expr ) Returns the type of @(expr) as a string:
"scalar"for scalars
"string"for strings
"area C"for configuration areas
"area R"for register areas
"area D"for data areas
"area P"for program areas
"struct"for structures
""for existing, but unassigned vars
15 left( expr1, expr2 ) The first expr2 chars of the string expr1
15 right( expr1, expr2 ) The last expr2 chars of the string expr1
15 mid( expr1, expr2 ) The last part of the string expr1, starting at position expr2 (0 is the first position)
15 mid( expr1, expr2, expr3 ) expr3 chars of the string expr1, starting at position expr2
15 str( expr ) expr as a string (same as print would output)
15 firstleft( expr1, expr2 ) Takes two string expressions. Returns the left part of expr1, up to but excluding the first occurence of expr2 (searching from left).
15 firstright( expr1, expr2 ) Takes two string expressions. Returns the right part of expr1, down to but excluding the first occurence of expr2 (searching from right).
15 lastleft( expr1, expr2 ) Takes two string expressions. Returns the left part of expr1, up to but excluding the last occurence of expr2 (searching from left).
15 lastright( expr1, expr2 ) Takes two string expressions. Returns the right part of expr1, down to but excluding the last occurence of expr2 (searching from right).
14 expr . expr Convert area to bit area, with given bit offset
14 expr ` expr Change width of area
13 ++name Increase name, then return it
13 --name Decrease name, then return it
13 ../expr expr evaluated in the parent scope
13 @expr Reference to a variable, whose name is given in the string expression.
13 $expr Scalar value. The base offset of areas and structures, and the numerical value of strings.
13 &expr Bit offset of area or structure
13 *expr Mask of area or structure
13 ~expr Binary NOT
13 !expr Logical NOT
13 -expr Negation
12 expr * expr Multiplication
12 expr / expr Division
12 expr % expr Modulus
11 expr + expr Addition, and string concatenation
11 expr - expr Subtraction
10 expr >> expr Binary shift right
10 expr << expr Binary shift left
9 expr >= expr Logical greater than or equal
9 expr <= expr Logical less than or equal
9 expr > expr Logical greater than
9 expr < expr Logical less than
8 expr != expr Logical difference
8 expr == expr Logical equality
7 expr & expr Binary AND
6 expr ^ expr Binary EOR
5 expr | expr Binary OR
4 expr && expr Logical AND
3 expr ^^ expr Logical EOR
2 expr || expr Logical OR
1 name = expr Assigns value of expr to name, and returns that value
1 name += expr Adds (or concatenates) expr with name, and stores and returns the result
1 name -= expr Subtracts expr from name, and stores and returns the result
1 name *= expr Multiplies name by expr, and stores and returns the result
1 name /= expr Divides name by expr, and stores and returns the result
1 name %= expr Calculates name modulus expr, and stores and returns the result
1 name |= expr Ors expr with name, and stores and returns the result
1 name &= expr Ands expr with name, and stores and returns the result
1 name ^= expr Eors expr with name, and stores and returns the result
1 name <<= expr Shifts name left by expr bits, and stores and returns the result
1 name >>= expr Shifts name right by expr bits, and stores and returns the result

Literal data types

A literal can have one of 4 different types:

Scalars

A scalar is a one-dimensional integer. Literal scalars can be written in various ways:

Areas

An area is a 2-dimensional extent. One dimension is the type of memory, which can be configuration memory (C), registers (R), EEPROM data (D), or program memory (P). The second dimension is the offset within this memory. Finally, it also has a size. Literal areas are written like this:
areatype scalar
where areatype is either 'C', 'R', 'D', or 'P', defining the type of memory, and the scalar is the offset in words. The size of a literal area is always 1 word.

Example: D10: 1 word at offset 10 in the EEPROM data memory.

The . and ` operators can be used to change the properties of an area.
area . expr changes the area from word to bit, giving it a bit-offset equal to the scalar value of expr. The size of the area changes to 1 bit.
area ` expr changes the size of the area to the scalar value of expr. The size is measured in either bits or words depending on the type of area given.

Examples:
D10.0: 1 bit at (word-)offset 10 in the EEPROM data memory.
D10.0`4: 4 bits at (word-)offset 10 in the EEPROM data memory.
D10`4: 4 bytes at (word-)offset 10 in the EEPROM data memory.

Strings

A string is a sequence of max. 511 chars enclosed in double quotes. Character constants can also be used within strings (with the same behaviour as in scalars). These are C strings, so they can not contain \0 (NUL).

Example: "foo\n": The word 'foo' followed by an LF.

Structures

A structure is a collection of data areas called members. Structures can be in two states, prototypes and instances. The prototypes have everything except a base address, and can be used in calculations where only the size is used. When a base address is attached (using ->), the value becomes an instance of the prototype, and can be used where actual addresses are needed. All structures are treated as normal values, and can be stored in variables, used within expressions, etc.

A literal structure prototype is written like this:
struct { member [, member ]... }

Each member has a unique name within a structure, and can take 3 forms:

Note that the syntax allows name to start with '?', and/or be a @(...) reference. This is because the same syntax is used in alloc. If you use anything except a plain name in a structure, it will be allowed (the space will be allocated), but you will not be able to reference the member with ->.

Example:

def x = struct {
  foo = bit`6,		// 6 bit wide bitfield
  bar = byte,		// one single byte
  baz = bit.14`2,	// two bits at offset 14 (byte 1, bit 6 and 7)
  quux = struct {	// inline sub-structure
    flop = bit.0	// one bit fixed at offset 0
  }.20`3		// makes 3 copies of the sub-structure (total of 3
			// bits) and places it at offset 20
};

The structure operator -> has 3 different modes of operation, depending on the type of the first argument given:

The [ operator has special behaviour for structures. Normally it uses the "natural size" of the base area to index a given item. When used on a structure, it uses the size of the structure as the "natural size". This means that in the example above, the 3 copies of the sub-structure can be referenced as x->quux[0], x->quux[1], and x->quux[2].

Note that there is no difference between x->quux[0] and x->quux. This is because there are no arrays in this language (it just looks like it). So there are not really 3 structures there, just one that takes 3 times the space.

Structure alignment

There are special rules for alignment of structures and their members. These rules only apply if there are byte members involved, which must be byte-aligned to be useful. If you want byte-size fields without the alignment, use bit members of size 8.
Any byte members in the structure will get byte-aligned offsets. This alignment is contagious, and the structure itself will also have to be byte-aligned to ensure that the members are. If this structure is then used as part of another structure, it infects that as well, and it will also require byte alignment. In certain cases the byte-alignment also spreads to the size of the structure, for example if there are more than one in a sequence. Padding bits may be inserted between them so each copy is byte-aligned. These bits are 'lost', i.e. it is currently impossible for the member allocation code to place small bitfield members within these padding spaces. This may be added in the future, so don't assume that the padding bits are free for other uses.

Preset variables

There are a few predefined global constants: w=0, f=1, false=0, true=1.

Options

Also, there are many preset options that can be changed. All of these have the form option\type[\name].
Type "warning"
All of these can be turned on or off. The value 0 switches the warning off. Any other scalar switches the warning on. They all default to 1 unless changed.
variableWarning
option\warning\opOverwriting program memory at offset ...
option\warning\ssUsing string value as scalar
option\warning\asUsing area base as scalar
option\warning\bwUsing bit area as word area
option\warning\pwUsing partial area as whole word
option\warning\mbUsing multi-bit area as one bit
option\warning\rbReplacing bit offset
option\warning\wbWrapping bit offset
option\warning\waWrapping area offset
option\warning\wdWrapping destination specifier
option\warning\wnWrapping bit number
option\warning\geJump to expression, ...
option\warning\crCode '...' not in use, removed
option\warning\movfDefault destination is f, ...
option\warning\bitwrapBitfield x`y crosses byte boundary, ...
option\warning\rebaseRebasing struct
option\warning\fwHaving a local 'f/w' is...
option\warning\oldallocThis code uses the old alloc syntax
Example: option\warning\bw = false;
Type "print"
There is only one of these. It turns off the print statement if set to false. It actually turns it off, i.e. the arguments will not be evaluated, so any side effects from these will stop happening.
Example: option\print = false;
Type "list"
These control the assembler listing (enabled by -v). They are all on by default.
variableEffect
option\listIf set to false, it disables the assembler listing.
option\list\fileEnables printing of the filename.
option\list\lineEnables printing of the line number.
Example: option\list = false;
Type "ptf"
These allow you to set the headers when the output format is ptf. Normally they will not exist, so the headers are omitted. When created (using def) the relevant header lines will be included. They must be created in the global scope to have any effect.
variableEffect
option\ptf\nameCreates a header line called name.
Example: def ?option\ptf\Date = now();

Note that there is a big difference between
option\foo = ... and
def option\foo = ...
In the former case, you are changing the existing value, which would usually be the global setting. In the latter case, you create a new local option, and the selected value only applies within the current block. This is exactly like all other variables.

Chip characteristics

Another set of variables define the limits of the target chip. The complete set is:
variableContents
chip\name This is just a string giving the name of the chip. It is mainly used to ensure that you don't accidentally include libraries written for different incompatible chips.
chip\size\config This is the size of the configuration area, in words.
chip\size\data This is the size of the EEPROM data area, in bytes.
chip\size\program This is the size of the program area, in words.
All of these are set to default values if you use the chip command. This will also create them as constants. Otherwise they behave exactly like normal variables.
If you need to target a chip that is not in the list, you can set the limits yourself. For example:
def chip\name = "24F951";
def chip\size\config = 16;
def chip\size\data = 8192;
def chip\size\program = 4194304;
  
It is not strictly necessary to set a name, it is only used for headings, etc. But it does make it a lot easier when you come back to the code later.

The various variables need to be defined before they are needed. The compiler will only check this immediately before it needs the value, so if you get strange errors about chip\size\*, it is probably because it hasn't been defined in time.

Variable states

A variable can be in one of 3 major states. In macro and function code, it can be necessary to determine the state of an argument. def() and type() is used for this:
Statedef()type()
Does not existfalseGives an error
Exists, unassignedtrue""
Exists, assignedtrueString giving type
When calling a macro or function with some arguments missing, the affected parameter variables exist, but are unassigned.

Arithmetic rules

Some operators work on more than scalars, and behave in various weird ways when applied to areas. This is described below, see also structures for other strange effects.

$area
Returns the scalar value of the area or structure, which is the word offset.
*area
The mask of the area, which is a scalar containing a bit mask with 1's covering the area. This only works for bit areas.
&area
Bit offset of the area. This only works for bit areas.
area+scalar
scalar+area
Returns another area, covering the same width as the original, but offset from it by width*scalar bits. For example, For word areas, this always works, and offsets by width*scalar words instead.
area-area
Works for areas of the same type, and the same kind (word or bit). In each case, a new area is returned, covering the space between the base offsets of the two areas. The widths are ignored, and the result is always the absolute value. For example: If the result is a bit area covering more than one word, an error is returned.
area < area
area > area
area <= area
area >= area
area != area
area == area
Allowed for areas of the same type. The result is a comparison of the base offsets if they are different, otherwise the widths. Word and bit areas can be compared, and will return results depending on the actual width. E.g. a word area of width 1 will be equal to a bit area covering the whole word (8 or 14 bits depending on type). This is necessary to return consistent results, so (a >= b) returns true if (a == b) and (a < b) is always the opposite of (a >= b) etc. etc.
area[scalar]
This is also an operator, it returns an indexed subset of the area, measured in the "natural size" (1 word or 1 bit).

Initialising

Initialising an area (with area or init) will register the values as the initial content of that area. This can be done with the config and data areas, and will cause the data to be included in the output file at the appropriate addresses.

You can initialise an area to a string. With a word area, the characters are simply stored at increasing word offsets.
If it is a bit area, every character will be stored at increasing area locations (increasing as if adding 1 to the area after every char).
Example: area D3.0`4, foo = "12345";
This will store 0x21 at offset 3, 0x43 at offset 4, and 0x5 in the low nibble of offset 5. This is because '1' (0x31) truncated to the width of the area (4 bits) is 0x01, so D3.0`4 becomes 0x01. The next area is D3.0`4 + 1 = D3.4`4, which becomes 0x02, giving 0x21 in the whole byte. And so on.

Tips & Tricks

Here are a few things that are nice to know when you're writing in this language.

Tip #1: This is not C

The C-like syntax can easily make you slip into "C-mode" mentally. This should be avoided, since it is really quite different. For example, a perfectly normal thing like this:

n = 0;
while (n < 5) {
  print n++;
}
...will completely fail to do what you'd expect and instead become an infinite loop.

The reason for this is important to know, since it affects a lot of constructs in this language.

Since this is an assembler, it makes several passes over the code to deal with forward references. Some statements are run on every pass (e.g. while), but others, like print and PIC instructions, are only executed on the last pass. This is necessary so they can use references to variables that don't yet exist, but will be created in a later pass.

But it also means that side effects should be avoided in these commands, since they will only happen at the last pass. That is why the above becomes an infinite loop - the n++ is not evaluated in the first pass, so the loop never ends.

The statements that execute on every pass are:
alloc, area (except init data), code, def, enum, expr;, every, for, foreach, if, init (except init data), link, switch, while, & return (from function).
All of these are safe to use side effects in. For the same reason, they can't use variables that are forward-referenced.
The rest are usually only executed once, on the last pass. Of course, it's slightly more complicated than this - in certain circumstances they are executed on every pass too. If the interpreter is currently evaluating a function call (in any code pass), commands like print, error, & warning will be executed, macro invocations and every will be ignored, while PIC instructions will generate errors.
This means that the above code could be changed to:

n = 0;
while (n < 5) {
  -echo(n++);
}

func echo(x)
{
  print x;
  return 0;
}
...and it stops being an infinite loop.
There are several things to note here:

The above is still useless, so here's the "correct" way of writing it:

n = 0;
while (n < 5) {
  print n;
  n++;
}
Now n++ has become an expr; statement, so it is evaluated every time, and the loop terminates properly. print is only executed on the last pass, so you get only one list of numbers out.