Difference between revisions of "Cubescript"
(→substitution) |
(delayed evaluation and early termination of logical statements) |
||
Line 147: | Line 147: | ||
stuff | stuff | ||
] | ] | ||
+ | </code> | ||
+ | |||
+ | Also on the subject of if statements, logical operators like && and || in fact execute its arguments iteratively. This allows us to make use of [] to delay evaluation of conditionals. As we learned in the () section, when an instance is encountered it is immediately executed and substituted with its result. This method also allows us to terminate these logical checks early if some exit condition is met (ie, a false result is encountered with &&) without evaluating all the conditions) | ||
+ | |||
+ | <code> | ||
+ | var1 = 2 | ||
+ | var2 = 3 | ||
+ | var3 = 10 | ||
+ | |||
+ | //a traditional if with multiple conditions | ||
+ | if (&& (= $var1 2) (>= $var2 4) (& $var3 2)) [] [] | ||
+ | |||
+ | //This is in fact interpreted as follows; since () denotes immediate substitution | ||
+ | //While early termination does work, all of the conditionals are evaluated, rendering it somewhat moot. | ||
+ | if (&& 1 0 2) [] [] | ||
+ | |||
+ | //We can rewrite this to make proper use of delayed evaluation and early termination as follows | ||
+ | //While this example doesn't show it, like C this is very useful if you need an earlier condition to be true | ||
+ | //for subsequent conditions; ie C: if(ptr && ptr->val) {} which terminates early if ptr is NULL. | ||
+ | if (&& [= $var1 2] [>= $var2 4] [& $var3 2]) [] [] | ||
</code> | </code> | ||
Latest revision as of 05:15, 6 August 2012
Cubescript is the scripting language utilised within the sauerbraten/cube2 engine. it is used to set aliases, variables, create binds and generate the menu.
Writing in Cubescript
Aliases
The brunt of cubescript is concerned with Aliases. Aliases have 3 basic uses.
- store lists and values for extended periods of time
- store temporary lists and values
- grouping a set of commands which are useful when combined
There are two ways to declare an alias. You can do it via infix notation, or by using the alias command. It should be noted that aliases are the exception to the rule, infix notation is only available for creating aliases.
A VERY important note, alias specific operations are available to aliases only. this means builtin variables can't be set or overridden through alias, =, pop and push (among others).
//command alias myalias [1] //infix variant myalias = [1]
executing aliases
To execute an alias, you execute it like any other command. if I had an alias named myalias, I would simply invoke '/myalias to execute it.
These aliases also have access to a class of special aliases. Those are the temporaries in which arguments are stored. There's also a useful variable named 'numargs' which tell you how many arguments were provided. The aliases containing the arguments are named arg. they are numbered and enumerated, starting from 1, so you'd use $arg1 to access the first argument. This does not start at 0, since arg0 is the command's name (arg0 is also not defined.. ever, nor included in numargs).
//prints all arguments to screen, one by one //see the other comments for an explanation print = [ //declares an alias named print loop i $numargs [ //loops $numargs times, setting i to the current iteration echo (getalias (concatword arg (+ $i 1)) //gets the value of the arguments, by accessing $arg1 $arg2 $arg3.... ] //close loop ] //close print alias print I like unicorns //should print I like unicorns on 3 separate lines
push and pop
Push and pop used to be two separate instructions, but since the advent of the new Cubescrpt interpreter/compiler (2.6.1), they have been merged into a single instruction. The syntax for this singular stack manipulation command is as follows.
push variable value body
The stack is automatically popped after execution
myalias = "candy" push myalias "lollipops" [ echo $myalias ] echo $myalias //expected output //lollipops //candy
Syntax
Cubescript is not your typical scripting language. Cubescript is not the most flexible, or the best language, but it more than adequate for our needs and purposes. Cubescript draws its main inspiration from quakescript and from Lisp. It is therefor said that Cubescript contains many "Lispisms". Below is a summary of notable features/quirks.
- comments are denoted by //
- both newlines and ; denote the end of a call.
- newlines will automatically close of any strings you may have forgotten to. (you should not rely on this)
- substitutions are done through the use of $ and @ tokens, (See below)
- everything is a function which may take arguments. With the exception of setting an alias, infix notation does not exist
- there are no arrays or vectors, only lists.
- most implementations have a 25 word limit. This means commands can be invoked with up to 24 arguments
one liner example
alias hi [say "hi"; sleep 5000 [say "Hi again"]]
multi liner example
alias hi [ say "hi" sleep 5000 [ say "Hi again" ] ]
it's obvious what the above code does, it'll make your character say hi, and wait 5 seconds before saying hi again.
indenting is done in levels, the Tabulator (TAB) key is usually used to indent the text. while not necessary, it tends to make it bigger, and more legible as seen in the above example (when you get to big stuff, you'll thank yourself for indenting it.
As a rule of thumb, pretty much only [ increases the indentation level. Also note, ", ] and ) can close each other, so make sure you close them off properly
containers
Containers in cubescript are of 3 specific types, "", () and [] - all of these have significant differences in operation and use
"" container
"" containers are used to store things EXACTLY as they are written, with a few small exceptions - in-text commands which are denoted by a preceding ^. They are as follows
- ^f#
- ^f# is used to colour text. # must be replaced by either a number or a capital letter (A-Z). r and s are special control characters which will restore and save the text's current colour respectively.
- ^n
- insert a '\n' character (newline/linefeed)
- ^t
- inserts a '\t' character (tabulator)
- ^"
- inserts a quote (")
() container
() containers are used mainly for logic/arithmetic operations. They are usually nested inside [] blocks, and unless they are nested inside []'s they're permanently substituted with their results.
For example - the following will turn into an infinite loop. The condition is initially evaluated and substituted with one and since 1 can never = 0, it's true forever more
i = 0 while (= $i 0) [i = (+ $i 1)]
the following code on the otherhand is devoid of such issues. Being nested inside the [] means it's not substituted and evaluated each cycle
i = 0 while [= $i 0] [i = (+ $i 1)]
[] container
This container is used for nesting, deferring execution of it's contents, deferring calculations and substitutions of () containers, and substitutions of aliases/variables written with a @ prefix (note several levels are needed to achieve this). [] containers are also the ONLY container capable of spanning multiple lines.
This is the most used container and due to it's properties, is ideal for creating aliases.
generally statements in cubescript can't span multiple lines. If you should use this container and occupy several, the execution of the prior command continues exactly where this container finished off. This can be demonstrated with an if statement.
if 1 [ do stuff ] [ do other stuff ]
Also on the subject of if statements, logical operators like && and || in fact execute its arguments iteratively. This allows us to make use of [] to delay evaluation of conditionals. As we learned in the () section, when an instance is encountered it is immediately executed and substituted with its result. This method also allows us to terminate these logical checks early if some exit condition is met (ie, a false result is encountered with &&) without evaluating all the conditions)
var1 = 2 var2 = 3 var3 = 10 //a traditional if with multiple conditions if (&& (= $var1 2) (>= $var2 4) (& $var3 2)) [] [] //This is in fact interpreted as follows; since () denotes immediate substitution //While early termination does work, all of the conditionals are evaluated, rendering it somewhat moot. if (&& 1 0 2) [] [] //We can rewrite this to make proper use of delayed evaluation and early termination as follows //While this example doesn't show it, like C this is very useful if you need an earlier condition to be true //for subsequent conditions; ie C: if(ptr && ptr->val) {} which terminates early if ptr is NULL. if (&& [= $var1 2] [>= $var2 4] [& $var3 2]) [] []
substitution
there are two principle ways of substituting values in cubescript, the first is through the use of $ tokens, the second is through the use of @ tokens.
a very simple example:
myalias = 5 echo $myalias
myalias is substituted into the echo statement, so you should see 5 printed to the top-left of the screen
using the @ token would have also resulted in the same behaviour
myalias = 5 echo @myalias
There's a big difference between the two, the @ token can be used to concatenate stuff together, $ can't. @ is also substituted immediately up to the first level of nesting. (this can be incremented by using additional @ tokens, ie @@ for 2 levels of nesting). Do take note that too few @ tokens may cause a substitution to happen too late, and too many may likewise cause it to happen too early or cause other weird and subtle issues.
An example of use in a level 1 nest
myalias = "I am awesome" mymenu = [guitext @myalias]
if you were to type /echo $mymenu, it should read: guitext "I am awesome"; using the $ token on the other hand
myalias = "I am awesome" mymenu = [guitext $myalias]
typing /echo $mymenu should produce: guitext $myalias
An example of concatenation
as i mentioned, @ also allows you to concatenate stuff together, which could result in shorter and simpler scripts. It should be noted that this can also result in extreme obfuscation
myalias0 = 5 myalias1 = 3.1415 myalias2 = 2.481 loop i 3 [ do [ echo $myalias@i ]]
this should print 5, 3.1415 and 2.481 respectively
the purely $ token equivalent
myalias0 = 5 myalias1 = 3.1415 myalias2 = 2.481 loop i 3 [ echo (getalias (concatword myalias $i)) ]
Lookup of a Lookup
There are two ways to look up multiple things; the first is by encasing it inside 'getalias', the second involves using multiple lookup tokens. There is a caveat with the second method, while it works on more types, it's only available in sandbox as of 2.6.1.
All 3 of the below examples should print "victory!"
alias1 = "alias2" alias2 = "alias3" alias3 = "victory!" //the first method echo (getalias (getalias $alias1)) //the second method; 2.6.1 echo $$$alias1 //to show compatibility with @ do [ echo $$@alias1 ]
Branching/Decisions
Decisions are based on 6 key commands, if, ?, cond, case (integer), casef (float) and cases (string). There are also other ways to do this. For example you might be able to easily query something about an object, where it's obviously unique. You would then execute an alias whose name has this unique bit attached onto the end. This is an advanced method and is generally not recommended.
if and ?
Arguments: Cond True False
Unlike most other languages, cubescript's if contains an implicit else, in short we do not have an else statement. The provided condition is true if it ends up as a series of alpha numerics, or in the case of a number, as a value other than 0.
myalias1 = 5 myalias2 = "woot" //if else-if else if $myalias1 [ echo "myalias1 is true" ] [ //this shows the alpha numerics case if $myalias2 [ echo "myalias1 is false and myalias2 is true" ] [ echo "myalias1 is false and myalias2 is false" ] ]
? acts exactly the same way, but there's one HUGE difference. ? does not execute the chosen result.
//an example of where ? can be used where if would fail items = 1 echo (format "You are carrying %1 item%2." $items (? (= $items 1) "" "s"))
cond
basically you provided a several pairs of arguments, the first half of the pair is the condition, and the second is the result. To put it in a programming perspective, this is like creating a long line of if else's together. Execution stops when the first condition is matched. Should you place several conditions where more than one may be true at any one time, only the first will be executed.
The following example assumes you have a light entity selected
lightstrength = [ cond [< (ea 0) 0] [ echo "It's like a black hole!" ] [= (ea 0) 0] [ echo "Blindlier than science!" ] [< (ea 0) 8] [ echo "It's weaker than Billy's night light!" ] [< (ea 0) 32] [ echo "Nice night light you got there" ] [< (ea 0) 96] [ echo "It's like a lamp, but it's not a lamp" ] [< (ea 0) 512] [ echo "By Jove! I can actually see the ground!" ] [>= (ea 0) 512] [ echo "Dark places are icky, don't you agree?" ] ]
case
This works similar to switches in most programming languages. The biggest difference is that you can't break out, nor evaluate multiple cases (aka, fallthroughs).
The first argument tells it which variable to use. The following arguments are given in pairs, the first half is the state, the second is the result. If the state is a null type, it is considered the default case. The easiest way to generate a null type is to use ()
The following example requires a particles entity to be selected, it will print out what type it is. Do note that this example exceeds the 25 word limit.
parttype = [ pname = "" case (ea 0) 0 [ pname = "Fire and Smoke" ] 1 [ pname = "Fire" ] 2 [ pname = "Smoke Plume" ] 3 [ pname = "Smoke" ] 4 [ pname = "Fountain" ] 5 [ pname = "Explosion" ] 6 [ pname = "Meter" ] 7 [ pname = "Vs Meter" ] 8 [ pname = "Text" ] 9 [ pname = "Flare" ] 10 [ pname = "Lightning" ] 11 [ pname = "Fire" ] 12 [ pname = "Smoke" ] 13 [ pname = "Water" ] 14 [ pname = "Snow" ] 15 [ pname = "Leaves" ] 32 [ pname = "Lens Flare" ] 33 [ pname = "Fixed Size Lens Flare" ] 34 [ pname = "Sparkly Lens Flare" ] 35 [ pname = "Sparkly Fixed Size Lens Flare" ] () [ //default pname = "A very pretty albeit unsupported particle" ] echo The particle you have selected is of type: $pname ]
Naming
another way of doing decisions/branches is to use paths with different names. This has limited usage though. in short it requires you to query some information to concatenate onto another word, which you would then execute. There are exceptions. Other than the number of results you can query, there is no limit here. A handy trick is to have several results execute the same alias, though this requires that they differ only slightly at most in operation.
//This example demonstrates the exception //it requires you to have an entity selected //This will conflict with showquickgui newgui "particles" [guitext "It's like a random series of nothing"] newgui "light" [guitext "SHINY!!!!"] newgui "jumppad" [guitext "boing!"] identify = [showgui (et)]
//shows a more standard example, at present this is exclusively used by edithud (see stdedit.cfg) //this also requires you to have an entity selected. identlight = [echo "it's so bright..."] identdynlight = [identlight] // to demonstrate how different results can execute the same script identjumppad = [echo "boing? boing!"] identparticles = [echo "Please don't change it to a lens flare!"] identify = [ if (enthavesel) [ident@(et)] [echo Select an entity first!]]
Lists
Ordered lists are the closest cubescript has to the likes of arrays. Their declaration is identical to any alias, as they are simply items in a string separated by white space. Using [] is recommended as the container of choice, since it allows you to use "" to include multi-word items in a list as a single unit as well as define the list over multiple lines. "" can also be used to define lists. An example can be inspected below.
//declares 2 equivalent lists of 3 elements mylist1 = [one two "three four"] mylist2 = "one two ^"three four^"" //prints the 3rd element echo (at $mylist 2)
loops
There are at least 2 ways to loop through a list. The first method involves the use of looplist to iterate over it and the second the use of listlen to determine the list's length. The difference between using looplist and listlen is that you will not have a reliable way of determining an element's index and won't have to manually fetch the element from the list. Basically looplist is faster at the cost of some information and should be used in all cases where the index does not need to be known. An example can be inspected below.
mylist = [ 0 1 3.14159265359 2.71828182845 sqrt(-1) 1 //to demonstrate that the index cannot be determined reliably with looplist ] //demonstrates looplist looplist var $mylist [ echo number (listfind item $mylist [strcmp $var $item]) : $var ] //demonstrates a loop + listlen combo loop i (listlen $mylist) [ echo number $i : (at $mylist $i) ]
concatenation
Concatenation is pretty simple, albeit not very efficient. It is basically, setting the list to itself plus the additional item. This is of course best achieved through the concat commands.
mylist = [1 2 3 4 5 6 7 8 9 10] //using concatword mylist = (concatword $mylist " " 11) //using format mylist = (format "%1 %2" $mylist 12) //using concat - we're also preparing it for pretty list mylist = (concat $mylist 14) //pretty list does add an item onto the list, but it changes the format dramatically and the new item is placed before the end //it is ideal more so for making things pretty for presentation than concatenation mylist = (prettylist $mylist 13) //should print 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 14 echo mylist is $mylist
concat returns everything given to it as a singular item, spaces and all. Concatword ignores spaces and ""'s must be explicitly provided. Concat is therefor ideal to add lists together.
mylist1 = [1 2 3 4 5] mylist2 = [6 7 8 9 10] mylist3 = [11 12 13 14 15] mylist4 = [16 17 18 19 20] mylist5 = [21 22 23 24 25] mylist6 = [26 27 28 29 30] finallist = "" //how concatword merged three lists finallist = (concatword $mylist1 " " $mylist2 " " $mylist3) //concat unlike the above does not require finallist = (concat $finallist $mylist4 $mylist5 $mylist6) echo (prettylist $finallist)
finding
there are two ways to find things, the first is to use listfind, and the second is to loop through the list. Both are demonstrated in the example below
mylist = [ "Jack" "Jane" "Joseph" ] //This is an example of looking for a single element, note that this variant of... //strcmp unlike its C counterpart returns 1 when strings are equal //in this example, a 0 should be printed at the top of your screen - listfind returns the index echo (listfind f $mylist [strcmp $f "Jack"]) //In this example, -1, as Jill is not in the list echo (listfind f $mylist [strcmp $f "Jill"]) //in this example, we want to find anything which contains an e and add it into a list results = "" looplist var $mylist [ if (>= (strstr $var "e") 0) [ results = (concatword $results "^"" $var " ^"") ] ] //this should print: Jane Joseph echo $results
List of commands
General
The following commands are quite often used in a lot of scripts.
- alias N B
- creates an alias named N with body B, the intext version is denoted as N = B (equivalent to alias N B)
- at S I
- returns the Ith element in string S, note that counting starts from 0. If it's out of range, "" is returned
- getalias A
- returns the value of A. this also does not produce a warning should an alias be undeclared, nor complains when fetching built in variables.
- if C T F (F is optional)
- if condition C is true, execute T, otherwise F is executed
- listlen S
- returns the amount of elements in list S
- loop V N B
- loops N times, and aliases the current iteration to V; B is executed every iteration.
- result S
- returns the string, useful when aliases need to be executed
- rnd N L
- chooses a random number between 0 and N-1 (inclusive). L sets the lower limit. eg rnd 12 7 will return either 7 8 9 10 or 11
- sleep N B
- executes B after a delay of N milliseconds. NOTE: even if N is <= 0, it'll still wait till the next frame before executing it
- while C B
- executed B while C is true. NOTE: surround C in a [] container, or you risk an infinite loop due to the condition not being reevaluated.
Map Configuration
ONLY APPLIES TO FPSGAME
The following commands are useful in level only aliases
- triggerstate
- level_trigger
- level trigger is invoked as level_trigger_1 = [] as an example, the end number is the 4th number in the mapmodel's strings
- level_base
- Used to give bases names in capture mode. This is unfortunately unused in PAS
Binds
The following commands are useful in binds.
- onrelease
Gui Creation
For newui, please see new menu editing
These commands are used to create various menus.
- cleargui
- guibar
- guibutton
- guicheckbox
- guifield
- guikeyfield
- guiimage
- guilist
- guirolloveraction
- guirollovername
- guislider
- guilistslider
- guinameslider
- guistayopen
- guitab
- guitext
- guititle
- guistrut
- newgui
Comparisons
The functions here can change the values, or return a logical comparison for all intents and purposes, the C version of booleans apply here; values not equal to 0 are true - and functions that return true return 1
Integer Arithmetic
- + A B
- returns the value of the numbers added together
- * A B
- returns the result of multiplying A and B
- - A B
- returns the value of A - B
- = A B
- returns true of the two are equal
- != A B
- returns true if the two aren't equal
- < A B
- returns true if A is less-than B
- > A B
- returns true if A is greater-than B
- <= A B
- returns true if A is less-than-or-equal-to B
- >= A B
- returns true if A is greater-than-or-equal-to B
- ! A
- if the argument is false, it returns true
- &&
- returns true if all arguments are true
- ||
- returns true it at least one argument is true
- div A B
- returns the value of A divided by B
- mod A B
- returns the modulus of the two arguments
- min
- returns the lowest valued argument of all provided arguments
- max
- returns the highest valued argument of all provided arguments
Bitwise Integer Operations
- ^ A B
- returns the value of A xor B ie (^ 8 4) is 12 - (^ 12 4) is 8
- & A B
- returns the value of the bits both A and B have
- | A B
- returns the value of the bits either A or B have
- ~ A
- returns the inverted bitwise version of A ie (& (~ 128) 255) is 127
- ^~ A B
- xor;s the arguments after applying an inversion on B
- &~ A B
- and's the arguments after applying an inversion on B
- |~ A B
- or's the arguments after applying an inversion on B
- << A N
- shifts A N bits
- >> A N
- shifts A -N bits
Floating Point Arithmetic
Generally these just have an -f suffix
- +f A B
- returns the value of the numbers added together
- *f A B
- returns the result of multiplying A and B
- -f A B
- returns the value of A - B
- =f A B
- returns true of the two are equal
- !=f A B
- returns true if the two aren't equal
- <f A B
- returns true if A is less-than B
- >f A B
- returns true if A is greater-than B
- <=f A B
- returns true if A is less-than-or-equal-to B
- >=f A B
- returns true if A is greater-than-or-equal-to B
- divf A B
- returns the value of A divided by B
- modf A B
- returns the modulus of the two arguments
- minf
- returns the lowest valued argument of all provided arguments
- maxf
- returns the highest valued argument of all provided arguments
Strings
- =s A B
- returns 1 if the string matches, 0 otherwise
- !=s A B
- returns 0 if the string matches, 1 otherwise
- <s A B
- returns true if the C strcmp returns less-than 0
- >s A B
- returns true if the C strcmp returns greater-than 0
- <=s A B
- returns true if the C strcmp returns less-than-or-equal-to 0
- >=s A B
- returns true if the C strcmp returns greater-than-or-equal-to 0
- strcmp A B
- returns 1 if the string matches, 0 otherwise (unlike the C version with returns the disparity)
- strstr H N
- returns the position of N in H - otherwise -1 is returned
- strlen S
- returns the length of the string
- strreplace S O N
- replaces all instances of O inside string S with N
String/text formatting
These commands are used to format aliases which are normally huge bits of text
- concat C
- everything you type after will be returned; with expressions and substitutions performed
- concatword C
- the next 25 arguments will be executed and subsequently concatenated without any spaces between them
- format F S1 S2 S3 S4 S5 S6 S7 S8 S9
- F is a string containing %# tokens, format substitutes the arguments with their respective %# tokens (ie: S5 will replace %5)