This is actually more of a beta than the last release, since I haven't had time to do extensive testing. I'm stealing time to work on it from the research paper I'm supposed to be writing.
But I think it's stable enough to be worth giving to the people who might want it, so I've put it up on
my website for anyone who wants to give it a workout for me. I hope to write a better tutorial than the one I provided for the last one, but I have to get through with my school projects first.
Code written for the old version can be made to work with the new with a bit of effort. I averaged about ten minutes per 100 lines of code, and that was while I was doing debugging on the system at the same time.
Differences from the old version:
* Each class can be made to output to a specific file using the File:[filename] directive, where [filename] is the name of the VC file to output to, minus the .vc extension. Put this in each class that you want to redirect.
* ClassBuilder will grab all *.dma files that are in the current directory or any subdirectories, and generate a main file that includes all the sub-files generated. I forget what the default file is, but if you put a File: directive outside of any classes, that will be made into the new main output file. This way you only have to #include the main VC file in your system.vc, and all the sub-files created by the classes will be included automatically.
* You must write an error handler for runtime type errors. It must look like this:
int DMATypeError(string expected, string found, string function, string class)
where 'expected' is the class name that was expected, 'found' is what was actually found, 'function' is the function the error occurred in and 'class' is the class the function belonged to. If DMATypeError returns 0, the function that was being called will be aborted. It will simply do a 'return;' if the function return type was void, otherwise it will either return '' or 0. Type checking is done on all arguments passed to a function, so if you have the function:
void SomeFunction(Player p1, Player p2, Weapon p3) {
// My code
}
then by the time it reaches the 'My code' comment you can be sure that all three parameters are of the correct type (assuming you always return 0 from the DMATypeError function. If you don't, it's up to you to figure out what to do about it.)
* HTML documentation is generated for the classes. The table of contents can be sorted either alphabetically by base class name (with all subclasses printed in hierarchical fashion beneath the base class) or by file name that the base classes appear in. Each class also has its own page with details about the class, its fields, constructors, destructors, functions and inherited functions. In the docs\ directory you can find a CSS file that will let you adjust the color scheme if you don't like the way I did it.
* Comments are now done in blocks (rather than the stupid single-line only comments I had earlier), and will attach themselves to the next available Class, Field or Code block. The comments will be put into the HTML documentation for whatever comes after them.
* Each function must now be given its own code block in the following format:
[FunctionType]:[ReturnType] [FunctionName]([Parameters])
// code goes here
EndCode
where [FunctionType] is either Init, Dest, Code or InheritCode. [ReturnType] is either a VC data type (void,int,string) or the name of a class. [FunctionName] is just the name of the function, and [Parameters] are the values that are passed to the function. An example:
Code:int MultiplyByFour(int value)
value = value * 4;
return value;
EndCode
* Multiple constructors (Init blocks) are allowed, as are destructors (Dest blocks). If an Init block is not defined, a default [ClassName]New() function will be generated. If a Dest block is not defined, a default [ClassName]Free() function will be generated. The only difference between an Init block and a Code block is that Init will automatically create a local variable named 'this' of the same type as the class the Init block is in, allocate the memory for it, and it will return 'this' automatically at the end of the function. It requires that you set the return type to the name of the class. Similarly, Dest blocks expect to have an argument of the same type as the class with the name 'this', and will automatically free the memory taken by 'this'.
* Parameters with the same name as a field in the class will automatically be assigned to that field. If you have a field named HP then you can do like this:
Code:void PlayerSetHP(Player this, int HP)
EndCode
and the player's HP will automatically be filled in with the value passed to PlayerSetHP. This applies to all types of functions: Init, Dest, Code and InheritCode.
* Defines for each class type are generated automatically, so this is legal:
Class:CWeapon
Field:AttackPower:quad
Field:ParryChance:byte
Init:CWeapon WeaponNew(int AttackPower, int ParryChance)
// AttackPower and ParryChance are both field names, so
// they will automatically be filled in.
EndClass
Class:CPlayer
Field:Weapon:CWeapon
Code:void GivePlayerWeapon(Player this, int atk, int parry)
CWeapon newWeapon;
newWeapon = WeaponNew(atk, parry);
$this.Weapon$ = newWeapon;
EndCode
* Accessing fields has been made a lot easier, as seen in the example above. You can nest field accesses to any depth. If you had a tree data structure, for example, this would be legal:
CTree myTree = TreeNew();
// Pretend a bunch of values are added to the tree here
Log('Some node deep inside the tree: ');
Log(str($myTree.Root.Left.Right.Left.Left.Right$));
The requirement for being able to do this is that you declare the variable you're using as the root object (myTree in this case) to be of the correct type. ClassBuilder will catch attempts to use incorrect field names or classes that don't exist. The variables being used can be local (declared inside the function), arguments (passed to the function as parameters) or globals (declared inside a Define: block).
* If you use a root object that isn't recognized by ClassBuilder as being a variable name, it will attempt to use it as a field name inside 'this' object. For example:
CPlayer this;
CWeapon newWeapon;
$this.Weapon.AttackPower$ = 5;
$Weapon.AttackPower$ = 5; // This is equivalent to the line above
newWeapon = CopyWeapon($this.Weapon$);
newWeapon = CopyWeapon($Weapon$); // Also equivalent to the line above
* Accessor methods are no longer automatically generated. That may change, but I didn't really use them in the earlier versions and I'm kinda stuck for time right now. They're easy enough to write manually if you need them, and that way you can do any additional processing if you need to.
* The biggest unfixed bug has to do with initializing variables. I made an attempt to allow for all styles of variable declaration, meaning that the following are all legal:
int some_var;
int a, b, c;
int d, e, f; string oooh;
int fancy = 5, nonfancy, fancy_again=6;
string cool = 'cool', uncool;
However, the local variable parser gets confused when commas are found on the line that aren't meant to separate variables. Both of these examples will cause errors in the code generation (which will be obvious when you try to run Verge):
string s = 'Ack, this one confuses me. See the comma?';
int x = UhOh(5, 3, 'Argument lists are bad too.');
The correct way to handle these is like this:
string s;
s = 'Hey, commas are okay now.';
int x;
x = Okay(5, 3, 'Argument lists are okay if there's no var declarations on the line.');
When I get time I'll be fixing this.
* EndClass is now mandatory at the end of a class.
* Operator overloading was going to go in, but I've put it on the back burner due to concerns about how it will work with polymorphism.
* Polymorphic functions (InheritCode blocks) must now have their function prototype fully specified every time they are declared, and they must match exactly (except for possible extra whitespace) with the prototype of the previous function. I realize this could get annoying, but I found it necessary to get the doc generation right, and I wanted inheritable functions to remain consistent with non-inheritable functions, to simplify the generation code.
I think that's about all. Feel free to mail bug reports (along with a .dma file that reproduces them...preferably the minimum required to reproduce the bug) or questions about how things work to kevinhobbs@gmail.com
The CString class included in the zip is working fine, and the event scheduling code seems to work as well, though I haven't done intensive testing on it (mostly I converted it straight from the old format and made sure it compiled...it didn't involve changing any code, just the ClassBuilder markers), so I think things should mostly be okay.
Use verge.bat to run the example (it uses ClassBuilder.exe to regenerate the VC code, which I didn't include in the zip) and then check v3.log and system.vc to look at the simple example. You'll notice a -lot- of trace messages scrolling past. Don't worry about those. If there are any problems with your input, it'll stop the generation and print a distinctive error message at the bottom of the screen, and pause until you hit a key. If everything seems to have gone right, it'll print 'All okay!' and try to start V3.
The CString class has been changed from the last release of my string lib, by the way - it is now a wrapper around my old string lib, and it takes care of catching dropped pointers so you don't have to. You can check the docs\ folder for more information on the included classes.
The only other major change that I'm thinking about is including interfaces, which act like polymorphic functions but you don't have to inherit from a base class that defines them to have them work. Like this:
Interface:IDrawable
InterfaceCode:void Draw(IDrawable this, int bitmap)
EndInterface
Class:CSquare
Interface:IDrawable
Field:Width:word
Field:X:squad
Field:Y:squad
Code:void Draw(IDrawable this, int bitmap)
// code to draw a square onto a bitmap
EndCode
EndClass
Class:CCircle
Interface:IDrawable
Field:Radius:word
Field:X:squad
Field:Y:squad
Code:void Draw(IDrawable this, int bitmap)
// code to draw a circle onto a bitmap
EndCode
EndClass
and then do:
CSquare square = CSquareNew(x, y, width);
CCircle circle = CCircleNew(x2, y2, radius);
Draw(square, screen);
Draw(circle, screen);
and have them drawn correctly despite being two different classes with no inheritance relationship.