LavishScript:Data Sequences

From Lavish Software Wiki
Jump to navigation Jump to search


There are two basic operations for any kind of data: read and write. In LavishScript, reading is always done via data sequences, which are then converted to a string for output or use in a Command. For example, "${Time}" will get converted to something like "12:45:00". Writing to data can be done with special Commands or with data sequences.

First we will explain each of the concepts and throw some completed data sequences at you. Afterward, you will learn how to form your own data sequences (you can also see the Syntax page for complete syntax rules).


In LavishScript, anything that stores data is considered an "object". Most objects are also associated with other objects. For example, your arm is associated with your body, your hand, your shoulder, and so on. Your arm also has certain properties -- the color of your skin, the size of your muscles, etc. Each of these data are considered objects in LavishScript. Some objects are only relevant when a "parent" object is given -- in other words, if I were to ask how big an arm is, you might say "whos arm?", because there's all kinds of arms and that's just too generic. In LavishScript, you would literally ask something more along the lines of "Me, get me Right Arm. Right Arm, get me Length", and each of those is specific enough to refer to a single object. Here is what it might look like as a LavishScript data sequence: ${Me.RightArm.Length}.

Viewing Objects

Objects have a clearly defined list of properties, and this list will be exactly the same for all objects of a certain type. For example, colors can be described by hue, saturation and brightness. A combination of these three values can describe any given color, but the values for different colors are not necessarily the same. Because of this, we can "view" each color the same way. Likewise, objects in games can be viewed the same way as other objects. A player is a player and a piece of equipment is a piece of equipment, but not every two players or pieces of equipment will have the same values. This makes it very easy to work with objects, because we can work with types of objects in a uniform way, rather than each individual object having its own list of properties. The type of object, combined with a list of properties and a list of potential ways to manipulate this type of object, forms what we call a "Data Type".

Additionally, each type of object has a specific way of "answering your question", so to speak. The question "What is the length of my right arm?" will be some number. This number may have some unit of measurement (e.g. inches or centimeters), but it will always be the same unit of measurement for the length of an arm. Sometimes the object doesn't define an answer. The question "What is my right arm?" might be met with a dumbfounded look, or a sarcastic answer. Or in LavishScript's case, it may be NULL, meaning it simply does not have an answer for you. The name for this is called the "return value", or "To String". This can be misleading, though, because there is also a type of object called "string" -- it's not giving you an object, this is simply the name for what happens when you move away from the data sequence, and into the real world.


To retrieve another object given a starting point, such as an object representing your Right Arm given Me, you use a "member" of the type of object you presently have. In the earlier example, RightArm is a member of whatever type "Me" happens to be -- let's call it "body". So we have a body type, which has a RightArm member. In discussions, we can refer to the member as body.RightArm, because this applies to all body objects, not just Me. When we access RightArm, we're getting another object, which may be of another type. We'll call it "bodypart". We would access the object in a data sequence like this: ${Me.RightArm}. Then we are left with a "bodypart" object, which is then turned into text because we hit a }.

We could, however, continue the data sequence, because we have this object and can continue until we have what we're really looking for. ${Me.RightArm.Length} will get the length of my right arm. Some people don't have a right arm. What happens then is the data sequence ends when we try to get RightArm, before Length is even considered. Instead of giving a "bodytype" object in this case, we simply skip to the } and the result is NULL -- which is taken to mean "no object". The same is true in any data sequence -- the moment something doesn't exist, we have no object, and the result is NULL. As long we do have an object, the sequence continues until we hit a }, at which point the final object is converted to text.

Members are allowed to have "indices", which can be used to select from a group of possible results. Indices are not limited to only numbers; they are the equivalent of parameters to a command.


To perform an action on a given object, or using a given object, we use a "method" of the type of object. This could be literally anything that involves the object. Methods, in contrast to members, will never result in a new object. It will always be the original. This means that methods can be chained together in a data sequence, and you can even follow it up by accessing members.

Methods are allowed to have "indices", which can be used as parameters to an action. Indices are not limited to only numbers; they are the equivalent of parameters to a command.


Forming a basic data sequence

The simplest form of data sequence is simply accessing an object, and converting it to text. This is done by enclosing the name of the object in the special data sequence characters, like so: ${Me}. Any "Top-Level Object" or "Variable" is fair game.


Some objects themselves have indices, which are generally used to select from a set of possible results. For example, the If Top-Level Object actually uses two to three indices, and results in a string object depending on the value of the first dimension (or "parameter" depending on the context): ${If[1>2,"1 is greater than 2","2 is greater than 1"]}. As demonstrated, indices are enclosed with brackets [] directly following the object name, and are separated with a comma.

Members and methods can also have indices.

Accessing Members

The next logical step is to access a member of an object, as defined by the object's type. This is done by placing a . after the object, followed by the name of the member. This will result in yet another object -- remember, until you hit the }, there is an object! ${If[1>2,"correct","incorrect"].Length} gets the Length of the string object given by If. ${System.OS} gets the OS member of the system type, from the System object. And of course, for every new object you have, you can access another member in the same way. ${System.OS.Length} will get the length of the string object given by the OS member.

Accessing Methods

Methods are accessed much like members, just with a : instead of a .. ${Int[123]:Inc} creates an int object using the Int Top-Level Object, and accesses its Inc method, which increments the value (giving a final result of 124, in this case). Methods, again, do not give you an entire new object. As with members, every time you have an object you can access another member or method. ${Int[123]:Inc.Hex} will give a final result of 7c (the number 124 in hexadecimal, which is base 16).

The result of a method is always the original object (even if its value has changed). The exception to this is if the method call failed (returned false, to extension authors). This allows you to test for the failure of a method call by checking ${Object:Method(exists)}.


Because we have a well-defined beginning (${) and end (}) to a data sequence, we can use data sequences inside data sequences, anywhere we please -- in indices or even for the result to be used as names of objects, members, methods, etc. ${${If[1>2,correct,incorrect]}} will try to access an object called "incorrect". It's literally the same as ${incorrect}, because the data sequences are resolved from the inside to the outside, and the resulting text is used directly where the inner sequence is located.

Type Casting

Type casting is the act of changing the "view" of an object -- temporarily looking at an object as if it were a different type of object. Type casting is generally not recommended, except for some special cases. It is important to understand that using type casting incorrectly can result in program crashes and other undesirable results. However, there are some simple cases where type casting is simply the best way to get the job done. For example, if you need to determine if your data sequence results in a valid object, type casting to the exists type is the best and most accurate way. To change your view of an object, place the name of the type you wish to use in parentheses, like so: ${Me.RightArm(exists)}.

The problem with type casting is that some object types are incompatible. If I had a "bodypart" type, and tried to cast to a "body" type, that would produce entirely undesirable results. It is always safe, however, to cast to an "atomic" or immediate value type. These types are byte, float, int, bool, exists, and type. "type" is actually a special type that works on the object type itself, rather than the object. Casting to "type" will let you retrieve the name of the object type, for example (and other details about the type, if you wish). ${Me.RightArm(type)} would result in "bodypart". Object types that are not immediate values (such as the string type or a type representing a character in a game) can be cast to int to retrieve the address of the actual memory location, and can be accessed with the LavishScript "ptr" types. ${Me(int):Inc[4](intptr)} will actually get the 32-bit integer at the memory location 4 bytes after the start of the actual memory location storing Me. ${System.OS(int):Inc[1](byteptr)} will actually get the byte value at the memory location 1 byte after the start of the actual memory location storing the name of your operating system. Scary? Good, this is an advanced topic.

See Also