Lore And Lutes Object Oriented Programming Language Documentation For C++ Game Developers Vs. 1.01 (10/27/2002) Written By: Andy Stone ASFC By: Andy Stone (astone42@attbi.com) Visit: http://loreandlutes.sourceforge.net for more information. See copying.txt or copying for licensing information. ============================================== Changes (Document Versions): ============================ 1.0: Initial document. 1.01: Fixed some dumb spelling mistakes, re-worded a few things so they sound better. What LOOP Is: ============= The Lore And Lutes Object Oriented Programming Language (LOOP) is a library which includes code made to be modified into a scripting language. If you've ever edited a Lore And Lutes (L&L) game then you've used LOOP when you edit a class. This document describes how to use the LOOP library to create your own scripting language for games written in C++. LOOP Dependencies: ================== LOOP depends on ASFC, and thus SDL and SDL_image, you'll need to have these libraries running and set up for development on your system before you can start programming with LOOP. Also I suggest that you read up on ASFC before trying to delve into the world of LOOP. You can read the ASFC documentation online at <http://loreandlutes.sourceforge.net>. Example compilation: g++ *.cpp -lasfc -lloop -lSDL -lSDL_image The LOOP Object: ================ To use LOOP you really only need to take advantage of one class: LOOP_LOOP. If you want to program in your own commands for the scripting language you'll have to delve deeper into other classes but for now just know you need this class. The first thing you should do though is create a program that will run a generic loop script: #include <loop/loop.h> #include <loop/looplib_standard.h> int main(int argc, char *argv[]) { LOOP_LOOP LOOP; LOOP_LOOPLib_Standard lib; string file; string class; string member; cout << "Linking the standard library to LOOP\n"; lib.Link(&LOOP); cout << "What file would you like to execute? " cin >> file; cout << "\nLoading: " << file << endl LOOP.ReadClass(file); cout << "What class would you like to create an instance of?"; cin >> class; cout << "\nCreating a new instance of class" oLOOP.NewInstance(0, class, SCOPE_LOCAL); cout << "What member would you like to call: "; cin >> member; oLOOP.GosubInstance(class, member); cout << "\nGosubing " << member << endl; cout << "Executing file for 100 iterations:\n" for(int i = 0; i < 100; i++) oLOOP.RunInstance(0); cout << "Finished\n"; return 0; } Go ahead and create a project, copy and paste that code, and compile. If everything works as it should then you can use this program to test LOOP scripts that show up in this document. A bit about the source ---------------------- Reading the source you'll probably notice the ReadClass() member is called. This member will read a text file and compile it in memory so that LOOP can execute it. The NewInstance command will create a new instance of class and the RunInstance will run an instance. Near the top you'll see an instance of LOOPLib_Standard created. This object is the standard LOOP library which includes basic commands for LOOP like #StrEqu and #Cout. The Link() member links this library to LOOP thus making all the commands in the lib accessible by LOOP. Now test that code ------------------ If you compile and build the program above you should be able to run generic LOOP scripts (ones that only use the the standard LOOP lib). To test LOOP create the follow file save it as test.script: @test :test #cout "Hello, World!" #return ^ Next run the generic LOOP Scriipt runner and enter the following information: Linking the standard library to LOOP What file would you like to execute? test.script Loading: test.script What class would you like to create an instance of? test Creating a new instance of class What member would you like to call: test Gosubing test Executing file for 100 iterations: If everything works properley you should get these results: Hello, World! Writing loop script files ========================== If you've programmed classes within the L&L editor before then you already know how to write LOOP scripts (if so you can just skim this section, be sure to read the Starting and Ending Classes section though as it differs from how you write L&L scripts in L&L). Otherwise I've got some explainin' to do. A LOOP script is simply a text file that includes a list of commands just like a normal program. Like any programming language LOOP requires that you write in a certain way (syntax). Sample LOOP Script ------------------ @TestClass :SayHello #cout "Hello, World!" #Return ^ LOOP is L + OOP --------------- LOOP in some respects is an object oriented language, in that you write classes that contain members and you can have inheritance. It however isn't strongly typed, there are really only three variable types. So it's not truly an Object Oriented Language. Nevertheless, its important that you understand the basic concept of OOP before you try to program and use LOOP. Being that if you're reading this document you probably know how to program C++ I'll assume you know OOP. Starting and Ending Class ------------------------- Each script file you create will contain a class, the first line of all script files should include an at sign (@) and the name of the class you want this file to contain. For example if you want a class named TestClass the file should start out like this: @TestClass Class names can contain any character you want except [, ], (, or ). To end a class place a caret at where the class ends. For example a generic class format is: @ClassName //Class Code ^ Anything after the carret will be ignored by the LOOP parser so you can place comments and notes there if you'd like. Be careful to end the class though as forgetting to do so can confuse LOOP and might freeze it. Comments -------- LOOP uses C++ style comments, two slashes denote a line comment telling the LOOP parser to ignore everything until the end of a line. /* and */s are block comments and are ignored just as they are in C. Commands -------- To actually make LOOP do something you have to use commands! Commands are programmed in C++ and this doc will explain how to make your own commands later on. To use a command type a pound sign (#) followed by the command's identifier, followed by any parameters you want to pass to it. Separate all parameters with spaces. For example the command cout takes a string parameter and will output this parameter onto the console: #cout "This command couts" There are three types of parameters LOOP accepts, ints, floats, and strings. Constants of those three types are passed just like that are in most languages. For example a string is passed by enclosing the string in double quotes ("), ints are passed by typing a whole number, and floats are passed by typing a number with a decimal point. Ints and floats may be positive or negative. Example: #cout "String " #coutInt -1234 #cout " " #coutFloat 1.234 Result: String -1234 1.234 Variables are passed by enclosing the variable identifier in brackets []. Variables --------- In LOOP there are three types of variables: string Holds a string of characters like "Hello" int Holds an integer number like 123. Int is held in memory just like an int in C++. float Holds a number with a decimal point, like 123.4. In memory a float is actually stored as a double. There are also two categories of variables public and private. By default whether a variable is public or private won't effect LOOP. You have to program in the difference yourself if you like. To create a public variable write a percent sign (%) followed by the type of variable, a space, and in double quotes the identifier you want for this variable. Example: %string "varString" //Creates a string called varString %int "varInt" //Creates an int called varInt %float "varFloat" //Creates a float called varFloat To create a private variable write a dollar sign ($) followed by the type of variable, a space, and in double quotes the identifier you want for this variable. Example: %string "varPubString" //Creates a public string called varPubString %int "varPubInt" //Creates a public int called varPubInt %float "varPubFloat" //Creates a public float called varPubFloat To pass a variable to a command simply enclose the variable's identifier in []s. For example: %string "message" #StrEqu [message] "Hello, World!" #cout [message] Labels ------ Labels mark a location within a program. To create a label type a colon (:) followed by the identifier you'd like for this label. For example: :label Label's are used to mark the beginning of methods (sub routines), that #Gosub or #Goto commands call. The Standard Library -------------------- In LOOP a library is a set of several commands. LOOP comes packaged with a sample library called the "LOOP Standard Library". Its not neccessary to use this library but its highly suggested. The commands in the standard standard library are listed below. The rightmost column in the table below specifies what types parameters are passed. I for int, F for float, S for string. If a parameter is enclosed in []s that means a variable has to be passed. For example the table entry for RemoveSubStr looks like this: RemoveSubStr Removes the sub string between the two passed ints. [S]II So RemoveSubStr expects a string variable to be passed and two integer variables or constants. Example: @Test %string "string" :Test #StrEqu [string] "String oops!" #RemoveSubStr [string] 7 11 #cout [string] #Return ^ Result: String! ============================================================================== Command Desc Pass Debugging --------- Cout Writes a string onto the console S CoutFloat Writes a float onto the console F CoutInt Writes an int onto thte console I CoutDebugInfo Display LOOP debugging information. Conditional ----------- IfStrEqu If two strings equal execute the command on the SS next line. If the next command is #begin execute all commands between #begin and #end. If the two strings aren't equal skip the next line unless it's a begin and which case skip to the #end command associated with the #begin command. IfIntEqu Like IfStrEqu but compares two ints II IfIntGT If int is greater than II IfIntLT If int is less than II IfIntGTI If int is greater than or equal to II IfIntLTI If int is less than or equal to II IfFloatEqu If float is equal to FF IfFloatGT If float is greater than FF IfFloatLT If float is less than FF IfFloatGTI If int is greater than or equal to FF IfFloatLTI If int is less than or equal to FF Misc ---- Begin Used to mark the beginning of a conditional block for #If commands End Used to mark the ending of a conditional block for #If commands String Manipulation ------------------- StrEqu Sets the the first string passed equal to the [S]S second AppendStr Add the 2nd string passed to the end of the 1st [S]S InsertStr Insert 2nd string into 1st [S]SI RemoveSubStr Removes the sub string between the two passed ints [S]II UpperString Capitalize the 2nd string, place it in the 1st [S]S SubStr Grab the sub string between the 2 int locals [S]SII StrSize Return the size of a string [I]S Int Manipulation ---------------- IntEqu Set int equal to [I]I IntInc Increment the value of an int by 1 [I] IntDec De-increment the value of an int by 1 [I] IntAdd Find the sum of two ints [I]II IntSub Find the difference between two ints [I]II IntMul Find the product between two ints [I]II IntDiv Return the ratio between two ints [I]II IntMod Find the modulo between two ints [I]II Float Manipulation ------------------ FloatEqu Set float equal to [F]F FloatInc Increment the value of a float by 1 [F] FloatDec De-increment the value of a float by 1 [F] FloatAdd Find the sum between two floats [F]FF FloatSub Find the difference between two floats [F]FF FloatMul Find the product between two floats [F]FF FloatDiv Find the ratio between two floats [F]FF Seek ---- Goto Set the point of execution to a label S GoSub Set the point of execution to a label. Return to S this point when the next #Return command is called. Send Perform a goto command on another instance SS GoSend Perform a gosub command on another instance SS Return Used for marking the end of a method called by a #GoSub or #GoSend statement Sample LOOP script: =================== //This loop script will print all the even numbers between 0 and 100 //when the PrintEvens method is called. @Mathematician %int "counter" %ing "mod" :PrintEvens #IntEqu [counter] 0 :Update #IfIntLTI [counter] 100 #Begin #coutInt [counter] #cout " " #IntAdd [counter] [counter] 2 #Goto "Update" #End #Return ^ To run this script using the generic LOOP runner enter the following info: Linking the standard library to LOOP What file would you like to execute? script.script Loading: script.script What class would you like to create an instance of? Mathematician Creating a new instance of Mathematician What member would you like to call: PrintEvens Gosubing PrintEvens Executing file for 100 iterations: Creating Your Own LOOP Library ============================== Once you've learned how to create your own scripts using the standard LOOP library you may like to create your own library of commands. Doing so is fairly easy once you get everything set up, but getting everything set up can be a pain! The way LOOP handles commands is fairly easy, you create a function that maps to your command, then you tell LOOP where this function is, what command it links to, and what parameters are passed to the command. Whenever LOOP encounters your new command in a script it calls through a function pointer your function. This sounds easy but its not! Anyways, the first thing you need to do is create .h and .cpp files for your library. Copy in the code for each file as seen in appendix A (near the bottom of the file). You'll notice that the .h file includes the following headers: #include <loop/loopmacros.h> #include <loop/info.h> #include <loop/loop.h> loopmacros includes macros that make it possible for you to pass a function within a class to LOOP, if you've ever used function pointers you may have encountered troubles with this before. The macro will create a static member in your library class that will map to the dynamic member to be executed. If you don't understand that don't worry just know you have to use the macros in there. The info header includes an info object, an info object will be passed to the function you create for your new command. The info object holds information about what parameters are passed, what instance is passing it, and includes a few helpful members that make LOOP command programming easier for you. And the loop header includes the main LOOP object which will get passed to a member your about to program called Link(). Link() will link() all of the commands in your library to the main LOOP_LOOP object. Now go through the file and anywhere it says Insert<something>here insert the appropriate name. For example replace: InsertNameHere: With the name of your library. For example MyLib. InsertCommandType: With a helpful description about what types of commands you're defining below. InsertCommmandName: Insert the name of the command you want to create. Pay close attention to this line: LOOPPORT(LOOPCMDPRT_InsertCommmandName, LOOPLib_InsertNameHere, LOOPCMD_InsertCommandName); LOOPPORT is the macro that defines members for your LOOP command that will be called whenever the command is encountered in a script. The first parameter passed will define a port function which helps resolve some issues with how function pointers work, the second param passed is the name of the class your member functions dwell under, and the third parameter tells the macro what you want your actual member function to be called. If you want your library to include more than one command simply copy that line as many times as necessary replacing all the InsertNameHeres with the name of each command. Next near the bottom of the .cpp file you should have these lines: void LOOPLib_InsertNameHere::LOOPCMD_InsertCommandName(LOOP_Info oInfo) { //Inset command code here } This is a template of what your member function will look like. Replace the Insert<something> text with the appropriate information. If you like you can change the identifier of the LOOP_Info variable (oInfo) passed. Next scroll up to your Link() member, which will link your library to the LOOP object. Take a gander at this code: poLOOP->NewCommand("InsertCommandName", &LOOPCMDPRT_InsertCommandName, this); poLOOP->AddParam(PARAM_INSERTPARAMTYPE1); poLOOP->AddParam(PARAM_INSERTPARAMTYPE2); poLOOP->AddParam(PARAM_INSERTPARAMTYPE3); poLOOP->AddParam(PARAM_INSERTPARAMTYPE4); Replace all Insert<somethings> with the correct information. The AddParameter calls below tell LOOP what type of parameters will get passed to your new command in the order they should get passed. Possible params to pass include: PARAM_INT Pass an int variable or int constant PARAM_FLOAT Pass a float variable or float constant PARAM_STRING Pass a string varaible or string constant PARAM_INT_REF Pass an int variable PARAM_FLOAT_REF Pass a float variable PARAM_STRING_REF Pass a string variable You can add as many or as a few parameters as you'd like. If your library has more than one command (most likely) simply copy and paste the code for all the commands you need. For example a library for two commands called #WriteInt and #WriteString might have a Link() member that looks like this: void LOOPLib_TestLib::Link(LOOP_LOOP* poLOOP) { poLOOP->NewCommand("WriteInt", &LOOPCMDPRT_WriteInt, this); poLOOP->AddParam(PARAM_INT); poLOOP->NewCommand("WriteString", &LOOPCMDPRT_WriteString, this); poLOOP->AddParam(PARAM_STRING); } The Info Object =============== As you write code for your command's function (replacing the //Inset command code here comment) you'll probably want to get information about the parameters that were passed to your command. You can get this information from the LOOP_Info variable. LOOP_Info includes the following members for reading parameters: int GrabInt (int iNumber); double GrabFloat (int iNumber); string GrabString (int iNumber); int GrabIntRef (int iNumber); int GrabFloatRef (int iNumber); int GrabStringRef (int iNumber); Set the iNumber param equal to which <int/float/string> is passed. For example if you had a command called drawline that took 4 ints for coods it might look like this: void LOOPLib_MyLib::LOOPCMD_DrawLine(LOOP_Info oInfo) { FunctionElsewhereThatDrawsALine ( oInfo.GrabInt(0), oInfo.GrabInt(1), oInfo.GrabInt(2), oInfo.GrabInt(4) ); } Realize that if you have a member that gets passed variables both by reference and by non-reference the iNumber parameter for non reference grabbing members also maps to the reference passed variables. For example if you had a command called PassVars that took i[i]i and you executed this in a script: #PassVars 10 20 30 Then: oInfo.GrabInt(0) = 10 oInfo.GrabInt(1) = 20 oInfo.GrabInt(2) = 30 oInfo.GrabIntRef(0) = An integer representing the location of the intref variable. This will be iLocation for the SetInt() member if you choose to use it. If you'd like to change the value of a variable passed by reference you can use the Variable Setting members of the LOOP_Info object: void SetInt (int iLocation, int iTo); void SetFloat (int iLocation, double dTo); void SetString(int iLocation, string sTo); Adding to this documentation ============================= Currently there's a lot of information that could be added to this doc but hasn't been mostly because I'm busy coding :-). But if you have something that you think should be added, or if you use LOOP and know just what this doc needs feel free to help me. E-Mail me, Andy Stone, at astone42@attbi.com. Also if you notice any dumb spelling or grammar mistakes that just stand out and bug you e-mail me where the are and I'll try to fix those too. Thanks. Errors, Bugs, Questions, Comments? ========================================== E-Mail me (Andy) or Eoin: Andy Stone (astone42@attbi.com) Appendix A: Template LOOP Library Class ======================================= templatelib.h --------------- #include <loop/loopmacros.h> #include <loop/info.h> #include <loop/loop.h> class LOOPLib_InsertNameHere { public: //- [Constuctors] - LOOP_LOOPLib_Standard(); void Link(LOOP_LOOP* poLOOP); //- [InsertCommandType] - LOOPPORT(LOOPCMDPRT_InsertCommmandName, LOOPLib_InsertNameHere, LOOPCMD_InsertCommandName); private: //Vars }; templatelib.cpp --------------- #include "templatelib.h" LOOPLib_InsertNameHere::LOOPLib_InsertNameHere() { } void LOOPLib_InsertNameHere::Link(LOOP_LOOP* poLOOP) { poLOOP->NewCommand("InsertCommandName", &LOOPCMDPRT_InsertCommandName, this); poLOOP->AddParam(PARAM_INSERTPARAMTYPE1); poLOOP->AddParam(PARAM_INSERTPARAMTYPE2); poLOOP->AddParam(PARAM_INSERTPARAMTYPE3); poLOOP->AddParam(PARAM_INSERTPARAMTYPE4); //Repeat for more commands... } void LOOPLib_InsertNameHere::LOOPCMD_InsertCommandName(LOOP_Info oInfo) { //Inset command code here }