Not Quite C Compiler
version 1.0 b1

Introduction

NQC is a simple language for programming the LEGO RCX.  The preprocessor and 
control structures of NQC are very similar to C.  NQC is not a general purpose language - 
there are many restrictions that originate from actual limitations and lack of detailed 
information about the RCX itself.

A NQC program consists of tasks and subroutines.  The first task is started automatically 
when the program is run, the remaining tasks can be started by the program.  Subroutines 
can be shared across all of the tasks.

The compiler itself understands very little of the RCX.  Most of the RCX functionality 
(such as playing a sound, reading an input sensor, etc) is added by the rcx.nqh include.  
This file is included automatically by the compiler (unless -s is specified on the command 
line).  The file itself defines many high level macros (such as PlaySound()) that are 
implemented in terms of low level primitives within the compiler itself.  This approach 
means that new "system" features can be easily added to rcx.nqh without modifying the 
compiler or introducing new keywords.

Usage

The nqcc command line useage looks like this:

nqcc [-s] [-d] [-l] [-e] [-o output_file] [-Dsym=[def]]
     [-Usym] source_file

The default behavior is to compile the specified source_file to an output file that is named 
by replacing the ".nqc" extension of the source filename with ".rcx".  If the source 
filename does not end with ".nqc", the default output filename is the source filename with 
".rcx" appended to it.  The "-o" option may be used to specify an alternate name for the 
output file.

The source file name "-" causes nqcc to read stdin as the source file.  In this case, the -o 
option must be used to explicitly name an output file if one is desired.

The "-s" option prevents the compiler for automatically including "rcx.nqh" before 
compiling the source file.

The "-d" option causes nqcc to atttempt downloading the program to an RCX after 
compiling it.  If an output file was not explicitly named ("-o" option), then no output file 
is generated.

The "-l" option writes a bytecode listing to stdout.

The "-e" options sends compiler error messages to stdout instead of stderr.  This option is 
only needed when running nqcc under Win95, which does not support redirection of 
stderr from the standard shell (command.com).  The following examples show how to 
redirect stderr (to a file called errors) under other platforms:

MPW:	  nqcc test.nqc errors
WinNT:  nqcc test.nqc 2>errors	

The -Dsym[=def] option defines preprocessor symbol sym to the value def.

The -U option undefines preprocessor symbol def.

Two environment variables are used by nqcc:

NQCC_INCLUDE  - this environment variable can be used to specifiy a directory to 
search for include files in addition to the current working directory.  When building 
filenames, nqcc simply concatentates the value of NQCC_INCLUDE with the target 
filename, so NQCC_INCLUDE must end in the appropriate directory separator ('\' for 
PC, ':' for Mac, '/' for Unix).

RCX_PORT - this environment variable can be used to specify a serial port other than the 
default (COM1 for PC, Modem for Mac).  The value of RCX_PORT should be the name 
of the port (for example "COM2" for the PC, or "b" for the Mac's printer port).


Preprocessor

The preprocessor implements the following directives: #include, #define, #ifdef, #ifndef, 
#if, #elif, #else, #endif, #undef.

The #include command works as expected, with the caveat that the filename must be 
enclosed in double quotes:

#include "foo.nqh"

The #define command is used for simple macro substitution.  Redefinition of a macro is 
an error (unlike in C where it is a warning).  Macros are normally terminated by the end 
of the line, but the newline may be escaped to allow multi-line macros:

#define foo(x)  do { bar(x); \
                     baz(x); } while(false)

Conditional compilation works similar to the C preprocessor.  Expressions in #if 
directives use the same operators and precedence as in C.

C and C++ style comments are supported

/* this is
   a comment */

// another comment


Tasks and Subroutines

Each program is made up of tasks and subroutines, each of which is composed of 
statements.  Each program must have at least one task (named "main"), which serves as 
the entry point of the program.

A task looks like this:

task task_name
{
   statements
}


Tasks can be started or stopped with the following statements

start task_name;
stop task_name;


Subroutines look very similar to tasks, but use the sub keyword.  Note that there are no 
parameters or return values for subroutines.  The only variables available are global 
variables.

sub sub_name
{
    statements
}

Subroutines are called just like in C, with the caveat that there are not parameters or 
return value:

sub_name();

Due to a limitation in the RCX firmware, subroutines are not allowed to make subroutine 
calls (either to themselves or another subroutine).

Statements are contained within tasks and subroutines.  Like C, the semicolon (';') is used 
as a statement terminator.  Groups of statements can be combined into a a block using 
braces ('{' and '}').


Values

Many statements (such as variable assignment, or conditional testing) make use of 
"values".  A value can be many things: a constant, the contents of a global variable, a 
random number, the reading from an input sensor, etc.  Each of these is covered in more 
detail below.  In addition, some of the high level commands may take constants and/or 
values as arguments.  In the description of a command its arguments will be noted as 
either "const" or "value".  "const" arguments must be a numeric constant.  "value" 
arguments may be either a numeric constant or another sort of value (such as a variable).  
Note that not all value types are legal for all commands that use values.

Numeric constants can be written as either decimal (e.g. 123) or hexidecimal (e.g. 
0xABC).  Presently, there is very little range checking on constants, so using a value 
larger than expected can have unpredictable results.  Constants can be combined using the 
standard arithmetic operators (+, -, *, /, %, <<, >>, &, |, ^, ~).  Precedence and 
associativity are the same as C.  Parentheses can be used to group lower precedence 
operations together.

The RCX provides 32 global variables.  In order to use variables in your program, you 
must first declare them:

int foo;

All of the variables are global (even across multiple programs), hence they must be 
decalred outside the scope of any tasks and subroutines.  Variables can be assigned values 
using the following assigment operators:

var = value;
var += value;
var -= value;
var *= value;
var /= value;

Note that general expressions (e.g. a = b + c) are only supported for compile time 
constants and not for runtime evaluation:

int foo,bar;

task main
{
    foo = 2 + 3;  // legal
    foo = bar + 2; // illegal
}

Other value types inclue:

	Input(n)	- current value of sensor 'n', where n=0-2
	Timer(n)	- current value of timer 'n', where n=0-2
	Random(n)	- random number between 0 and n-1? (not sure about the range)
	Message()	- value of the last received IR message
	Watch()	- value of the system clock 

Note: the rxc.nqh file defines macros IN_1, IN_2, and IN_3 as Input(0), Input(1), and 
Input(2), respectively. 


Control Structures

Conditions can be either a comparison beteen two values, or one of the predefined 
constants "true" and "false".  There are six relations (<, >, <=, >=, ==, !=).  Conditions 
may be combined using && and ||, or negated with '!".

Here are the control structures:

if (condition) statement
if (condition) statement else statement
while(condition) statement
do statement while(condition)
repeat(value) statement
wait (condition) statement

The first four structures are identical to their counterparts in C. 

The "repeat" structure causes a statement to be executed a number of times.  This number 
does not have to be a constant, for example it could be the value of a global variable, 
however the number is only evaluated prior to starting the loop (as opposed to the "for" 
structure in C which evaluates the condition before each iteration).  The RCX allows 
"repeat" loops to be nested (maintaining its own "stack" of look counters).  The "break" 
statement is also legal within repeat loops, although it has a rather unexpected result 
when used within a nested loop.  Although the control flow will be broken, there is no 
way to pop the old counter off the internal stack, so the enclosing loop will be using the 
wrong counter when it starts repeating.  This cannot be reliably checked at compile time 
since the internal repeat stack is shared across subroutine calls.

The "wait" structure executes the body until the condition becomes true.  It is actually 
implemented as a macro (so be careful of side effects).  It it handy when waiting for 
something to happen:

wait(IN_3 == 1); // wait for sensor 3 to have value 1


Several other control flow statements are supported:

break;
continue;
return;

"break" and "continue" can be used within "do" and "while" loops.
"return" can be used to exit a subroutine before getting to its end.


Sensors

The constants IN_1, IN_2, and IN_3 can be used to either read an input sensor, or to 
specify a sensor.  Sensor types are defined by IN_SWITCH, IN_LIGHT, IN_ANGLE, 
IN_PULSE, and IN_EDGE.  The Sensor() function is used to initialize the sensor:

Sensor(value sensor, const type);

Some sensor types (such as the IN_ANGLE type) allow you to "reset" the sensors 
internal counter with the following command:

ClearSensor(value sensor);

The only supported value types for "sensor" in the above two examples are numeric 
constants and Input(n) - all other types have undefined behavior.

Here's a brief example that configures sensor 1 as a switch, then waits for it to be pressed 
before playing a sound.

Sensor(IN_1, IN_SWITCH);
wait (IN_1 == 1);
PlaySound(0);

The macros IN_1, IN_2, and IN_3 are equivalent to Input(0), Input(1), and Input(2) and 
can be used anywhere that a runtime value is legal (such as setting a variable, checking a 
condition, etc). 


Motors

The constants OUT_A, OUT_B, and OUT_C refer to the three outputs.  They can be 
combined (using + or |) in order to specify multiple outputs.  OUT_FULL is a constants 
that represents full speed for a motor.  Here's how to turn on all three motors for 1 
second:

	Fwd(OUT_A + OUT_B + OUT_C, OUT_FULL);
	Sleep(100);
	Off(OUT_A + OUT_B + OUT_C);

These commands control motors:

Fwd(const outs, value speed) // turn motor(s) on forwards
Rev(const outs, value speed) // turn motor(s) on backwards
Off(const outs) // stop motor(s)


Other Commands

PlaySound(const n); // play system sound #n
PlayNote(const freq, const duration); // play a note

Sleep(value v) // sleep for v ticks (100 ticks per second)

Display(value v) // set the dispaly mode as follows:

Note: display modes for "Display" are as follows: 0 = system clock, 1-3 = input channel, 
4-6 = output channel.  To show the value of the third input, use "Display(3)", not 
"Display(IN_3)".  The later command will read the value of sensor 3, and use that value 
as the argument to the display command (e.g. if the sensor's value is 0, then the system 
clock will be displayed).  Yeah, this is pretty stupid, but its how the RCX works.

SendMessage(value m);	// send low 8 bits of m as an IR message
ClearMessage();	// clear the last received IR message

ClearTimer(const n);	// clear timer #n


Data Logging

The RCX supports a Datalog feature.  Using this feature requires three steps:

1) Set the datalog size
2) Add data points to the datalog
3) Upload the datalog to a host

You can set the datalog size with the following command

SetDatalog(const n); // create datalog for n points

Point can be added with

Datalog(value v); // add v to the datalog

Only certain types of values may be added (variables, timers, sensor readings, the system 
clock).

Some (or all) of the datalog may be uploaded at any time as follows:

UploadDatalog(const index, count count);

"index" is the first data point to upload, and "count" is the number of points to upload.

Each entry in the datalog uses three bytes - the first byte is the type of entry,
the next two are a 16 bit value in little endian format.  If you set the size of
the datalog to N, then there are actually N+1 entries - the first one (at index 0)
is the number of data points collected so far (type = 0xff), while the remaining
ones (index 1 - N) are the points themselves.  Here are the type codes for normal
data points:

	0x00 - value of a variable
	0x20 - value of a timer
	0x40 - value of a sensor
	0x80 - value of the clock


Keywords

The following keywords are used in nqcc:

asm		if		stop
break		int		sub
continue	repeat	task
do		return	true
else		start		while
false


Grammar

program : unit_list
	;

unit_list: unit_list unit
	|
	;

unit 	: int id_list ;
	| task id block
	| sub id block
	;

id_list	: id_list , id
	| id
	;

block	: { stmt_list }
	;

stmt_list : stmt_list stmt
	|
	;

stmt 	: ;
	| block
	| asm  { bytes } ;
	| while ( condition ) stmt
	| do stmt while ( condition ) ;
	| repeat ( value ) stmt
	| break ;
	| continue ;
	| if ( condition ) stmt
	| if ( condition) stmt else stmt
	| id assign value ;
	| start id ;
	| stop id ;
	| id ( ) ;
	| return ;

bytes	: bytes , expr
	| expr
	;

value	: expr
	| id
	| @ expr
	;

assign	: = | += | -= | *= | /= 
	;

expr	: number
	| expr binop expr
	| - expr
	| ~ expr
	| ( expr )
	| [ value ]
	;

binop	: + | - | * | / | % | & | | | ^ | << | >> 
	;

condition : value relop value
	| ! condition
	| condition && condition
	| condition || condition
	| ( condition )
	| true
	| false
	;

relop	: == | != | < | > | <= | >=
	;


For Hackers Only

NQC contains very little in the way of RCX specific functions.  Support for controlling 
motors, reading sensors, etc. is provided in the rcx.nqh file.  This section explains some 
of the low level constructs used within these definitions.

Internally, many of the RCX operations use "values", which are essentially a form of 
typed data.  Each value has a "type" and a "index", which together specify a sort of 
effective address for the runtime value.   Of course, one of the types is "constant", in 
which case the effective value is equal to the index itself.  Other types include reading an 
input sensor or generating a random number.

The compiler can perform arithmetic operations only on compile time constants.  Certain 
statement (such as conditional clauses) require values, and constants are automatically 
promoted to values in these situations.

However, sometimes it is desireable to specify the type/index pair directly.  This can be 
done by using the @ operator, which converts a 24 bit number directly to a value.  The 
lower 16 bits are the index, the next 8 are the type code.  For example, since the type code 
for "constants" is 2, the following value is equal to 0x123:

@0x20123

The type code for variables is 0, so the following refers to variable 7:

@0x00007

This is very different from 0x00007, which equals the number 7.  It would've been much 
nicer if LEGO had made the type code for constants equal to 0, but we have to live with 
this hack.

Sometimes it is necessary to convert a value back into its "effective address".  This is 
done by enclosing the value in brackets:

[ Random(7) ]

The effective address is constant, thus can be manipulated with the compiler's arithmetic 
operators.

Finally, the "asm" statement allows most primitives to be implemented:

asm { data };

"data" is one or more numbers, separated by commas.  The asm statement emits the literal 
data contained within its braces.  The data bytes for an asm statement must be compile 
time contsatnts, not runtime values.
