Training C - Compiler/Interpreter for Teaching Programming in C

Training C is a Compiler/Interpreter intended for casual programming and for teaching kids - ages, say, 10 to high school - how to program in C. Features of Training C are:

  • An interactive mode, similar to the one for Python, which allows a user to test expressions, statements and declarations by typing them in and having them evaluated on the spot.
  • One click compile and run from a customized edit window.
  • A somewhat simplified version of C, to make it easier for a beginner to get started.
  • Stricter checking than is usual for C compilers - in order to catch trainee errors or misunderstandings as early as possible.
  • Runtime error checking that include use of uninitialized variables, array bounds checking and bad pointer references.
  • Integrated debugging capabilities.

I am looking for people who would like to try this out. If interested, please let me know.

rtoelle@telus.net
May 16, 2017

Demonstration of Interactive Mode


Training C Version 0.90
>>// Training C demonstration -
>>//
>>// Expressions, statements or declarations may be entered in interactive
>>// mode. Expressions are evaluated and the result is displayed -
>>3+4
7
>>1 << 5
32
>>1.0 / 3.0
0.333333
>>99/10		/* integer division truncates in C */
9
>>
>>// Variables may be declared and assigned to -
>>int j = 3
3
>>int i
>>i = j + 1
4
>>i*j*i
48
>>// In order to catch trainee errors or misunderstandings at the earliest stage
>>// possible, type checking by Training C is stricter than by a normal C compiler.
>>// (these warnings can be inhibited by using explicit casts)
>>i = 3.0
      ^
warning: conversion from double to int performed for assignment
3
>>double d = i
             ^
warning: conversion from int to double performed for assignment
3.00000
>>int *ptr = 0
             ^
warning: conversion of 0 to pointer type results in NULL
NULL
>>
>>// A number of standard C functions are built in:
>>sqrt(10.01)
3.16386
>>assert(3 > 5)
        ^
runtime error: assertion violation
>>printf("the square root of two is %10.10f\n", sqrt(2))
the square root of two is 1.4142135624
39
>>strlen("abc")
3
>>// The C99 bool type is built in, and is actually required in situations
>>// in which C programmers are used to using int:
>>3 > 0
true
>>true && false
false
>>if (i = 1) printf("abc\n")
        ^
warning: conversion of int to bool performed for if condition
abc
>>(i ? 3.5 : 4.5)
   ^
warning: conversion of int to bool performed for 1st operand of ?
3.50000
>>(i != 0 ? 3.5 : 4.5)		// this is what was probably meant
3.50000
>>
>>// For convenience, a print statement is provided. It prints one or more comma
>>// separated expressions. Values are separated by a space, and followed
>>// by a newline character:
>>print 3.4f, "hello", 5 == 4, 99 >> 1, sizeof(double)
3.40000 hello false 49 8
>>if (false) print 1; else print 2
2
>>for (i = 0; i < 4; i++) print i, i*i, i*i*i
0 0 0
1 1 1
2 4 8
3 9 27
>>// In interactive mode, semicolons are optional at the end of lines, but
>>// are otherwise used as usual:
>>print 1;;print 2;
1
2
>>// A backslash as the last character in a line can be used to continue the line:
>>if (3 == 3) \
++    print 1; \
++else \
++    print 2
1
>>// Two informative commands, typeof and parse, are provided:
>>typeof &i
pointer to int
>>typeof true ? 2.0 : 3.0
double
>>parse a >> b << c
(LSHIFT (RSHIFT (IDENT a) (IDENT b)) (IDENT c))
>>parse *a[i]
(PTRDEREF (ARRAYREF (IDENT a) (IDENT i)))
>>
>>// Functions can be defined and called:
>>int main() { \
++    printf("Hello world\n"); \
++}
>>main()
Hello world
0
>>int fac(int n) {return n <= 1 ? 1 : n * fac(n-1); }
>>fac(5)
120
>>
>>// There is extensive runtime checking. Division by zero:
>>i/0
   ^
runtime error: division by zero
>>// Array bounds checking:
>>int a[4] = {0}
>>a[3] + a[4]
          ^
runtime error: array subscript value is out of range
    array subscript value: 4 array size: 4
>>// Use of uninitialized variables:
>>int b[3]
>>b[0] = b[2] = 77;
77
>>b[0] + b[1] + b[2]
          ^
runtime error: use of uninitialized memory
>>// NULL pointer references:
>>int *p = NULL
NULL
>>print p
NULL
>>*p
  ^
runtime error: attempt to dereference the NULL pointer
>>// Pointer values out of range:
>>p = &b[0]
0x0078
>>*p + *(p+2) + *(p + 20)
                ^
runtime error: attempted load from out of range address
>>
>>// There are a number of commands to assist in debugging:
>>show functions
int    fac(int n)
int    main(void)
>>break main
>>main()
(55) int main() { \
user breakpoint at entry to function main
nested>step
(56)     printf("Hello world\n"); \
nested>continue
Hello world
0
>>// Type quit to exit a Training C session:
>>quit


Program Examples

One of the most useful Training C features is Interact Mode (the Interact button or -i option), in which Training C compiles your source and then enters interactive mode to give you a chance to test it out. In the following example, the sieve process can be performed by typing do_sieve(), and then the results can either be examined directly, or by making multiple calls to results(). Or, you could set a breakpoint for do_sieve(), and step throught the sieve process. (just type a = {0} to reinitialize the sieve array.)

// Use the sieve method to find primes and factor numbers

#define N 1000000  // sieve array size
assert(N >= 5);
int a[N] = {0};

void do_sieve(void)
{
    int p = 2;    // initial prime number

    do {
        /* mark multiples of p as non-prime: */

        for (int i = p*p; i < N; i = i + p)
            /* to get factors in ascending order, don't mark out
               an element if it is already marked out: */
            if (a[i] == 0)
                a[i] = p;

        /* scan sieve array to find next prime number: */

        for(p++; a[p] != 0; p++) ;

    } while (p*p < N);    // quit this process once p is too large
}

void results(int n1, int n2)
    /*  print results for integers from n1 to n2: */
{
    if (n1 < 2)  n1 = 2;
    if (n2 >= N) n2 = N - 1;

    for (int i = n1; i <= n2; i++) {
        if (a[i] == 0)
            printf("%5d: PRIME\n", i);
        else {
            printf("%5d: Factors: ", i);
            int k = i;
            do {
                printf("%d ", a[k]);
                k = k / a[k];
            } while (a[k] != 0);
            printf("%d\n", k);   // print the last factor
        }
    }
}

Executable statements may appear outside of any function body (at level 0). For example:

// define a function to sum the elements of an array:
double sum_array(double a[], int n)
{
    double sum = 0.0;
    for (int i = 0; i < n; i++)
        sum = sum + a[i];
    return sum;
}

double a[] = {1.1, 2.1, 3.1, 4.1, 5.1};
print "elements of array:", a;
print "sum of elements:", sum_array(a, 5);

// reinitialize array a and try again:

a = {100, 200, 300.1};   // a[3] and a[4] will be set to 0
print "elements of array:", a;
print "sum of elements:", sum_array(a, 5);

The following program builds a graph and traverses it to produce an output string. To test this program you would click the Interact button (or use the -i option). Training C would compile it and then enter an interactive session. At that point you could invoke build_graph(), dump_graph() and do_tour() to build and traverse various instances of the graph.

// Find a string on a fixed size alphabet in which every possible string of
// length len occurs exactly once as a substring (a De Bruijn sequence).
// This is done by building a De Bruijn graph and recursively traversing it.
// Each edge traversed corresponds to a character in the resulting D.B. sequence.

#define MAXLEN 5   // maximum substring length
int len;           // substring length

// the number of graph nodes will be equal to alphabet_size ** (len-1)
#define MAXNODES 625
int num_nodes;
// the number of graph edges will be equal to alphabet_size ** len
#define MAXEDGES 3125
int num_edges;

typedef int NODE;
char node_name_buff[MAXNODES * MAXLEN];
char *node_name[MAXNODES];
int edges[MAXNODES][MAXNODES];
int edge_present;

void build_graph(char *alphabet, int substrlen);
void dump_graph(void);
void do_tour(NODE start_node);

int ipow(int a, int b)     // compute a ** b
{
   int v = 1;
   while (b-- > 0)
      v = v*a;
   return v;
}

void build_graph(char *alphabet, int substrlen)
{
    int n, m, i, j, alpha_size;

    len = substrlen;
    assert(len >= 2 && len <= MAXLEN);
    alpha_size = strlen(alphabet);
    assert(alpha_size >= 2);
    num_nodes = ipow(alpha_size, len-1);
    assert(num_nodes <= MAXNODES);
    num_edges = ipow(alpha_size, len);
    assert(num_edges <= MAXEDGES);
    edge_present = 1;
    edges = {0};    // zero out the edges array

    for (n = 0; n < num_nodes; n++) {
        int val = n;
        j = len * n;
        node_name[n] = &node_name_buff[j];
        for (i = 0; i < len-1; i++) {   /* name of len-1 characters */
            char c = alphabet[ val % alpha_size ];
            node_name_buff[j + len - i - 2] = c;
            val = (val - (val % alpha_size)) / alpha_size;
        }
        node_name_buff[j + len - 1] = '\0';
        assert(val == 0);
    }

    for (n = 0; n < num_nodes; n++) {
        for (m = 0; m < num_nodes; m++) {
            /* len-2 characters of the node names must match: */
            for (i = 0; i < len-2; i++) {
                if ((node_name[n])[i+1] != (node_name[m])[i])
                    goto dont_add_edge;
            }
            edges[n][m] = edge_present;
        dont_add_edge: ;
        }
    }
}

void dump_graph(void)
{
    int n, i;

    for (n = 0; n < num_nodes; n++) {
        printf("(%2d) ", n);
        printf("%s  out-going edges:  ", node_name[n]);

        for (i = 0; i < num_nodes; i++) {
            if (edges[n][i] == edge_present)
                printf("  %s", node_name[i]);
        }
        printf("\n");
    }
}

void find_tour(NODE curr);
char tour[MAXEDGES + 1];
int k;     // index into tour array for next character

void do_tour(NODE start_node)
    /* traverse graph starting at start_node */
{
    assert(start_node >= 0 && start_node < num_nodes);

    printf("Computing tour starting at node %s\n", node_name[start_node]);

    k = num_edges;             /* point to end of tour array */

    find_tour(start_node);     /* traverse the graph */
    assert(k == -1);

    tour[num_edges] = '\0';
    printf("----- tour = <%s-", node_name[start_node]);
    printf("%s>\n", tour);

    edge_present ++;           /* prepare for next call of do_tour */
}

void find_tour(NODE curr)
    /* find_tour calls itself recursively to traverse the graph and put
       the results into tour[] */
{
    NODE i;
    assert(curr < num_nodes);

    for (i = 0; i < num_nodes; i++) {
        if (edges[curr][i] == edge_present) {
            edges[curr][i] ++;        /* delete edge */
            find_tour(i);
        }
    }

    k--;
    if (k != -1) {
        assert(k >= 0);
        /*  for the tour string, we use the last letter of the node name: */
        tour[k] = (node_name[curr])[len-2];
    }
}

How to Run

Installing

A Training C installation consists of two files, trc.exe and trcedit.exe, which can be run on windows systems. These files are placed in whatever directory desired and desktop shortcuts are created to them. Clicking the Training C Interactive shortcut will bring up a Training C window in interactive mode. Clicking the Training C Edit shortcut will bring up a Training C edit window. C source code can be typed into this window and Training C itself can be invoked by clicking on the Session, Check, Interact or Run buttons.

The Training C window is apt to come up with a small font. To get a reasonable font size, color scheme, and window size, right click on the shortcut icon and select properties.

Running Training C in a Command Window

Typing just trc.exe will enter Training C interactive mode.

To run Training C in batch mode, type trc.exe followed by the name of your C source file. The file will be compiled and statements at level 0 will be executed. Then, if the file contains a function named "main", it will be executed. If you don't want the program executed, add a "-c" option (check only) to the command line.

To run Training C in Interact Mode, specify your source file, followed by a "-i" option. In this mode, your file will be compiled, and Training C will then enter interactive mode. At this point you can test your functions by invoking them with various arguments, examining variables, etc. A main function, if present, won't have been run - just type "main()" when you want to run it.

Commands

In addition to C statements and expressions, there are a number of Training C commands that can be typed in in interactive mode. Commands are recognized only when they begin in column 1 of an input line - so if you have a variable or function whose name conflicts with a command name, just ensure that it doesn't appear at the beginning of a line.

Nested mode refers to the situation in which execution of a user function has been interrupted. This can happen as a result of a user set breakpoint, or because of a runtime error. In nested mode Training C changes its prompt to "nested>", and C code and commands can be entered as usual. The quit command will cancel execution of the user program and return to normal interactive mode.

quit End a Training C interactive session. If in nested mode, execution of the user program will be ended and Training C will return to normal interactive mode.
continue
c
If in nested mode, continue execution of user program.
print expr Print expression value.
show f[unctions] Print list of defined user functions and their types.
show v[ariables] Print list of variables in the current scope, with their types and current values.
show b[uiltins] Print list of builtin functions (C library functions) available in your version of Training C.
typeof expr Display type of the expression.
list Display source surrounding current line position.
list func List source lines for indicated function.
list n List 10 lines beginning at indicated line.
list n,m List range of lines.
break line
break func
Set breakpoint at indicated line, or at entry to named function.
clear line
clear func
Clear previously set breakpoint.
step
s
Step to next user statement.
next
n
Same as step, but don't step into a function call.
where Show current program location.
backtrace [n]
bt [n]
Print backtrace of call stack. n specifies the maximum number of frames to print (10 by default).