PbML grammar is fairly simple if you know C++/Java (and maybe Perl).
It is CASE SENSITIVE, and it describes an OO world, so there are classes, (interfaces), methods and attributes. As for this version PbML is a loosely typed language.
During the explanation of the syntax the following are used:
| <bracketed_name> | stands for a variable, an attribute, a method name, a class name etc... | |
| [...]? | things between square brackets are optional | |
| [...]* | things between square brackets may be repeated zero or more times | |
| [...]+ | things between square brackets may be repeated one or more times | |
| A | B | means that one can put or A or B, but not both |
Usually identifiers are expressed just like in C/C++/Java, and so are constants.
Valid variable and list names: foo, f_56
Basic types are: "int", "float",
"boolean" and "string" (and
"scalar").
Object types are, for the moment being, just "object".
To make a variable/device of type "<type>" just write
<type>[]
for example: boolean[] coins;
Special values are:
Please see the examples.
A PbML file is a sequence of class definitions and interface definitions.
Please note that the same class can be defined across several files. Each time one must redeclare the class header and then add new attributes and methods. The resulting class will be the union of every method, device and attribute found in every file under the definition of the same class.
interface definition: //TO BE WRITTEN
class definition:
class <class_name>
{
vars:
<access_type> <type>[ "[
//more attribute definitions here
devices:
//more device defs here
actions:
<method_name> ( [<type> <param_name> [,<type> <param_name>
]* ]) @ <phase_name>
{
//here method body
}
//more action here
commands:
<method_name> ( [<type> <param_name> [,<type> <param_name>
]* ]) @ <phase_name>
{
//here method body
}
//more commands here
routines:
<type> <method_name> ( [<type> <param_name> [,<type>
<param_name> ]* ])
{
//here method body
}
//more routines here
}
Please note that each section (vars, actions, ...) may be repeated more than once (it can even by omitted) in a class definition. <method_name> <phase_name> <obj_name> <attr_name> <param_name> can be any valid C-like name.
<access_type>
Thus it makes perfect sense to write:
int i=item1.subitem2.subsubitem.i;
as long as "subitem2" is a object readable attribute of "item1", "subsubitem" is a readable object attribute of "subitem2" and finally "i" is a readable scalar attribute od "subsubitem".
A method body is a collection of statements:
PbML features the typical statements, that are written and behave just like C++ ones:
automatic variable declaration (of type int, boolean, string, float, scalar, object, int[], object[],...)
if..then..else
while .... (no break statement yet)
return ....
assignment
Other statements deserve a little explanation:
One can invoke a method of the same object whose code is executed, or of some other object.
The former is written in a typical way:
foo(4,5);
or mimicking some high-level languages, one can pass parameters in whatever order, provided one specifies the formal parameters name for each parameter:
foo(start=5, end=4); //if foo's prototype is foo(int start, int end);
to invoke a method of an object, or of a device we own, just write:
<device_name>.method(params);
<object_reference>.method(params);
PbML does support multiple indirection, if the attribute whose method is called is a readable object.
In theory only routines should be invoked. This means that in practice also actions and commands can be invoked. But beware!! This is a most-unnatural behaviour!.
A library call takes this form:
<library_name>::<service>
We have now three libraries:
Yagga, Sys and Math.
Suche services are listed in appendix B.
Math is a one-to-one mapping of the java.lang.Math class, and has every usual method, max, min, sqrt, sin, cos, atan... in the more obvious form.
Library calls does not allow for the passing of parameters with their formal names:
Math::min(a=5,b=19); //wrong,
Math::max(5,8); //correct
Lists are collections of devices, or collections of reference to objects, or plain lists of scalar values: The former are declared in the "devices:" section of a class definition, the latter can bedeclared in the "vars" section or in the body of a function.
Please note that lists contains only reference to objects, in an ordered or unordered way. By removing an object form a list one does not destroy the object.
To do so one must explicitly call the "destroy" operator on an object reference.
Moreover, assigning a list to an object reference makes perfect sense and simply assigns the first element of the list to the object element.
To iterate over such collections one must use the following construct:
foreach
for example:
object[] ships;
ships=Yagga::getAll("Ship");
foreach ship in ships
{
ship.doDamage(5);
}
In the body of the foreach statement the variable "ship" is given every iteration the value of one element in the list, from first to last.
To call a method on every object contained in a list one can write
foreach item in list
item.method();
a much compact syntax is:
list.method();
This will broadcast the method() call to every object in the list. This can simulate user defined events, and the code executes more quickly than the foreach construct.
Please note that if an object in the list does not have the method method(), Yagga will issue a warning but will keep on executing the cycle.
One can initialize or assign elements to a list in several ways.
The simplier are:
object[] list=list_old; //assign the values of another
object[] list=(item1,item2,list3); //assign a list of values, that can be single objects or lists
Please note that list and list_old will reference the same objects.
If you destroy an object from list_old, it won't be accessible from list. If you, instead, remove an object from a list it will keep on existing on the other list.
Llists can be restricted by mean of the "subset" operator.
This operator has the form
list=old_list[a==5];
or
item=old_list[index];
In the first case list will be assigned every object in old_list that has the attribute "a" with the value of 5. In the second case item will be assigned the value of the (index+1)-th element (lists start counting from zero!!!) of the list.
Generically the subset operator must always follow this syntax:
list[<item_side_expression> <relational_operator> <current_context_expression>]
or
list[<current_context_expression>]
where:
<item_side_expression> is an expression evaluated in the "scope" of each item of the list. It can thus contains only constants or name of object's attributes or devices (or method calls, provided methods are defined in every object in the list).
<relational_operator> MUST be one of : "==" "!=" ">" ">=" "<" "<="
<current_context_expression> is an expression evaluated in the "scope" of the current object. It can use local variables, constants, obeject references or calls etc... as usual. It can, obviously, not refer to attrbutes or methods of a particular object in the list. If this appears alone between the square brackets, than it is taken as the index of the element to be returned.
Please note that in the first syntax the relational operator must always be present, even if it seems superfluous.
So for example:
list=old_list[visible]; //visible is a boolean attrib of elements in the list
the compiler won't compile.
Instead one must explicitly specify:
list=old_list[visible==true];
Lists have several methods predefined.
They have a slightly different from standard method invocation, but these methods have parameters and return values.
These methods are called with the following syntax:
object list=old_list;
list=>method(params,params,...);
length
int length()
returns the number of items referenced by the list.
contains
boolean contains(object item)
returns true if "item" is referenced in the list, false otherwise
remove
object remove(int index)
object remove(object item)
removes from the list the index-th element (or the reference to the "item" object). If removal is successful returns the removed object.
Please note that:
list=list=>remove(item);
is equivalent to just
list=>remove(item);
pop push
object pop()
object popend()
pops the rightmost element of the list. Returns the popped element or null.
object popbegin()
pops the leftmost element of the list. Returns the popped element or null.
object[] push(object item)
object[] pushend(object item)
pushes the obejct passed as a paramater in the list, on the right side. Returns the new list.
object[] pushbegin(object item)
pushes the object passed as a paramater in the list, on the left side
sort
object[] sort(string key1, string key2, ....)
sorts ascendingly elements in the list according to the ordering based on the attributes names passed as parameters.
If list[] contains object of class "Automobile" with attributes "numberOfWheels", "maxSpeed" and "4Wd" then
list=>sort("maxSpeed")
will sort list[] with cars sorted from slower to faster, whreas
list=>sort("4Wd","numberOfWheels")
will sort cars by 4Wd and numberOfWheels, i.e. first came 2Wd (and among 2Wd cars are sorted by ascending number of wheels) then came 4Wd (and among these, cars are sorted by number of wheels).
If an attribute is not present in an object results are impredictable.
Please ensure that every object in the list has the attributes used for sorting. Returns the sorted list.
object[] downsort(string key1, string key2, ....)
sorts descendingly elements in the list according to the ordering based on the attributes names passed as parameters. Returns the sorted list.
min/max
scalar min(string attribute)
returns the lowest value that the attribute specified in "attribute" has among objects in the list. "scalar" means it can return a string, a float, an integer or even a boolean.
scalar max(string attribute)
returns the highest value that the attribute specified in "attrbiute" has among objects in ther list.
minOn / maxOn
object[] maxOn(string attrib)
will return a list of objects that have tha maximum value of the attribute specified in "attrib" among the objects in the list. The returned list can contain a single element or even an empty list (with zero elemens).
object[] minOn(string attrib)
will return a list of objects that have tha minimum value of the attribute specified in "attrib" among the objects in the list.
As we have seen in section 2.1.1 Actions should terminate with one of the following statements, which must appear in the same place a "return" statement would appear. These statements tell the engine if, and how, recall this action for a particular object. They have the same flow-control meaning as a return, i.e. they terminate the current action execution whenever they appear in the code being executed.
If none is specified, than an action returns with a default code of "terminate".
New objects can be created. The instruction is:
object <obj_ref> = new <class_name>;
after this instruction the object exists until one destroys it. After an object creation the OWNER is copied from the creating object to the created object. One can then modify this value.
5.2.5.1 CREATING FROM PROTOTYPES
New object can be created from special template stock object called prototypes.
These prototypes are objects of an existing PbML class with certain default values for their attributes and devices.
These prototypes are defined in prototype files. Please see appendix A.4 for further details on the file format.
To create an object from a prototype:
object
string name="Fokker DR II";
object a=new Airplane(name);
a.setX(4);
....
Please note the following:
-The owner of the newly created object is the same as the owner of the object in wich the code is being executed.
-Scalar attributes are copied exactly from the prototype's attributes to the newly created object
-Objects and devices follows a particular behaviour:
1) in the prototype definition an object reference or device must be assigned, as a value, the name of another prototype.
2) this prototype is created in cascade, i.e. a side effect of the creation of an object from prototype is the cascading creation of every referenced object/device.
3a) if this secondary object is assigned to a device, it is made unique. i.e. every instance of the parent object (created from a prototype) references a different device created from the same prototype. The owner of these cacading object is the same as the owner of the parent object.
3b) if the secondary object is assigned to an object reference (in the vars: block) then it is made global, i.e. every instance of the parent object (created from a prototype) references the SAME object created from the same prototype.
The owner of these "global" objects is a generic "__UNIVERSE__", but of course is no particular player.
Example:
Prototypes:
[Move m1]
....
[Move m2]
....
[Guns Vickers]
...
[Airplane Fokker]
...
myMove<=m1,m2;
//in the PbML for Airplane,
//myMove[] is defined in the vars: block
guns=Vickers;
//in the PbML for Airplane,
//guns is defines in the devices: block
if one instantiates:
object a1=new Airplane("Fokker");
object a2=new Airplane("Fokker");
two instances of Guns (from proto Vickers) are created (let's call them 'Guns0001' and 'Guns0002', and one instance of Move m1 and one of Move m2 (from protos) are created with name 'm1' and 'm2';
'guns' of 'a1' will refer to the object of class Guns named 'Guns0001' and 'guns' of 'a2' will refere to 'Guns0002', whereas the list 'myMove', in both airplanes, will have two items, each of one refering to the same Moves: 'm1', 'm2'.
It is then clear that building objects from prototypes has a strong effect: devices are actual components of the parent object, whereas objects as variables are just references to objects, objects that can and are shared among different objects.
So please use object instantiation in prototypes with caution. This behaviour is only found in object instantiation from prototypes with cascading creation of other objects. If one, in the code does write:
object myM1=new Move("m1");
object myM2=new Move("m2");
a1.addMove(myM1); //modify the myMove list
a1.addMove(myM2);
then a1's myMove will point to newly created (and different from anybody's else) list of moves.
To delete an object from the game simply write:
destroy
The deletion will take place at the end of the current phase.
Views are created in order to give to the player who owns an object information over some aspect of the simulated world.
Information is passed without any predefined form or meaning, except the one the designer wants to give to information received by the client.
To create a view an object sends to its owner (or to every player if broadcst is specified) a list of pairs, each pair consist of an item and a value assigned to this item.
create [broadcast]? view
{
pair, pair, ...
};
each pair is
name=value,
where value can be any valid expression, a constant, a variable or an attribute. If it is an object reference or a list the compiler will give error. In such a case, please supply a suitable string that describes the obejct.
For example:
create view
{
type="introspection",
gear=gear,
max=max,
fuel=fuel*10
};
Please see chapter 7 and appendix B for the creation of the default introspective view.
Each object in the world has an "owner", a string representing the player who "owns" this object.
This doesn't mean that a player automatically knows its object, she still needs to receive a view about a particular object in order to know it.
This is a default attribute: 'OWNER'.