CMOC for BASIC Programmers

Date: 2023-05-05
Copyright © 2021-2023 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.

Types

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 2023.

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
B = B - 2
C = C * 4;
D = D / 4;

++A;
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.