# Dusk OS C compiler

The C compiler is a central piece of Dusk OS. It's written in Forth and is
loaded very early in the boot process so that it can compile the drivers it's
about to use.

This compiler needs to meet two primary design goals:

1. Be as elegant and expressive as possible in the context of a Forth, that is,
   be an elegant fallback to Forth's shortcomings.
2. Minimize the work needed to port existing C applications.

It is *not* a design goal of this C compiler to be able to compile POSIX
applications without changes. It is expected that a significant porting effort
will be needed each time.

Because of the first goal, we have to diverge from ANSI C. The standard library
will likely be significantly different, the macro system too. Both will
hopefully fit better with Forth than their ANSI counterparts.

But because of the second goal, we do want to stay reasonably close to ANSI.
The idea is that the porting effort should be mostly a mechanical effort and
it should be as little prone as possible to subtle logic changes caused by the
porting.

For this reason, the core of the language is very close to ANSI C.

## Other pages

* Implementation details [doc/comp/c/impl]
* Standard Library [doc/comp/c/lib]

## Usage

There are three ways to compile C code with this compiler:

1. cc<< ( -- )
2. ccc<< ( -- )
3. :c

The regular method is through "cc<<", which compiles the specified file. For
example, "cc<< foo.c" Reads the file "foo.c" as a unit and compiles every
element in it. Functions will be added to the system dictionary unless they have
the "static" storage type.

"cc<<" clears local symbols and macros from the previous unit before it
proceeds. In some cases, you don't want that. In this case, you can use "ccc<<"
(*c*ontinue cc<<) which doesn't clear this data.

Another method is to compile in an "inline" manner with ":c". This word reads a
single "unit element" (a function definition, a global variable or a typedef)
from the input stream and compiles it. It returns to normal Forth interpretation
after it parses the last token of the element. Example:

:c int foo() {
  return 42;
} foo . \ prints 42

As with "ccc<<", ":c" doesn't clear temporary data before it compiles.

## Differences in the core language

Writing for DuskCC is the same as writing for another ANSI C compiler, but there
are a few differences:

* the C preprocessor is similar, but not 100% compliant
* no 64bit types
  * no long, redundant with int
  * no double, float is always 32b
  * char is always 8b, short is always 16b, int is always 32b
* No "signed" or "unsigned". char/short/int are signed, uchar/ushort/uint are
  unsigned.
* No logical shortcut in the "x ? y : z" operator. With && and ||, shortcuts
  work as expected.
* Number literals are the same as Dusk OS, so 12345, $1234 and 'X'. As a special
  case, the "0x" prefix is supported as a "$" alternative. No octal support. No
  '\n', '\r', '\t', '\0' char literals.
* string literals are not null-terminated, but "counted strings". The exact same
  format as system strings [doc/usage/lit]. You can create a null-terminated
  string literal by appending a "0" next to the closing quote, for example
  "hello"0. This 0 shifts the address of the literal by 1 (to skip the count)
  and adds an extra 0 at the end.
* Added pspop() and pspush() built-in functions. They're tricky to use and are
  described in cc/impl.
* No "varargs". pspop() and pspush() replace that functionality.
* "struct MyStruct {...};" automatically creates a "typedef" to the struct.
* The "struct" keyword can't be used to reference structs, only to define them.
* Variables can't have the same name as their type, it confuses the compiler.
* Unions aren't automatically "entered" during struct name matching. If you have
  a struct with a union in it, field access must name the union explicitly.
* No bit fields. I expect that they're not worth their complexity weight, not
  only to parse them and generate them correctly, but also to do so in an
  efficient manner. The plan is to port code using bit fields with manual
  masking/unmasking code.
* The keyword "static" has a slightly different meaning. See below.
* No multi-dimensional array.
* No "const" attribute.
* "enum" is not a type, is always anonymous and has no other purpose than to
  declare the constants it contains. Those constants, like types, are permanent
  and never cleared from memory.
* "enum" can only be declared at unit top level.

## Pre-processor

The pre-processor allows you to define text expansion macros which can then be
used directly in C code. You define a macro with #define:

    #define FOO bar

The #define directive reads the next identifier, considers it the macro name,
and then reads the rest of the lines and associates it verbatim with that name.

Then, the tokenizer, whenever it encounters an identifier, checks if it
corresponds to a macro name before sending it to the parser. If it does, that
name is not sent to the parser. Instead, the content of the associated macro is
processed through lib/macro (see doc/lib/macro) and fed back into the tokenizer
as if that content had textually replaced the macro name. Parameters to feed to
the macro must follow the macro name exactly as described in doc/lib/macro.
Therefore:

    #define RET return %<;
    int foo() { RET "42" }

is the exact same equivalent to:

    int foo() { return 42; }

## Function prototypes

A function prototype is a function without a body. Examples:

    static int foo(short a, char b);
    uint max(uint a, uint b);

There are two types of prototypes, static and non-static, with both a completely
different usage.

Static prototypes are for forward declarations. When you declare one, memory
wide enough for a jump is allocated. Then, when the real function is declared,
it writes a jump to itself in that reserved space.

The static attribute of the prototype is not carried to the implementation
function. You can very well have a prototype (which uses "static") that is a
forward declaration to a non-static implementation.

The non-static prototype is to allow C to call Forth words. By default, C has no
visibility to the Forth dictionary because Forth words don't have C function
signatures. When you declare a non-static function prototype, the CC looks into
the system dictionary for a word of that name and links that symbol to the found
word. There's an error if not found.

You can only link words that have a signature compatible with C, that is: 0 or 1
return value.

Be aware that if you link words that do fancy things like shrinking PS or
modifying RS, you are on risky grounds and you should know what you do. The best
approach with these situations is to proxy the word as "void()" and use
"pspop()" and "pspush()" for argument passing.

Also, note that CC's lib [comp/c/lib] already proxies quite a few system words.

## calias

When a prototype function wants to interface a forth word that has a name that
can't be expressed as a C identifier, we have to resort to creating a proxy
forth word for it and it's tedious (and pollutes the forth namespace).

To alleviate this, we have the "calias" word (this is a forth word, not a C
statement). It allows the creation of a prototype linking to a forth word of a
different name. Like "alias", it first take the name of the target word, but
instead of being followed by the name of the word to create, it's followed by a
C function prototype.

For example, if you wanted to interface "0-9?" in C, you would do:

calias 0-9? int isdigit(char c);

## Symbols, types and macro visibility and lifetime

The C compiler creates two kinds of artifacts: types and symbols.

Types are what is created by "struct" and "typedef". Those artifacts bind a name
to type information. Once they're created, the following C code can use these
names to refer to these types.

Types are never cleared. Once a type is created, it is always available. It can,
however, be shadowed by a new type of the same name.

Creating a type doesn't create a Forth word, so by default, types are invisible
to Forth. It is possible, however, to export a type to a Forth structure with
":export ( self -- )". This word generates fields [doc/usage/bind] for each of
the struct's members. You'll typically want to wrap an :export into "struct[ ...
]struct". The code looks like this:

  struct[ MyType S" MyType" findTypedef CDecl :export ]struct

After that, a "MyType" struct with the same fields as the C type is available to
Forth.

Symbols are declarations of functions and variables at different offsets. A
variable declared outside a function in a C unit is a global variable and
generates a Forth word that acts like a "create" word: it yields the variable's
address.

A function declaration also generates a Forth word that call into the generated
function.

If you don't want to generate a Forth word for your declaration, begin the
declaration with the "static" keyword. Then, the function or variable will only
be available to C code.

Non-static symbols are never cleared from memory. They always stay available to
both Forth and C code. Symbols can be shadowed by a new definition of the same
name.

Static symbols are cleared at the beginning of the "cc<<" call. The ":c" word
doesn't clear static symbols and can refer static symbols created in the
previously compiled C unit.

During symbol lookups, static symbols are searched first, so a non-static symbol
cannot shadow a static one. When static symbols are cleared, they unshadow any
non-static symbol they would shadow.

Symbols declared in function bodies are local variables and are cleared at the
end of the function body. During lookup, they are searched before all global
symbols.

Function arguments are part of the function signature type, so they're permanent
just like the signature.

Macros are not exposed to Forth as words and are cleared at the beginning of the
"cc<<" call.

## Header files

Dusk OS has some ".h" files littered around which have the same name as a
corresponding Forth source file. These files are not C source files, they're
Forth source files, but they serve the purpose of a traditional ".h" file in the
outside world, that is, to provide C with an interface.

These files can't be simple C source files because providing a C interface to a
Forth library often involves more than simple function prototypes or structure
definitions. It often involves mangling of the arguments, which have to be done
in wrapper words written in Forth.

Therefore, if you want to use a Forth library in C that has a corresponding ".h"
file, you'll want to load that ".h" file with "?f<<" before you compile your C
code.
