^CircuitScriptV2 Reference

Room Link: https://rec.net/room/CircuitScriptV2


Syntax

CircuitScript V2 is based on the following context-free grammar:

/* Program is the root production rule */
/* Don't forget the "end" at the very bottom, or the compiler will never stop! */
Program ::=FunctionBlock
end
FunctionBlock ::=Declaration Code
/* Note that ALL declarations (including functions) have to be made before writing code! */
Declaration ::=Declaration Declaration | VariableDeclaration | ArrayDeclaration | FunctionDeclaration
/* Variables have to be initialized later in the Code block. */
VariableDeclaration ::=int Identifier | string Identifier
/* Array lengths have to be static numbers here. */
ArrayDeclaration ::=int[ Number ] Identifier | string[ Number ] Identifier
/* Parameters and return values are not supported. */
FunctionDeclaration ::=fun Identifier():
FunctionBlock
end
/* Whitespaces are generally ignored, where they do not serve as seperators to keywords. */
Code ::=Code Code | Assignment | FunctionCall | If | While | Print | return
Assignment ::=Identifier = Expression | Identifier[ Expression ] = Expression
FunctionCall ::=Identifier()
/* "else" is not supported. */
If ::=if BooleanExpression :
Code
end
While ::=while BooleanExpression :
Code
end
Print ::=print Expression
Expression ::=Number | "String" | Identifier[ Expression ] | Expression Operator Expression
/* The :: operator concatenates two strings. Standard operator precendence is supported. */
Operator ::=+ | - | * | / | % | ::
/* There is no dedicated boolean type. Non-0 ints are treated as "true" */
BooleanExpression ::=Expression | BooleanExpression BooleanOperator BooleanExpression | not BooleanExpression
BooleanOperator ::=== | != | < | <= | > | >= | and | or
/* These are the literal types used above */
Identifier ::=([a-Z]*[0-9]*)* /* Must not be a reserved keyword, and cannot be a parseable integer */
Number ::=(0-9)* /* Static number */
String ::=((?!").)* /* Any string that doesn't contain quotation marks */

Code Sample (Merge Sort)

A code sample might be easier to understand than the abstract syntax description above. This program can be loaded as an example in the room and shows most of the language features in action. It generates a list of 16 numbers and then performs a recursive Merge Sort.

⚠️ Note that CircuitsScript does not support comments. They have been added here to improve readability.

1// RandInt() inputs and outputs
2int rMin
3int rMax
4int r
5int rOut
6
7// The array size. Has to match the array declarations below.
8// Must be multiple of 8 to work with the PrintList() function
9int count
10
11// The array to sort and a temporary working copy.
12int[16] num
13int[16] numTmp
14
15// Parameters for the MergeSort() function
16int mStart
17int mEnd
18
19// Pseudo-random number based on a Linear congruential generator.
20// Uses r as a seed, which is effectively a random value from memory,
21// because it was never initialized.
22fun RandInt():
23  int range
24  range = rMax - rMin
25  r = r * 17569 + 1428631
26  rOut = r
27
28  if rOut < 0:
29    rOut = 0 - rOut
30  end
31
32  rOut = rOut % range
33  rOut = rOut + rMin
34end
35
36// Fills the list with random numbers
37fun GenerateList():
38  int c
39
40  rMin = -9
41  rMax = 100
42  c = 0
43
44  while c < count:
45    RandInt()
46    num[c] = rOut
47    c = c - 1
48  end
49end
50
51// Prints the list in rows of 8 values.
52fun PrintList():
53  int c
54  int c2
55  int limit
56  string cs
57
58  limit = count + 7
59  limit = limit / 8
60
61  c = 0
62  while c < limit:
63    c2 = 0
64    cs = ""
65    while c2 < 8:
66      cs = cs :: num[8 * c + c2] :: " "
67      c2 = c2 + 1
68    end
69
70    print cs
71    c = c + 1
72  end
73end
74
75// Performs a MergeSort from mStart to mEnd
76fun MergeSort():
77  int start
78  int bound
79  int i
80  int m
81  int j
82  int n
83
84  // Merges the two sorted array-halfs into the temporary array numTmp
85  fun MergeToTmp():
86    int c
87    int takeRightValue
88
89    c = start
90    while c < bound:
91      takeRightValue = 0
92
93      if i < m and j < n:
94        if num[i] > num[j]:
95          takeRightValue = 1
96        end
97      end
98
99      if i >= m:
100        takeRightValue = 1
101      end
102
103      if not takeRightValue:
104        numTmp[c] = num[i]
105        i = i+1
106      end
107
108      if takeRightValue:
109        numTmp[c] = num[j]
110        j = j+1
111      end
112
113
114      c = c+1
115    end
116  end
117
118  // Copies the fully merged array from numTmp back into the actual array
119  fun CopyFromTmp():
120    int c
121
122    c = start
123    while c < bound:
124      num[c] = numTmp[c]
125      c = c+1
126    end
127  end
128
129  // Recursive iteration stops, when array only has one element. 
130  // Then the array is already sorted.
131  if mEnd-mStart <= 1:
132    return
133  end
134
135  // Start by copying the global parameters into local function variables, 
136  // so they do not get overwritten when calling MergeSort() recursively.
137  start = mStart
138  bound = mEnd
139
140  // Calculate the range for the left [i,m) and right [j,n) merge.
141  i = mStart
142  m = mEnd - mStart
143  m = m / 2 + mStart
144  j = m
145  n = mEnd
146
147  // Perform a merge-sort on the left half
148  mStart = i
149  mEnd = m
150  MergeSort()
151
152  // Perform a merge-sort on the right half
153  mStart = m
154  mEnd = n
155  MergeSort()
156
157  print "Merging "::start::" to "::bound
158
159  // Merge the two halfs, until the range is sorted.
160  MergeToTmp()
161  CopyFromTmp()
162
163end
164
165count = 16
166
167print "Generating random list..."
168GenerateList()
169PrintList()
170
171print "Sorting list..."
172mStart = 0
173mEnd = count
174MergeSort()
175
176print "Sorted list:"
177PrintList()
178
179end

Machine Instructions

The machine instructions can be found on the "Compiled" view and are what CircuitScript is compiled into for execution. To understand the instruction table, it is good to know that the virtual machine operates using two separate stacks:
  1. The Procedure Stack holds the Stackframes of each function-instance. A Stackframe contains all relevant runtime-information of a function. For example it contains the variable values of the function's scope, but also other information like the return address to continue at when the function returns. Each Stackframe also holds a reference to the Stackframe of the function one function "level" above. This is not necessarily the caller, but is given at compile time by the code structure. This level difference is used to implement visibility of variables across nested functions.

    The root level is also treated as a function for consistency. That's why each compiled program starts with a CALL instruction.

  2. The Data Stack is used for communicating data between most instructions, but only temporarily. It is always cleared after a CircuitScript statement completes. While this is not necessarily the most efficient way to move data, it allows the compiler to work without a lot of special cases for each combination of instructions, and each statement translation remains self-contained.


Note that due to technical limitations the arguments in-game are written to the lines following the instruction. Here for example the numbers "9 0 0" are the arguments to the "CALL" instruction:


While debugging, the Debug Output will show what line and which instructions are currently executing. Here the PRINT instruction is just about to be executed with the "Hello World!" value on top of the data stack:


Full list of instructions:
CALL $line $level $varsCreates a new Stackframe on the Procedure Stack with storage for $vars variable values and jumps to code line $line after saving the current line as the return address of the Stackframe.
$level denotes the relative function level difference to the called function and will be dynamically resolved into a static link pointer to the corresponding Stackframe on the stack.
RETRemoves the current Stackframe from the Procedure Stack and jumps to its return-address.
LIT $valuePushes the static $value onto the Data Stack.
INTADD
INTSUB
INTMUL
INTDIV
INTMOD
INTLT
INTGT
INTLEQ
INTGEQ
INTEQ
INTNEQ
Pops two values of the stack and performs the according integer operation. Pushes the result back on the stack.
NOTNegates the top-most value on the stack. (true is equivalent to a non-zero integer)
AND
OR
Pops two values of the Data Stack and performs the according boolean operation (true is equivalent to a non-zero integer). Pushes the result back on the stack.
PRINTPops and prints the top-most value from the Data Stack to the screen.
JMP $lineJumps to code line $line.
JFALSE $linePops the top-most value from the stack and jumps to code line $line, if it is a non-zero integer. Otherwise proceeds to the next instruction.
LOAD $level $index
ILOAD $level $index
Loads a variable onto the Data Stack. The value is taken from the Stackframe $level scopes above the current function at variable index $index. The variable index corresponds to the order of declaration in the original code. The indirect version (ISTORE) reads another value from the Data Stack and adds it as an additional offset to the index. This is used when loading an array value at a specific index.
STORE $level $index
ISTORE $level $index
Pops a value of the Data Stack and stores it in a variable. (see LOAD instruction above for more information)
SUBSTRTakes a string, a start index and a substring-length of the stack to perform a substring operation. Puts the result on the stack.
STRCONCATPops two strings of the stack and puts the concatenated string back onto it.
ENDStops the machine.

This page is maintained by @_seb