-- Overview -- The primary scripting language for running bots in Darwinbots3 is called "Sunweaver". Sunweaver is a custom language based on stacks. It makes mutations and recombinations of programs quite easy, however it can feel quite foreign for humans to read and write. A sunweaver program is an ordered list of commands called "base pairs". Any number of base pairs, in any combination, is a valid program. Not all valid programs do very much, of course. The following is a brief informal overview of the Sunweaver language. -- Stacks and Reverse Polish Notation -- Sunweaver works with the concept of three separate stacks. There's an integer stack, which stores numbers in the range [[stacklo,stackhi]]. There's a boolean stack, which just stores true and false values, and there's a codule stack, which stores all the currently executing codules, to allow functions to unwind up the call tree and prevent recursion. Like stacks in other programming languages, values are placed at the top of the stack and removed from the top of the stack. All basepairs in the DNA operate in some way or another on these stacks. Once a basepair is executed, values are removed from the top of some of the stacks, and other values are potentially placed on top of some of the stacks based on the command. This effectively gives the DNA a "reverse polish notation". For instance, to add two numbers you must first specify the two numbers and then the add function. That is, 3 + 4 becomes 3 4 add This format can be cumbersome for people to read and write, but it is uniquely suited to a computer program that is allowed to mutate, as any random permutation of the commands in a syntactically valid program is a syntactically valid program. At the end of the DNA execution, all stacks are cleared. If you want to store values persistently across cycles, you need to store them in Memory, which is persistent. -- Memory -- Bots have [[num]] memory slots that can hold integers between [[memlo,memhi]]. This is the only way to store values from cycle to cycle, as the stacks are cleared after DNA execution is done. This is also how a bot receives information from it's sensors about the world, and how it actually does something like move forward or change its body shape. To read values from memory, you simply specify the memory location you're interested in (on top of the integer stack) and use the 'ref' command to place the value of that memory location on top of the integer stack. For instance, suppose that at memory location 3 the value '24' is stored. You can run 3 ref And it will remove 3 from the integer stack and replace it with 24. To store values to memory, you must specify first the value you want to place there, then the memory location, and then the store command. For instance 24 3 store Will store 24 in memory location 3. If you try to store or read from memory location 0, the command will fizzle. If you try to store to a location larger than [[num]], the location gets modded in to range (that is, it takes the least significant digits of the location you're trying to store to and stores there). If you try to store to a negative location, the absolute value of the location is used instead. If you try to store a value that is outside the [[memlo,memhi]] range, it will mod it in to range (again, just taking the least significant digits). -- Fizzle -- If a command would attempt to do something illegal (such as store to memloc 0 or call a codule already on the call stack), values will still be removed from the stack but the command will then abort. That is, the command extracts the input and just swallows it. This is called fizzling, and ensures program flow largely doesn't depend on what values are on the stack (call, branch and loop not withstanding). -- Commands and stack size -- If a command would take off more values from a stack than are currently on it, the command will abort. This means no values from any stacks are actually added, removed or changed. -- Operators -- Operators are commands which take one or more values from the stacks. There are math commands, which return values to the stacks after performing some math on them, such as addition or multiplication. There are flow commands, which take values from the stacks and potentially alter the program flow, such as calling in to a codule subroutine. There are store commands, which change the values of memory. Essentially, if it's not a constant number, it's an operator. -- Explicit operators -- In addition to the standard operators which pull their values from the stack, it's also possible to combine certain operators and their memory or codule locations to form a single operator. For instance, instead of writing 3 someloc store To store 3 in memory slot 'someloc', you could write 3 .someloc The '.' prefix turns 'someloc' in to an explicit operator. The '.' explicit operator corresponds to storing values in memory locations. Other punctuation correspond to other operations. -- Comments and parsing rules -- DNA is case insensitive. Use ' or // to specify a single line comment. Any characters between ' or // and the end of the line are ignored when parsing the DNA. All whitespace is also ignored, except to separate tokens. That is, whether you use a space or 17 tabs the DNA will parse the same. Of note are newlines. DNA is parsed line by line, so while normally a newline is the same as whitespace, there are some places, such as comments and metatags, where newlines are syntactically important. -- const and macro -- If a line starts with 'macro', it marks the start of a macro. The basic macro syntax is macro label code-fragment The label is a set of characters (can be any characters except whitespace and ' and //) which are used to identify the macro. The code-fragments are 0 or more valid DNA chunks that are compiled in to codules. In code, when the macro label is encountered, it is replaced with the DNA in the compiled codule (it does this inline. That is, without the overhead of a codule call). If a line starts with 'const', it defines a constant in the DNA execution. It must follow the format const label 36 Constants are stored in the DNA intact, and the mapping of label to 36 is stored in the DNA. This allows the reverse mapping from number to label to happen when someone goes back in and views the DNA as human readable text. It is a syntax error to have const or macro on a line but not be the first token. These const and macro tags are parsed before any of the actual DNA, so it doesn't matter where in the DNA they are defined. They could be placed at the very end of the DNA without issue, if that's your coding style. -- Codules -- Most programming languages have the concept of functions or subroutines, where program execution is halted temporarily in one place, runs through a number of commands elsewhere, and eventually returns to the original code being executed. Here, functions are called codules and have a number of important properties. First, like functions, codules save and interrupt the current program execution when they are called, and flow passes to the codule to execute itself. This passing of control is represented by having the codule add itself to the codule call stack. Once a codule is done running, it pops itself from the codule call stack and program execution returns to where it was called. Like functions, you can nest codules to create a call chain from A to B to C and on. Second, unlike functions, codules cannot call either themselves or already executing codules. If a codule attempts to add itself to the codule call stack and another copy of it is already present in the stack, the call won't happen and execution will continue on without it (this is, it will fizzle). This is to prevent infinite loops. Each codule is assigned a unique integer and placed in a codule slot. If you attempt to call in to a codule slot that is empty, the first non empty codule slot less than the current codule slot will attempt to be called instead. If that codule is already on the call stack, the call won't happen (again, it will fizzle). Negative codule slots are turned positive. Slots larger than [[codule slots]] are modded in to range (that is, the least significant digits are used). To call a codule, you specify the codule slot and use the 'call' command. eg: 13 call Will attempt to call in to the codule at slot 13. If there isn't one there, it will look for the first codule less than 13 and call in to that. If it still can't find one, 13 is taken from the integer stack but no call is made. -- Defining codules -- Codules in source code are defined as anything between the { and } punctuation. Any time the DNA parser finds a codule definition, the entire codule definition is removed from the code and a new codule is created with the removed code. Where the code was removed from, an integer representing the codule slot the codule was placed in is added to the code. This allows you to specify codules in-line. For instance { 1 2 add 3 store } call This will create a codule that adds 1 and 2 together and stores the result in memory location 3. That codule will get assigned to an empty codule memory slot and that memory slot will be put there in its place. So the above basically becomes: in codule slot 3: 1 2 add 3 store in the main codule: 3 call And when the program runs the effect will basically be 1 2 add 3 store If you have a preference for a codule slot for a codule (useful for some very specific math based call logic), you can specify the number by placing it either immediately after the opening brace or closing brace, eg: {3 1 2 add 3 store } { 1 2 add 3 store }3 If you want to name a codule so you can later on call it by name, place the desired label immediately after the opening brace. eg: {Three2Three 1 2 add 3 store } three2three call You can specify both the codule slot and a label like so {Three2Three 1 2 add 3 store }13 You can also chain the explicit operators with codules. For instance, these all will define and immediately call a codule @{ 1 2 add 3 store } @{three2three 1 2 add 3 store } @{ 1 2 add 3 store }13 -- Implicit codule -- Any code not inside a codule is part of the base level "main" codule, which has an index of 0. That is, if you just start writing code, it will all go in the 0th codule. This 0th codule is what is called when the DNA is first executed. -- Chromosomes -- Multiple codules all defined together constitute a "chromosome". This distinction is important, as it's possible for multiple chromosomes to control a single bot, and they will each have their own stacks and their own copies of the bot's memory, and they cannot call each other's codules. It's also on the level of chromosomes that sexual crossover events occur, which mix the DNA during sexual reproduction. -- Loops -- [loop count] [codule] loop A limited form of looping is supported. You must specify a number of times to loop and a codule. The codule will be called that number of times, and each time a decrementing value from [loop count] to 1 will be placed on the stack before the codule is run. As a simple example 20 { 5 swap store } loop Will store the value 5 in memory locations 1 through 20. As another example: 3 { } loop Will place the numbers 1, 2, 3 on the stack, with 3 being on the top. Negative loop counts will still loop that number of times, but the values placed on the stack before the codule is run will be negative. -- Branches -- A limited form of branching is supported. Suppose if some value is true you want to execute some code which might manipulate the stack in some way. You can do *myage 20 < youngling branch Here, if the memory location at myage is less than 20, the 'youngling' codule is called. If you want to do an if-else statement, you can use: *myage 20 < youngling branch not oldling branch -- Parsing order of precedence -- When interpreting a label, the system checks the following list in order, from top to bottom. As soon as a match is found, that match takes precedence. macros constants codules sysvars commands Note that this means that items higher in the list can hide items lower in the list. For example, macro ten 10 const ten 5 ten 1 store The above program will store 10 in memloc 1, not 5, because macros take precedence over constants. -- Example program -- [[code]] 'This doesn't do much, but it shows some of the tricks you can do, and how you 'might organize your code const A 10 'A will be 10 const B 20 'B will be 20 const C 30 'C will be 30 'define a custom command pyth, which returns the other value of the pythagorean 'triplet {pyth dup mul swap dup mul add sqrt } 'Make it look like pyth is an in-built command macro pyth @pyth ' To make rough looking if/else branches macro else not 'Store the hypotenuses in memory locations 10, 20, and 30 3 4 pyth .A 6 8 pyth .B 8 15 pyth .C 'If the value in 10 is less than the value in 20 *A *B < { 'Store how much larger A is than B in A *A *B sub .A } branch { 'Store the sum of A and B in A *A *B add .A } else branch [[/code]] -- Command types -- UnaryBoolean - Takes one value from the boolean stack BinaryBoolean - Takes two values from the boolean stack UnaryOperation - Takes one value from the integer stack BinaryOperation - Takes two values from the integer stack -- Full list of commands -- [[FullList]] -- Full list of explicit commands -- [[ExplicitList]] -- Sysvars -- Sysvars (System Variables) are named memory addresses reserved for use by the system. They are used for input and output variables to receive data from a bot's senses or to direct actions for a bot to engage in. To aid programming, each used location is given a mneumonic label. The labels, their memory locations, and a brief description of what they do is given below: [[sysvars]]