CMOC for BASIC Programmers

Date: 2025-05-10
Copyright © 2021-2025 Pierre Sarrazin <http://sarrazip.com/>
This manual is available under the Creative Commons Attribution-ShareAlike License.

Introduction

This document teaches a few parts of the C programming language to users of the Tandy Color Computer's BASIC interpreter. Those C constructs can be compiled by the CMOC compiler, which generates 6809 code mainly for the Color Computer's Disk Extended Basic environment.

To learn C properly and completely, it is recommended to read a book like The C Programming Language, written by the language's creators, Brian Kernighan and Dennis Ritchie. Several other books and online tutorials also exist. CMOC has its own manual, which explains how to compile a C program to a BIN-format executable.

Elementary types

char -128..127 (8 bits).
unsigned char 0..255 (8 bits).
int or short -32768..32767 (16 bits); on modern platforms, int is typically 32 bits, while short is still 16 bits.
unsigned or unsigned int or unsigned short 0..65535 (16 bits); on modern platforms, this is typically 32 bits.
long -2147483648..2147483647 (32 bits including 1 sign bit).
unsigned long 0..4294967295 (32 bits).
float Single-precision floating point type close to IEEE 754. When compiling with CMOC, only available when Extended Basic is present or when using option --mc6839, which adds about 8k to the executable. The size is then 40 bits under Extended Basic, 32 under --mc6839 (see Floating-point arithmetic in the CMOC manual). Typically 32 bits on modern platforms.
double Double-precision float. Also 40 bits when Extended Basic is present. Also 32 bits when compiling with --mc6839. CMOC always makes double an alias for float, which is why they are the same size. Typically 64 bits on modern platforms.

Names can be given to types with the typedef keyword.

Strings in C do not exist as they do in Basic. They are represented as arrays of char. See the following section for details.

Quick Reference

Start your program with #include <cmoc.h> to declare the functions that are provided by CMOC's small C library, e.g., printf(), rand(), etc. Some CoCo specific functions are declared by #include <coco.h>. A .h file is called a header file.

BasicCRemarks
PRINT "FOO"
PRINT "BAR";
printf("FOO\n");
printf("BAR");

or

printf("FOO\nBAR");

\n represents a "new line" character. CMOC's printf() maps this to CHR$(13). Omit it if no line change is wanted.

A C statement must be terminated by a semi-colon, except when it finishes with curly braces.

I=42 int i = 42;

or

int i;
i = 42;

The first line declares an integer variable called i and immediately initializes it to 42. An int can take -32768 to 32767 inclusively (16 bits).

The 2nd form separates initialization from declaration. Before being assigned 42, i may contain garbage.

Use unsigned instead of int to represent the range 0..65535.

W=-100000 long w = -1000000L;

32-bit signed integer (-2147483648..2147483647).

Use unsigned long to represent an unsigned 32-bit integer (0..4294967295). Use the UL suffix for constants: 70000UL.

X=3.1416 float x = 3.1416f;

With CMOC, floating-point arithmetic is only usable if the program runs with Extended Basic in memory. The f suffix on 3.1416 implies the single-precision float type, which means a 40-bit float with CMOC. Without the f, the type is double, which CMOC still maps to a 40-bit float. (On many other platforms, float is 32 bits while double is 64 bits.)

&HFF22 0xFF22

or

0xff22

32-bit hexadecimal constants are also supported: 0xDEADBEEF.

PRINT "A =";A;", B =";B printf("a = %d, b = %f\n", a, b);

%d means signed integer. %f means float or double.

Be careful to pass values of types that match the % placeholders.

CMOC's printf does not support all of C's printf's % placeholders and options.

%u means unsigned integer (0..65535).

N$="KNUTH";
PRINT N$;
char name[6];
strcpy(name, "KNUTH");
printf("%s", name);


C strings are not dynamically allocated like in Basic. The programmer typically declares an array of characters. The length must be enough for the string's characters, plus a terminating zero byte.

Here, the string to store has 5 characters, so the array must have (at least) 6 bytes to accommodate the zero byte.

printf(name) could also work here, but only if name contains no % characters. With %, it becomes a potential security flaw. It is recommended to always pass printf a format string in quotes.

PRINT USING "%   %";"X"
PRINT USING "###.###";1/3
printf("%-5s\n", "X");

The minus sign in %-5s means left justify. Omit it for right justify.

C's printf supports printf("%7.3f\n", 1.0f / 3.0f); which would be equivalent to ###.###, but CMOC's printf does not support width and precision for %f, as of 2025.

1+(2-3*4/5)

3↑7
1+(2-3*4/5)

C has no exponentiation operator.

The FuncPlot program on the CMOC Home Page has a powf() function in its source code that can work if Extended Color Basic is present in memory.

IF A>9 THEN A=0:PRINT "FOO" ELSE PRINT "BAR" if (a > 9)
{
  a = 0;
  printf("foo\n");
}
else
  printf("bar\n");

Braces are only required to group more than one statement together. They can be used around a single statement, if preferred.

FOR I=0 TO 100 STEP 2:PRINT I:NEXT I for (int i = 0; i <= 100; i += 2)
{
  printf("%d\n", i);
}

1st argument of for: Initialization statement. A variable declared here is only valid in the for statement. A previously declarated variable can also be used.

2nd argument: Condition to continue. Evaluated before the 1st iteration.

3rd argument: Statement that updates the control variable.

The body only needs braces if it contains more than one statement.

I = 0
WHILE I <= 100
  PRINT I
  I = I + 2
WEND
int i = 0;
while (i <= 100)
{
  printf("%d\n", i);
  i += 2;
}

WHILE is not supported by Color Basic.

In C, i = i + 2 could also be used, but may generate slightly less efficient code.

DO
  PRINT X
  X = X + 1
LOOP WHILE X < 5
int x;
do
{
  printf("%d\n", x);
  ++x;
} while (x < 5);

DO/LOOP is not supported by Color Basic.

A = A + 1
A = A + 3
B = B - 1
B = B - 2
C = C * 4;
D = D / 4;

++a;
a += 3;
--b;
b -= 2;
c *= 4;
d /= 4;

The right sides can be complex expressions, e.g., c /= a + b * 2;

A * 256
B / 16
a << 8
b >> 4

<< is the left bit shift operator. It can be used to multiply an integer by a power of 2.

>> is the right bit shift operator. It can be used to divide an integer by a power of 2.

X OR 64
Y AND NOT 8
x | 64
y & ~8

|, & and ~ are bitwise operators.

Not to be confused with ||, && and ! which are the corresponding logical operators, typically used in an if or while or do statement.

LEN(A$) strlen(a)

Assumes that a is a character array or a pointer to a sequence of characters (char *).

A$="FOO"
B$="BAR"
C$=A$+B$

char c[N];
strcpy(c, a);
strcat(c, b);

N must be replaced with a number of characters that is sufficient to contain the characters in arrays a and b, plus the terminating null byte.

N must be a constant.

In C, malloc() can be used to allocate memory dynamically, but CMOC does not come with such a function. Dynamic allocation is often best avoided on a slow machine.

IF A$="FOO" if (strcmp(a, "FOO") == 0)

strcmp() is case sensitive. stricmp() is not. strncmp() takes a 3rd argument that is the maximum number of characters to compare.

They all return 0 when the two strings are considered equal, -1 when the first string comes before the second one in lexical order, +1 when the first one comes after.

IF A>0 AND B<0 OR C=1 AND D<>2

 Y=(X AND 15) OR 128

 IF NOT (A=1 OR B=2)

 Y=NOT X

 Y=X XOR 8
if (a > 0 && b < 0 || c == 1 && d != 2)

int y = (x & 15) | 128;

if (!(a == 1 || b == 2))

int y = ~x;

int y = x ^ 8;

&& has precedence over ||. Both of them return true or false: they are logical operators, not bitwise operators. C also has & and |, which are the bitwise versions.

! is the logical negation operator.

~ is the bitwise inversion operator.

Basic does not have an exclusive-or operator; ^ is a bitwise operator in C.

ASC(A$) a[0]

If a is a character array, then a[0] is a value of type char, which can be treated as an 8-bit integer. A char value is not a string. (There are no strings per se in C, only character arrays.)

CHR$(27)

 PRINT "RED BLOCK: ";CHR$(191)
"\x1B"
or
'\x1B'

printf("RED BLOCK: \xBF\n");
or
printf("RED BLOCK: %c\n", 191);

A string literal is delimited by double quotes and has the type of a character array. A character literal is delimited by single quotes and has type char: it is a byte-sized signed integer value, not a string. (unsigned char is an unsigned byte.)

\x introduces a 2-digit hexadecimal code that can be used to represent special characters.

N$="1000":N=VAL(N$)

R$="3.1416":PI=VAL(R$)
const char *s = "1000";
int n = atoi(s);

const char *r = "3.1416";
float pi = atoff(r);


The floating-point example only works if targeting the CoCo's Extended Basic environment. Its floating-point routines must be present.

X=1000:A$=STR$(X)

Y=3.1416:B$=STR$(Y)
int x = 1000;
char a[7];
itoa(x, a, 10);

float y = 3.1416f;
char b[38];
ftoa(b, y);

That array passed to itoa() must have enough characters to represent a 16-bit integer, including the terminaing null character. For example, -32768 takes 5 characters, plus the '\0' that ends the string. The 3rd argument to itoa() must be 10 because CMOC's implementation of that function only supports base 10.

Unlike STR$(), itoa() does not start the string with a space when the number is non-negative.

ftoa() is only available if targeting the CoCo's Extended Basic environment.

The array passed to ftoa() must be at least 38 characters long. The f suffix on 3.1416f specifies that it is a single-precision number. Without that suffix, it would be a double-precision number. CMOC however makes both float and double the same size (5 bytes). It is recommended to use the f suffix when specifying float instead of a double.

F=INSTR(A$, "Y")
IF F=0 THEN PRINT "NOT FOUND" ELSE PRINT "FOUND AT INDEX";F
char *found = strstr(a, "Y");
if (found == NULL)
  printf("NOT FOUND\n");
else
  printf("FOUND AT INDEX %u\n", found - a);

If the 2nd string is found in the 1st string, strstr() returns a pointer to inside the 1st string where the 2nd string was found. If the 2nd string is not found, strstr() returns NULL.

The index computed as found - a will be 0 if the 2nd string is found at the beginning of a. Note that INSTR returns 1 in such a case; it returns 0 when the 2nd string is not found.

A$="FOOBAR"
P$=LEFT$(A$,3)
S$=RIGHT$(A$,2)
M$=MID$(A$,3,2)
char a[7];
strcpy(a, "FOOBAR");

char p[4], s[3], m[3];

strncpy(p, a, 3);
p[3] = '\0';

strcpy(s, a + strlen(a) - 2);

strncpy(m, a + 3 - 1, 2);
m[2] = '\0';

P$ becomes "FOO", S$ becomes "AR" and M$ becomes "OB".

The 2nd argument of MID$ is an index such that 1 is the first character ('F' in this example). In C, array indices start at 0, hence the "- 1" in the last strncpy() call.

strncpy() copies at most the number of characters specificed by its 3rd argument. This explains why the string gets terminated explicitly with a '\0' on the following line.

INPUT N
INPUT L$
LINE INPUT L$
char *response = readline();
int n = atoi(response);
float x = atoff(response);

readline() is specific to CMOC. In C, use fgets().

response will point to a zero-terminated string. It can be printed with printf("%s", response);

atoi() converts a decimal string to an integer value.

atoff() converts a decimal string to a floating point value. (This function only works if targeting the CoCo's Extended Basic environment.)

10 IF A=1 THEN PRINT "FOO":GOTO 90
20 IF A=99 THEN PRINT "BAR":GOTO 90
30 IF A=-35 THEN PRINT "BAZ":GOTO 90
40 PRINT "QUUX"
90 END


switch (a)
{
case 1:
  print("FOO\n");
  break;
case 99:
  print("BAR\n");
  break;
case -35:
  print("BAZ\n");
  break;
default:
  print("QUUX\n");
}

Do not forget the break statement at the end of a case, otherwise the execution will continue to the next case.

The last case in the switch body does not need a break statement.

The argument of the switch statement must be an integer expression, not a float, double or string expression.

10 X=5
20 GOSUB 1000
30 PRINT Y
40 END
1000 Y=X*X+1
1010 RETURN
int main()
{
  int x = 5;
  int y = f(x);
  printf("%d\n", y);
  return 0;
}

int f(int n)
{
  return n * n + 1;
}

Parameter n is local to function f(). Even if it were named x, it would still be a different variable than the x declared in main().

A local variable can have the same name as a global one. The local variable then masks the global one over the scope where the local variable exists.

A variable is local to the curly braces inside which it is declared. One exception is a variable declared in a for() loop, i.e., for (int i = 0; ...) {...}. That variable exists both in the for() body and in the for() parenthesis.

PSET, PRESET, LINE, DRAW, PAINT, CIRCLE

See the BGraph library on the CMOC Home Page.

INKEY$, JOYSTK and BUTTON

See the BControl library on the CMOC Home Page.

PLAY

See the BSound library on the CMOC Home Page.

RND(10) rand() % 10 + 1

rand() returns an int (signed) between 0 and 32767 inclusively. % is the modulo operator. In this example, rand() % 10 will return a value between 0 and 9 inclusively, then we add 1 to return 1..10 as in the Basic example. rand() is declared by <cmoc.h>

RND(-TIMER) srand(getTimer())

This seeds the random number generator used by rand(). This assumes that #include <coco.h> has been specified, to define getTimer(). The latter assumes that Color Basic is running normally, i.e., with interrupts enabled. srand() is declared by <cmoc.h>

OPEN, CLOSE

See the decbfile library on the CMOC Home Page.

GOTO n for (init; condition; increment)
{ ... }

while (condition)
{ ... }

do { ... }
while (condition);

Go To Statement Considered Harmful, CACM, by Edsger Dijkstra, March 1968.

To leave a C loop from its middle: break.

To jump from the middle of a C loop to the loop condition: continue.

END exit(0);

The program must have specified #include <cmoc.h> before making this call.

PROCEDURE stuff
PARAM a, b
PRINT a; b
END
void stuff(int a, int b)
{
  printf("%d %d\n", a, b);
}

Procedures are not supported by Color Basic. They are by BASIC09, which runs under OS-9.

This C code defines a function named stuff that accepts integer parameters named a and b. The void keyword means that the function has nothing to return. C does not have procedures: "void" functions are the equivalent.

Parameters are always passed by value in C, not by reference. Changing a and b in this function would not change the variables of the calling function.

Passing a string is done by passing a pointer to the first character of that string. The name of an array represents the address of its first element:

void example(void) {
  char name[10];
  strcpy(name, "Joe");
  f(name);
}

Function f is then declared this way:

void f(const char *n) { ... }

The const keyword means that function f promises not to modify the data pointed to by n.

PROCEDURE f
PARAM r
r := 1000
END
void f(int *pointerToInteger)
{
  *pointerToInteger = 1000;
}

To receive an integer by reference from function f, one must pass the address of an integer-typed location. The address of a variable is obtained with the & operator. To call this function:

int n;
f(&n);
printf("Got %d from f\n", n);

A simpler way to return a single value is the return statement:

int f(void) { return 1000; }

The type of the returned value of a function is specified before its name (int in this example). A function can have more than one return statement.

RUN stuff (10, 20) stuff(10, 20);

In C, function stuff() must already have been defined, or at least declared.

A C function can be declared without being defined by specifying a line like the following, which is called a function prototype:

void stuff(int a, int b);

DIM foo:BOOLEAN
foo=TRUE
typedef unsigned char BOOL;
enum { FALSE, TRUE };

BOOL foo;
foo = TRUE;

There is no boolean type in C89. CMOC's <coco.h> header file defines BOOL as an unsigned byte. It also defines TRUE as 1 and FALSE as 0.

TYPE Employee=name:STRING;number:INTEGER;temp:BOOLEAN struct Employee
{
  char name[32];
  int number;
  BOOL temp;
};


struct Employee e;
strcpy(e.name, "Joe");
e.number = 42;
e.temp = TRUE;

struct Employee *ptr = &e;
ptr->number = 43;

Record types are not supported by Color Basic. They are by BASIC09.

BOOL, TRUE and FALSE can be defined by #include <coco.h> or with typedef and enum.

For convenience, typedef can be used to avoid the requirement to specify struct when declaring an instance of a struct:

typedef struct S {
  char a;
  int b;
} S;

S foo = { '@', 1000 };
REM A comment line.

' Another comment line.
/* A comment line. */

/* A multi-line
   comment.
*/

CMOC and modern C compilers also accept single-line comments that are introduced by //:

// A single-line comment.