G-Pascal compiler specs and notes

Contents

Author: Nick Gammon (written in 2022)

Introduction

The G-Pascal compiler is a “tiny” Pascal compiler, entirely resident in the EEPROM, and available immediately after resetting your board.


Features


Limitations


How to enter and compile a program

  1. Use the Editor to enter your program (type “I” to insert text). Alternatively load it from your PC by using “LOAD”. The Editor is described here.

  2. Type “S” to syntax-check the code. This is slightly faster than attempting to compile it. This step is optional.

  3. When it is error-free type “C” to compile it and produce P-codes.

  4. If it compiles without errors type “R” to run the code. Alternatively type D to debug or T to trace it.

  5. If the code does not appear to be doing anything try typing Ctrl+C to abort it, Ctrl+T to trace it, or Ctrl+D to enter debugging mode. Alternatively press the NMI switch (if installed) to do a warm start, recovering control of the processor and retaining your source code.


Extensions and syntax notes


Reading and writing to the terminal

Read

The built-in statement READ (which is a reserved word) can read from your terminal in three ways:

Multiple variables can be read into (eg. READ (a, b, c)) however each one is treated exactly as if they appeared in separate READ statements, and thus to read three numbers (for example) you would have to enter them on three lines. An alternative would be to read into a CHAR array, and parse multiple numbers yourself.

To read without blocking use GETKEY which returns the current key as a number (eg. 65 for the letter ‘A’) or zero if no key is pressed. GETKEY marks the character as read, so if you need to check if a key is pressed and also find out what key that is, you would need to save it in a variable, for example:

var myKey : char;
begin
  write ("Press a key ...");
  repeat
    myKey := getkey;
  until myKey;
  writeln ("You entered ", myKey)
end .

WRITE and WRITELN

The built-in statement WRITE (which is a reserved word) can be used to write numbers, string literals, hex numbers or characters.

WRITELN behaves the same as WRITE except that it appends a newline character after writing its parameters. WRITELN may be used without any arguments in order to simply output a newline.

WRITE and WRITELN can take any number of parameters, these are just written one after the other without intervening spaces.

LCDWRITE

To write the the LCD display use LCDWRITE with the same arguments as WRITE (described above).


Calling machine code

You can call machine code (eg. that you produced with the assembler) by using call.

You can “seed” the A, X, Y and status registers prior to the call by using memc. For example:

memc [$10] := 22;  { A register }
memc [$11] := 33;  { X register }
memc [$12] := 44;  { Y register }
memc [$13] := %00000010;   { status register P: NV1BDIZC - see image below
                             - example sets Z flag }
call ($5000);      { call the machine-code function, which should end with an RTS }
                   { the values of A,X,Y,P after the call are now in the above addresses }

These addresses ($10 to $13) are “well-known” addresses used by G-Pascal. They will always be used for this purpose.


Accessing the VIA (Versatile Interface Adapater) pins

There are three inbuilt procedures and functions to control the 16 pins on the VIA chip, which operate similarly to the ones on the Arduino.

The VIA ports are PA0 to PA7 (pins 2 to 9 on the chip) and PB0 to PB7 (pins 10 to 17 on the chip). For the purposes of these functions they are numbered 0 to 15, where 0 is PA0, 1 is PA1, 8 is PB0 and 15 is PB7, and so on for the ones in-between.

Pins PA0 and PA1 on the VIA are used for the serial interface, as well as CB2 to detect the start bit for incoming serial data.

Pins PA5, PA6, PA7, PB4, PB5, PB6, PB7 are used for the LCD interface.

That leaves 7 pins for your own use (PA2, PA3, PA4, PB0, PB1, PB2, PB3).

Example:

pinMode (2, 1);  { set VIA PA2 (pin 4 on the chip) to output }
digitalWrite (2, 1);  { set PA2 to logic level 1 }
foo := digitalRead (3);  { read PA3 }

Syntax diagram

The compiler’s syntax diagram is here — check that if you find yourself getting error messages.

This was generated from the EBNF syntax here.


Differences to C

If you are used to C (or C++) you may find yourself getting a lot of error messages. Points to note are:

Things to be careful of


Compiler directives

Compiler directives are placed inside comments, with a “%” in front of them.

The % must directly follow the start of the comment.


Debugging

If your program seems to be producing strange results you can either Trace or Debug it.

Trace mode

If you select Trace instead of Run when you go to start execution, you will see something like this:

(0590)  00 d0 07 00
(0594)  14 3d 1c 00
(0595)  3d 1c 00 2c
(0598)  2c 00 20 f8
(059c)  80 37 00 f7
(059d)  37 00 f7 ff
(05a1)  2c 00 20 f8
(05a5)  2c 00 1d f8
(05a9)  04 32 00 20

This lists the address of the current P-code (pseudo-code) and then the P-code (first byte) and the following three bytes. Some P-codes are only one byte long (in the example above, at $0594 you can see that the next line is at $0595, so that particular P-code was only one byte.

You can see what lines of code the P-codes relate to by putting “{%L}” at the start of your source code. Then, when compiling, you will see something like this:

(058c)   27           while K < size do
(0598)   28           begin
(0598)   29             flags [k] := false;
(05a1)   30             k := k + prime
(05a9)   31           end ;

You can see from the above that we are clearly in the loop from lines 27 to 31, because the P-code addresses agree with the trace information.

Tip: If you add {%L} to your source, and recompile it, the P-code addresses detected in the trace may have changed because adding to the source file makes it longer, and thus the generated P-codes (which follow the source) will be at different addresses. Thus you may need to do a Trace again to make the addressess in the listing agree with the addresses in the Trace.

You can also start tracing in the middle of execution by typing Ctrl+T.

Debug mode

If you select Debug instead of Run when you go to start execution, you will see something like this:

(0537)  16 3d 18 00
 Stack: 5810 = 02 00 00 d0 07 00 d0 07 00
 Base:  5fff = 05 04 53 49 5a 45
(0538)  3d 18 00 2c
 Stack: 5813 = 01 00 00 d0 07 00 02 00 00
 Base:  5fff = 05 04 53 49 5a 45
(053b)  2c 00 1a f8
 Stack: 5816 = d0 07 00 02 00 00 07 00 00
 Base:  5fff = 05 04 53 49 5a 45
(053f)  81 37 00 f7
 Stack: 5813 = 02 00 00 d0 07 00 02 00 00
 Base:  5fff = 05 04 53 49 5a 45
(0540)  37 00 f7 ff
 Stack: 5810 = 01 00 00 02 00 00 d0 07 00
 Base:  5fff = 05 04 53 49 5a 45
(0544)  2c 00 1a f8
 Stack: 5816 = d0 07 00 02 00 00 07 00 00
 Base:  5fff = 05 04 53 49 5a 45

This gives you somewhat more verbose information. For each P-code address you see the P-code itself on the right. On the first line the P-code is $16 (Greater or Equal) followed by $3d on the next line (Jump if zero). See here for the meanings of the P-code numbers.

The Stack, underneath, shows the top three values on the runtime stack. In the case of the first line (Greater or Equal), the top value is 02 00 00 and since this is a little-endian machine, that is the number 2. The next value is d0 07 00, which is therefore $0007d0, which is 2000 in decimal. This example is from the Eratosthenes Sieve prime number generator below, so clearly we are comparing to see if 2 < 2000 (or effectively, if 2000 >= 2). Clearly either of those comparisons is true (2 is less than 2000) so we see a $000001 (true) on the top of the stack underneath the P-code at $0538. Thus the “jump if zero” is not taken, and the loop continues.

The Base underneath that is rather complicated, and is explained in the compiler source code. Basically it is used to restore the stack when a function returns, and has provision for three two-byte addresses:

You can also start tracing in the middle of execution by typing Ctrl+D.

Stopping tracing/debugging/execution

You can stop tracing/debugging in the middle of execution by typing Ctrl+N.

You can abort execution by typing Ctrl+C, or by pressing the NMI switch if you installed it.


P-code meanings

A list of P-code meanings is here.

Many of the P-codes have no operands. That is, they operate on the top contents of the runtime stack. For example, to add two numbers the compiler would generate:

LIT $012345     <--- push the constant $12345
LIT $056789     <--- push the constant $56789
ADD             <--- add top two items on the stack (leaving the result on top of the stack)

Example code

{ Eratosthenes Sieve prime number generator }

{ Note: does not detect the number 2 }

const size = 2000;
      true = 1;
      false = 0;
      perline = 10;

var flags : array [size] of char;
    i, prime, k, count, online : integer;

begin
    count := 0;     { how many primes we found }
    writeln;

    online := 0;    { numbers we have shown on this line }

    for i := 0 to size do
      flags [i] := true;

    for i := 0 to size do
        if flags [i] then
        begin
          prime := i + i + 3;
          k := i + prime;
          while K < size do
          begin
            flags [k] := false;
            k := k + prime
          end;
          if online > perline then
          begin
            writeln;
            online := 0
          end;
          online := online + 1;
          count := count + 1;
          write (prime, " ")
        end;
      writeln;
      writeln (count, "  primes")
end.

Output:

Running

3 5 7 11 13 17 19 23 29 31 37
41 43 47 53 59 61 67 71 73 79 83
89 97 101 103 107 109 113 127 131 137 139
149 151 157 163 167 173 179 181 191 193 197
199 211 223 227 229 233 239 241 251 257 263
269 271 277 281 283 293 307 311 313 317 331
...
3089 3109 3119 3121 3137 3163 3167 3169 3181 3187 3191
3203 3209 3217 3221 3229 3251 3253 3257 3259 3271 3299
3301 3307 3313 3319 3323 3329 3331 3343 3347 3359 3361
3371 3373 3389 3391 3407 3413 3433 3449 3457 3461 3463
3467 3469 3491 3499 3511 3517 3527 3529 3533 3539 3541
3547 3557 3559 3571 3581 3583 3593 3607 3613 3617 3623
3631 3637 3643 3659 3671 3673 3677 3691 3697 3701 3709
3719 3727 3733 3739 3761 3767 3769 3779 3793 3797 3803
3821 3823 3833 3847 3851 3853 3863 3877 3881 3889 3907
3911 3917 3919 3923 3929 3931 3943 3947 3967 3989 4001
4003
551  primes

Reserved words

The following words are reserved by the compiler. They may not be used as identifiers (variables, constants, function or procedure names) in the compiler.

address, and, array, begin, call, case, char, chr, const,
div, do, downto, else, end, for, function, hex, if, integer,
mem, memc, mod, not, of, or, procedure, read, repeat, shl,
shr, then, to, until, var, while, write, writeln, xor

Memory layout


Credits



License

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.