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:- 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. - 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 $vars | Creates 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. |
RET | Removes the current Stackframe from the Procedure Stack and jumps to its return-address. |
LIT $value | Pushes 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. |
NOT | Negates 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. |
Pops and prints the top-most value from the Data Stack to the screen. | |
JMP $line | Jumps to code line $line. |
JFALSE $line | Pops 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) |
SUBSTR | Takes a string, a start index and a substring-length of the stack to perform a substring operation. Puts the result on the stack. |
STRCONCAT | Pops two strings of the stack and puts the concatenated string back onto it. |
END | Stops the machine. |
This page is maintained by @_seb