Syntax is like the grammar of code. Different bodies are delimited (separated) by curly braces, while individual statements are delimited by semi-colons. A statement can sometimes be further broken down into expressions. Since the Interpreter is for code already inside a method body, it does not recognize curly braces and only pays attention to semi-colons initially. Every operation has its own grammar, so syntax will be described as the topic approaches.
Whitespace is just blank area - like margins or spaces - that make it easier for users to read the code (and right now, this document). As in C#, the Interpreter will remove all whitespace prior to evaluating (the act of interpreting) an expression. The base rules of C# apply, mainly any amount of spacing is okay, just keep individual words together.
The first thing the C# Interpreter does is deliminate the statement based on tokens, see Table 2.
For more information on the operators, see Operators below. For more information on function calls, see Methods below. For more information on object members, see Access below.
Accessing objects in C# is a simple process, but there are rules. All objects in C# have access modifiers (public, private, protected, etc.). Depending on where the object is and where it is trying to be accessed from determines what access modifier is required to access it. For the Interpreter, all objects must have a public modifier in order to be accessed during evaluation of code. The Interpreter will attempt anything given to it, just be aware of this major limitation.
For accessing classes (what objects are made from), the Interpreter can only see classes it has been told of by the programmer. Depending on the type of class, accessing an object’s members (fields, properties, methods, etc.) goes in the form of <variable or object name> . <member name> for example, world.getblock. If the member is a method, parentheses are needed as well as required parameters – see Methods below. Members that are fields or properties can be assigned and retrieved like variables.
Variables hold and manipulate data, sometimes automatically. C# is a strongly typed language, meaning it treats different variables very differently, depending on what they are storing. A key difference of the Interpreter here is that it does not know if variables are different until they are used. Whether an operation works will depend simply on what the data is.
Declaring variables is similar in the Interpreter: bool failed = false;. “bool” declares the data type, “failed” is the name, the equals assigns the variable, “false” is the value, and the semi-colon ends the statement. The white space before and after the equals sign is optional, because it is an operator and not part of a word. The first difference is the data type does not matter; variables get their data types only when they are assigned – and they can even change. The second is declaring multiple variables at a time is only allowed if the variables are not assigned immediately, e.g. bool a, b;. Declaring variables during evaluation creates local variables that can be used for the rest of evaluation. After the code runs, all local variables are removed. For assigning with the “new” keyword, see Keywords below. Note that the blue coloring is just to indicate keywords. Variables cannot have the same name as keywords or classes. Table 3 shows system variables that are managed by the Interpreter.
Other variables may be given for specific purposes by the caller; make sure to know what they mean and what they are for. Note that the return is a variable and can be assigned (see Keywords below for more info, and it does not need the equals here).
To access variables in C#, use just the name to get a value, dot notation like myInt.ToString() to access members, or operators for math: int myInt = world.getblock(myCoords).id + 2 - otherInt;. For more on operators, see Operators below.
Methods in C# are functions that exist within an object. Functions run a series of statements and return when they are done. Optionally, they can return a value as well as take in parameters, which are variables or data sent to the function. As an example, if a variable named world has a method called getblock which takes in a parameter of the same type as another variable named myCoords, then one would call that method like world.getblock(myCoords). This chain can also continue since the returned value is also an object: world.getblock(myCoords).ToString().ToString(). The last ToString is redundant, this is just to show it can be done. It is always a good idea to pay close attention to method signatures (Microsoft Docs).
Some operators, keywords, or objects in C# allow the creation of functions within a method body. None of those are allowed in the C# Interpreter, particularly because in the C# Interpreter, code is already as portable as a string. Additionally, the intention is that any code written during runtime is temporary to begin with (else it could be saved with the source and compiled next time).
Method parameters can also have “out” and “ref” modifiers that allow the method to change the caller’s copy of the parameters directly. Unlike C#, the C# Interpreter does not want the modifiers when calling the method, so be aware of how the variables might change. The C# Interpreter also does not require parameters of those types be variables and will allow literals; the method just won’t be able to return anything that way. Another feature parameters in C# could have is a default value making the parameter optional; however, to date, the C# Interpreter requires that the parameter must be entered manually.
In C#, all math is conducted with operators. Table 4 is a complete list of operators the Interpreter recognizes and what they do, in order of precedence. These are the ones the Interpreter will recognize and evaluate exactly as C# would. Unary means an operation with one side.
See Microsoft Docs (C# operators) for more information. Operators separate words just like white space, so white space is optional when using operators. The ! and . unary operators come before the operand. Though the ++ and –– operators exclusively come after the operand, they return the value after performing the function, unlike in C#. Use the other operators exactly as in C#, just make sure they are in the above list. Also note that using three operators in a row (e.g. a *= -1), is not allowed, parentheses must be used (e.g. a *= (-1)).
In memory, data is just a series of ones and zeros, but behind the scenes it is managed by C# depending on whether the data is a number, a point, all the fields of an object, or just a Boolean yes or no. When determining data types, the Interpreter initially gets a string of characters, which are letters, numbers, symbols, etc., and it looks at what it could be. E.g. if it is the string “true” or “false” then it knows it is a Boolean value, and so on. When declaring specifically the data type, the C# Interpreter will likely get an alias, e.g. “long” instead of “Int64”, which it immediately converts to the internal value. And so, when the C# Interpreter displays the code, maybe in an error message, it will have slightly different words – they mean the same thing. Table 5 is a list of data types the C# Interpreter automatically knows about.
Any of these (except the Extension) can be easily found on the Microsoft Docs website. Note that the Interpreter does not automatically deal with casting data types to other data types, use the Convert class for that. Integers are numbers for counting, for instance there cannot be 3 and a half people, only whole numbers. For sizes of the numbers, a short can range -32,767 to 32,768, inclusive; int ranges -2,147,483,647 to 2,147,483,648; and long ranges -9223372036854775807 to 9223372036854775808, literally astronomical. Floats and doubles allow decimal numbers, but at a cost of precision; see reference for those (Value Types). Also, var automatically aliases to Object.
Strings can be a handful if one were to require a string within a string. Consider the example string msg = “Error on “start”!”, the C# Interpreter would have no idea where the string starts or ends. The fix is to have an escape character, generally it is the backslash: string msg = “Error on \“start\”!”. This way, the C# Interpreter knows which set of quotes is on the inside, and which is on the outside. The escape character needs to be escaped of itself when used in C# (and most other languages), so when stacking this, the number of backslashes doubles every time. In the C# Interpreter however, there is a major difference; the backslash is fine on its own, so the number of backslashes is simply the layer depth of the quotes. For example, “Outer most, \“Outer, \\”Inner, \\\“Inner most.\\\”\\”\””. In C#, the triple backslashes would’ve been quadruples. Since in some cases this is not practical or just hard to read, the C# Interpreter recognizes the code keyword; more on that below.
Keywords are special words that C# recognizes and sets apart for specific tasks or operations. Like in C#, the Interpreter will not allow variables to have the name of keywords. Below is a list of the keywords the Interpreter recognizes.
All the functionality from C# of the new and null keywords work in the C# Interpreter. For the code keyword, the above string example would look like this: string msg = code(Error on “start”!). It just returns everything on the inside of the parentheses as a string and ignores everything inside. If it were nested: code(Outer most, “Outer, code(Inner, \“Inner most.\”)”); alternating is fine as well. It may look like a function call, but it really is just a parenthetical expression. An important note is that if the code were to be handled outside the Interpreter, for example calling a method with a parameter having a layered string, the use of the escape character would change to that of the method. In other words, it can get very confusing even if well thought and researched. But since layered strings are confusing, the keyword was created to help alleviate some of that. Use the return keyword the same way as C# (e.g. return 7;), but note that control does not return immediately unless it is the last line of the section (which is recommended anyway).
For the conditionals, the structure is given as if(condition) {first code} else if(condition) {middle code} else {last code} If the first condition succeeds, the first code runs, and the other code is ignored. Else, the middle condition is checked and, on success, runs the middle code. Otherwise, the else ifs can repeat indefinitely until an else is reached. If all conditions fail and an else is reached, the last code is run and it ends. Note that there is no penalty for having an else without an if before it, but it will never run, so be aware of that.
For the loops, the structure is given as while(condition) {code} or for(initializer; condition; incrementor) {code} The while loop checks the condition, runs the code, and repeats until the condition is false. The for loop initializes, checks the condition, runs the code, runs the incrementor, and repeats without the initializer. Note that the if, while, and for structures do not need the curly braces if there is only a single line of code to run.
The C# Interpreter Extension, accessed with ext., is for all the important functionality the C# Interpreter doesn’t have. The Extension also can do some things in 5 lines whereas it would be a major section in the C# Interpreter. The Administrator might’ve included a secondary extension, so be aware of that. Below is a list of fields and methods the Extension has to offer.
For more information on the persistent variable data type, see Microsoft Docs (Dictionary Class). As a quick example, say a user wanted to add a custom formula that had to keep track of how many times it was used. This could easily be accomplished by entering if(!ext.persistent.ContainsKey("round")) ext.persistent.Add("round", 0); int round = ext.Pget("round") + 1; ext.Pset("round", round);. The first line checks persistent for a key (name of a value) named round, and if it does not exist (note the NOT operator) run the code that adds a starting value with the name round. The next line declares a variable named round and sets it equal to the value of round in persistent plus one. And finally, the last line sets the value of round in persistent to the variable round. The next time this code would run, round would already exist in persistent, so the first line would fail the condition and not continue. Try to give variables concrete descriptive names, that way variables are less likely to have duplicate names.
Following is a quick example of the Try method, that gets additional detail from an error. Note that the code is sent to the method as a string, use quotes or the code keyword. var e = ext.Try("danger..."); if(e != null) {ext.writeLine("[" + e.Message + "], [" + e.StackTrace + "]");}
For the snippet class, perhaps they may be thought of as functions. If called from within the C# Interpreter, it will have access to all the variables the calling code had, and the calling code will have access to the variables the snippet made or changed. For example, ext.persistent.Add("function_counter", new ext.snippet(code( if(!ext.persistent.ContainsKey("round")) ext.persistent.Add("round", 0; int round = ext.Pget("round") + 1; ext.Pset("round", round); )));. For convenience, the parentheses of the code keyword are in red. Then whenever this code snippet was wanted, accessing it would consist of ext.Pget("function_counter").update();. This would assign and increment the round variable, write it back, and then it could be used however wanted.
Arrays, or other collections, are the basis of many functions in programming. The C# Interpreter provides limited indexing with square brackets. Consider the integer collection arr which contains the set {1, 3, 7, 2}, if the user were to access the value “7”, it would be done with arr[2] – counting in arrays always begins with zero. To declare an array, use the new keyword and square brackets, like new int[4]. Assigning follows the same rules as accessing. Single dimensional non-integer indexed arrays and multidimensional integer arrays are supported. Arrays within arrays have limited support, use with care; place in temporary variables if needed. Multidimensional non-integer arrays are not supported and must be accessed via collection methods.
Next: Errors