CIRCUIT is an interpreted programming language with 3 general-purpose registers (Namely X, Y and Z), 1 special ternary register called the Active Register Counter (or "ARC") and 6 instructions.
But one of it's main traits that makes it unique and novel is that all of it's instructions are nullary, meaning that none of them take any argument. While the usability of such a design may sound very limited at first, it's actually possible to implement a lot of common CISC machine instructions (Such as greatest common divisor (GCD), square of a number, multiplication of 2 numbers, maximum and minimum, etc) despite the small number of it's default built-in registers and (nullary) instructions.
CIRCUIT is an acronym for "Circular Iterative Table". Interestingly, it also starts with 'C', the name of the programming language its written in.
As demonstrated in the above image, the registers are structured similarly to a circular table (Circular array or index) with 3 seats (Registers) around it, and as the clockwise arrow shows, each register is accessed once every 3 cycles, in other words we always Iterate through the registers on the Table in a Circular manner.
ARC is a similar acronym that stands for "Active Register Counter".
An arc usually refers to a portion of a circle, and as its once again obvious in the image, the arrow is pointing to one of the three Sectors of the circle all the time.
CYCLE
CIRCUIT instead of having multiple instructions like X_INC, Y_INC and Z_INC to interact with different registers, or taking the register's index/name as an operand for said instructions (INC 0 or INC X for example), uses a special ternary register known as the Active Register Counter ("ARC") with 3 states (0, 1 or 2) that always points to X (0) at start; and each time an instruction (Including NOP) is executed, ARC increases by 1. (from 0 up to a maximum of 2, if it's already 2 then it simply folds back to 0, therefore cycling through X, Y and Z every 3 instructions.)
To further simplify, if you think of CIRCUIT as a clock, ARC is the clock hand.
CIRCUIT's syntax is similar to Assembly save for the lack of operands and arguments, additionally subroutines (Like Labels in Assembly) have an important role and are more of a necessity and much more verastile, with certain tasks being impossible to accomplish without using them.
The following program consisting of 3 subroutines that is used to find the maximum between 2 numbers and store it in the third register, covers the majority of what you need to know regarding the syntax to get started:
(This example is also available in the ./examples folder as "max.cit" with more details and consistency.)
; Lines starting with semicolons (";") are comment lines ignored by the interpreter.
C1:
NXT
NOP
NOP
DEC
NOP
INC C2:NOP EXT
NOP
NOP DEC INC
C0: NXT NXT NOP DEC DEC INC ; Inline comments are also allowed.
All 3 subroutines are valid, you could either write the instructions in a sequence separated by one or more whitespaces (" ") like in C0, below eachother like in C1, or as a combination of both as seen in C2.
The subroutines Cn are defined just like labels in Assembly and C. Also the order in which they are defined does not matter, for example C1 could be defined before C0, (TODO) likewise you could not define C1 and jump to C3 and C4 while skipping C2. A subroutine can only be defined once. (i.e. you cannot have two C2's.)
You can have one or more, up to a maximum of (TODO: max number) subroutines. C0 is always the first subroutine to get executed.
There are 3 "Control" instructions (NXT, PRV and EXT) that can be used to control the flow of the program.
Switching between subroutines is possible by using the NXT, PRV control instructions, these could appear anywhere in a subroutine and if the required conditions (Explained in the instructions map) are met then the switch to the next C(n+1) or previous C(n-1) subroutine happens, even if they appear in the middle or at the start of a subroutine and the instructions following them have not been executed yet.
EXT is the last control instruction that is nearly the same as as the above ones regarding it's placement and required conditions, except that it entirely exits the program without executing any other subroutine or instruction.
However, there are 3 special cases to NXT and PRV that are discussed in the Instruction Set section.
When the interpreter reaches the end of a subroutine and there aren't any control instructions and/or the required conditions arent met then it loops (Resumes execution) back to the beginning of the said subroutine.
CIRCUIT has 6 instructions that are listed below: (Internal Opcodes are not directly accessible to the user. They are executed by CIRCUIT's Virtual Machine after it's JIT compiler.)
Internal Opcode | Instruction Mnemonic | Description |
---|---|---|
0000 | NOP | No operation; does nothing. (Could be used to safely select the proceeding register as the active register without any other side effects.) |
0001 | EXT | Exit; if the value of the active register equals 0, then this exits the program entirely without loading any other subtask; otherwise nothing happens and this behaves the same as "NOP". |
0010 | NXT | Next; if the value of the active register equals 0, then this ends the current Cn subtask and switches to the first line of the next C(n+1) subtask; otherwise nothing happens and this behaves the same as "NOP".[*] |
0011 | PRV | Previous; if the value of the active register equals 0, then this ends the current Cn subtask and switches to the previous C(n-1) subtask; otherwise nothing happens and this behaves the same as "NOP".[*] |
0100 | INC | Increment; increaes the value of the active register by 1. |
0101 | DEC | Decrement; decreases the value of the active register by 1. |
[0110, 1111] | [Reserved] | These currently act the same "NOP". |
1) The first subroutine (C0) having a PRV instruction.
2) The last subroutine (Cn) having a NXT instruction.
3) There is only a single subroutine and it has a PRV or NXT.
In the first two cases a fold happens; in the first case the execution resumes at Cn (The last subroutine), and in the second case at C0 (the first subroutine).
The third case is exactly the same as the other cases, but because the first and last subroutine are the same (As there is only a single subroutine), it just keeps looping back to itself. (Unless there is an EXT and the required conditions are met)
C1: DEC NOP INC NXT
C2: EXT DEC