Fastspin was designed to accept the language Spin as documented in the Parallax Propeller Manual. It should be able to compile any Spin program, as long as there is space. The restriction is an important one; other Spin compilers produce Spin bytecode, a compact form that is interpreted by a program in the Propeller's ROM. Fastspin produces LMM code, which is basically a slightly modified Propeller machine code (slightly modified to run in HUB memory instead of COG). This is much larger than Spin bytecode, but also much, much faster.
fastspin has a pre-processor that understands basic directives like
#ifdef / #ifndef / #else / #endif.
#define FOO hello
Defines a new macro
FOO with the value
hello. Whenever the symbol
FOO appears in the text, the preprocessor will substitute
Note that unlike the C preprocessor, this one cannot accept arguments. Only simple defines are permitted.
If no value is given, e.g.
then the symbol is defined as the string
Introduces a conditional compilation section, which is only compiled if
the symbol after the
#ifdef is in fact defined. For example:
#ifdef __P2__ '' propeller 2 code goes here #else '' propeller 1 code goes here #endif
Introduces a conditional compilation section, which is only compiled if
the symbol after the
#ifndef is not defined. For example:
#ifndef __P2__ #error this code only works on Propeller 2 #endif
Switches the meaning of conditional compilation.
A combination of
A combination of
Prints an error message. Mainly used in conditional compilation to report an unhandled condition. Everything after the
#error directive is printed.
Includes a file.
Prints a warning message.
Removes the definition of a symbol, e.g. to undefine
There are several predefined symbols:
||always defined to 1 (for P1) or 2 (for P2)|
||if --asm is given (PASM output) (always defined by fastspin)|
||if C++ or C is being output (never in fastspin)|
||if C++ is being output (never in fastspin)|
||if compiling for Propeller 2|
Extensions to Spin 1
Fastspin has a number of extensions to the Spin language.
@@@ operator returns the absolute hub address of
a variable. This is the same as
@ in Spin code, but in PASM code
returns only the address relative to the start of the
IF...THEN...ELSE expressions; you can use IF/THEN/ELSE in an expression, like:
r := if a then b else c
which is the same as
if a then r := b else r := c
This may also be written in C / Verilog style as:
r := (a) ? b : c
In the latter form the parentheses around
a are mandatory to avoid confusion with the random number operator
fastspin accepts inline assembly in
PRI sections. Inline assembly starts with
asm and ends with
endasm. The inline assembly is somewhat limited; the only operands permitted are immediate values, hardware registers like
OUTA, local variables (including parameters and result values) of the containing function, or labels of that function. (Spin doesn't support goto and labels, but you can define labels in
asm blocks and jump to them from other
asm blocks that are in the same function. Some other languages supported by fastspin do have labels.) Member variables (declared in the
VAR block) may not be used directly in inline assembly.
Branching inside the function should work, but trying to return from it or to otherwise jump outside the function will almost certainly cause you to come to grief, even if the compiler allows it. Calling subroutines is also not permitted.
All non-branch instructions should work properly in inline assembly, as long as the operands satisfy the constraints above. Conditional execution is allowed, and flag bits may be set and tested.
If you need temporary variables inside some inline assembly, declare them as locals in the enclosing function.
PUB waitcnt2(newcnt, incr) asm waitcnt newcnt, incr endasm return newcnt
waits until CNT reaches "newcnt", and returns "newcnt + incr".
Note that unlike most Spin blocks, the
asm block has to end with
endasm. This is because indentation is not significant inside the assembly code. For example, labels typically start at the leftmost margin.
Abstract objects and object pointers
The proposed Spin2 syntax for abstract object definitions and object pointers is accepted. A declaration like:
OBJ fds = "FullDuplexSerial"
fds as having the methods of a FullDuplexSerial object, but without any actual variable or storage being instantiated. Symbols declared this way may be used to cast parameters to an object type, for example:
PUB print(f, c) fds[f].dec(c) PUB doprint22 print(@aFullDuplexSerialObj, 22)
PUB FILE and PRI FILE
pri function declaration may include a
file directive which gives the file which contains the actual definition of the function. This looks like:
pub file "utils.spin" myfunc(x, y)
This declares a function
myfunc with two parameters, which will be loaded from the file "utils.spin". The function will be a public function of the object. This provides an easy way to import the same function (e.g. a decimal conversion routine) into many different objects.
pub file and
pri file differ from the
obj directive in that they do not create a new object; the functions defined in the new file are part of the current object.
Note that there is no need for a body to the function (it is an error to give one). The number of parameters and return values, however, should be specified; they must match the number given in the final definition contained in the file.
The function body need not be in Spin. For example, to use the C
atoi function in a Spin object on a Propeller1, you could do:
obj ser: "spin/FullDuplexSerial.spin" pub file "libc/stdlib/atoi.c" atoi(str) pub test ser.start(31, 30, 0, 115_200) x := string("1234") ser.dec(atoi(x))
(For Propeller2 you would have to modify this to use "spin/SmartSerial" and to change the output pins appropriately.)
Beware that functions declared with
file are treated the same as other functions; in particular, note that the first function in the top level object will be used as the starting point for the program, even if that function was declared with
pub file or
pri file. So unlike in C, the declaration of external functions should be placed at the end of the file rather than the beginning (unless for some reason you want the main program to come from another file).
Multiple return values and assignments
fastspin allows multiple return values and assignments. For example, to swap two variables
b you can write:
a,b := b,a
It's also acceptable (and perhaps easier to read) if there are parentheses around some or all of the multiple values:
(a,b) := (b,a)
A function can also return multiple values. For instance, a function to calculate both a quotient and remainder could be written as:
PUB divrem(x,y) return x/y, x//y
PUB divrem(x,y) : q, r q := x/y r := x//y
This could later be used like:
(a,digit) := divrem(a, 10)
It is also allowed to pass the multiple values returned from one function to another. So for example:
' function to double a 64 bit number PUB dbl64(ahi, alo): bhi, blo bhi := ahi blo := alo asm add blo, blo wc addx bhi, bhi endasm ' function to quadruple a 64 bit number PUB quad64(ahi, alo) return dbl64(dbl64(ahi, alo))
fastspin allows method parameters to be small arrays; in this case, the caller must supply one argument for each element of the array. For example:
pub selector(n, a) return a[n] pub tryit return selector(n, 1, 2, 3, 4)
This particular example could be achieved via
lookup, but there are other cases where it might be convenient to bundle parameters together in an array.
This feature is still incomplete, and may not work properly for C/C++ output.
Default function parameters
fastspin permits function parameters to be given default values by adding
= X after the parameter declaration, where
X is a constant expression. For instance:
VAR long a PUB inc(n=1) a += n PUB main inc(2) ' adds 2 to a inc(1) ' adds 1 to a inc ' same as inc(1)
The default values must, for now, be constant. Perhaps in the future this restriction will be relaxed, but there are some slightly tricky issues involving variable scope that must be resolved first.
Default string parameters
If a default function parameter is declared as a string, and a string literal is passed to it, that string literal is transformed into a string constant. Normally Spin uses just the first character of a string literal when one is seen in an expression (outside of STRING). Basically fastspin inserts a
string operator around the literal in this case. So for example in:
PUB write(msg = string("")) '' do some stuff ... write(string("hello, world")) write("hello, world")
the two calls to
write will do the same thing. In regular Spin, and in fastspin in the case where the default value is not present on a parameter, the second call will actually be interpreted as:
write($68, $65, ..., 0) ' $68 = ASCII value of "h"
which is probably not what was intended.
Typed parameters and return values
The "expression" in a default parameter may also be a type name, for example
float, or one of the pointer types
@long (pointer to long),
@word (pointer to word),
@float. These do not provide a default value, but do provide a hint to the compiler about what type of value is expected. This isn't terribly useful for Spin, but does make it possible for the compiler to check types and/or convert them if necessary for Spin functions called from C or BASIC.
Variables declared as return values may also be given type hints, which will be used to determine the type of the function when it is accessed from another language.
' negate a floating point value PUB negfloat(x = float) : r = float r := x ^ $80000000
fastspin accepts some Spin2 operators:
a \ b uses the value of a, but then sets a to b x <=> y returns -1, 0, or 1 if x < y, x == y, or x > y
fastspin has some new operators for treating values as unsigned
a +/ b is the unsigned quotient of a and b (treating both as unsigned) a +// b is the unsigned remainder of a and b a +< b is an unsigned version of < a +> b is an unsigned version of > a +=< b is an unsigned version of =< a +=> b is an unsigned version of =>
cognew) functions in Fastspin can start functions from other objects than the current. (In "regular" Spin only functions from the same object may be started this way.)
Fastspin supports some new builtin functions. These typically end with an underscore to avoid confusion with existing variable names.
drv_(p, c) forces
p to be an output and sets it to 0 if
c is 0 or 1 if
c is 1. If c is any other value the result is undefined. Supported for both P1 and P2
drvl_(p) drives pin
p low, i.e. it forces
p to be an output and sets it to 0. This is supported for both P1 and P2.
drvh_(p) drives pin
p high, i.e. it forces
p to be an output and sets it to 1. This is supported for both P1 and P2.
p to be an output and inverts it. This is supported for both P1 and P2.
waitx_(n) waits for
n cycles, plus the cycle time required for the instruction. This is 2 cycles on P2, and 8 cycles on P1.
Compatibility with other Spin compilers
There are very few Spin features that are not supported yet.
_STACK are recognized, but do nothing.
There may be other features that do not work; if you find any, please report them so they can be fixed.
The lexer and parser are different from the Parallax ones, so they may well report errors on code the Parallax compiler accepts.
Timing of produced code is different, of course (in general much faster than the native Spin interpreter). This may affect some objects; sometimes developers left out delay loops in time critical code because the Spin interpreter is so slow they weren't necessary. Watch out for this when porting I2C, SPI and similar functions.
Special characters in identifiers
Special characters like
$ may be included in Spin function and variable names
by preceding them with a backquote, e.g. to define a function
pub chr`$(x) return x
In regular Spin opcodes like
TEST are always reserved.
In fastspin, opcodes are only reserved inside
DAT sections, so it is legal to have a function or variable named
fastspin adds some reserved words:
Literal strings like
"hello" are treated as lists of characters in regular Spin, unless enclosed in a
STRING declaraction. So for example:
is parsed as
whereas in fastspin it is treated as an array of characters, so it is parsed like:
which will be the same as
The difference is rarely noticeable, because fastspin does convert string literals to lists in many places.