CMOC for BASIC Programmers

Date: 2021-02-19

Copyright © 2021 Pierre Sarrazin <http://sarrazip.com/>

This manual can be distributed on the same terms as those of the CMOC compiler.

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

BASIC

C

Remarks

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 programs runs with Extended Basic in memory. The f suffix on 3.1416 means 40-bit float. Without f, the type is double, but CMOC maps it to 40-bit floats anyway. (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 2020.

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

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 "BLACK BLOCK: ";CHR$(128)

"\x1B" or '\x1B'

printf("BLACK BLOCK: \x80\n");

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 integer value, not a string.

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

INSTR(A$, "Y")

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 "BAR", 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.

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\n", response);

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

atoff() converts a decimal string to a floating point value. (As of 2019, it only works if Color Basic is present.)

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 8-bit or 16-bit integer expression, not a long, float, double or string expression. (The restriction on longs is specific to CMOC, not to C.)

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 one 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 braces 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.

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, by Edgar Dijkstra

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.

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

Note that parameters are always passed by value in C, not by reference. Changing a or 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 this address:

void example(void) {

  char name[10];

  strcpy(name, "Joe");

  f(name);

}

Function f is then declared this way:

void f(const char *n) { … }

PROCEDURE f

PARAM r

r := 1000

END

void f(int *pointerToInteger)

{

  *pointerToInteger = 1000;

}

Call:

int n;

f(&n);

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

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 in front of 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 this, which is called a function prototype:

void stuff(int a, int b);

BOOLEAN

typedef unsigned char BOOL;

There is no boolean type in C89. CMOC's <coco.h> header 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 the struct:

typedef struct S {

  char a;

  int b;

} S;

S foo = { '@', 1000 };