o: Whats up? I guess if your here, that means you want to learn about Lua, a highly portable and really nifty language designed for scripting applications. To demonstrate it, we're going to go all the way from setting up a stub C++ environment to creating an OO networking class and a fully functional IRC Bot.
What we're gona use:Optionally, we'll also cover:
You know what sucks? Having just implemented the perfect feature, and boom, stupid cat pulls the power cord. So just in case, this next step is an optional one, to setup and use a remote SVN server to backup our files as we go. If you already know or just don't care, skip.
- Download a SVN client for your OS. In this case, I'm using OS X, so I used Versions which is a great little app.
- Create a new account at Beanstalk.
Once the account is created, add a new repository.
Now type in a descriptive name and URL. For this example, I used 'LuaBot.'
Leave the rest of the settings how they are, and hit 'Next' at the bottom of the page. Again, we're going to leave permissions alone and just hit 'Next.'
Now go to the overview page.
Copy your repository URL and keep it handy, we'll need it in the next step.
- Now to setup Versions. You should already have it downloaded, so double click it and launch.
We want to Create a new bookmark to a repository URL.
Put in anything you want for name, it doesn't really matter. Its just a local alias to the repo.
Now for the location field, remember that repository URL I had you copy? Paste it here.
Enter your Beanstalk username and password, and click 'Save.' If everything went A-OK you'll see a menu option appear on the left with the same name you put in. Click on it, go Browse, and then select 'Trunk'
Use the 'Checkout' button on the top-left and choose a location. It can be anywhere, really, but it should be an empty folder. Now we're gona ditch Versions for a bit and move over to XCode.
Now that we (Hopefully) have our subversion repo setup, its time to begin working on our actual bot! The first step is to open up XCode (Or any IDE) and create a new project.
For the type, we want a generic C++ tool.
The project name can again be anything you want it to be, but for the 'Project Directory,' select the location you used as your trunk in the Versions client if you did those steps.
Sweet! So now we have a project setup and ready to go. First thing we need is, well, Lua. I linked to the package you need up above, so download it and unpack on your desktop. Open up the directory it extracted and open the 'src' directory. Select everything and drag it to your XCode project. Make sure your settings are the same as the ones shown below and hit 'Add.'
This step copies the files into the project directory, instead of just linking to them.
Now we're going to jump back to Versions for a minute and do a commit.
You'll notice that a nice fat "1" has appeared, as well as an icon with a question mark, that represents our project folder.
As you work in XCode, Versions will track changes, new files, and deletions. You just have to confirm them and commit.
So, select the "LuaBot" (or whatever) folder, and in the top right, click 'Add.' The only thing that will visibly change is a green "+" icon will replace the question mark. A new option should have become available, in the top left, it will say 'Commit.' When we use commit, it creates a copy of everything you've added on Beanstalk. The key to using subversion is doing commits fairly often, and using descriptive, informative messages.
After you commit, it'll take a few seconds to upload everything, so go grab a coke.
All done! Time to go back to xcode...
Time to get started on the actual coding. Open up main.cpp and lets do some setup.
[highlight=c++]
/****Lua headers*********/
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
/************************/
/****Standard headers****/
#include <iostream>
#include <map>
#include <string>
/***********************/
/****Unix Headers*******/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
/***********************/
std::map<std::string, std::string> args; //A std::map that holds the arguements passed to the application.
int main (int argc, char * const argv[]) {
//First thing we're going to do is a really cheesy way of handling arguements, using a std::map and a loop.
//First, setup the defaults.
args["-s"] = "irc.freenode.net"; //What server to use
args["-p"] = "6667"; //What port to use
args["-u"] = "Anonymous"; //What username to use
args["-l"] = "Lord Sidius the Third!"; //What nickname to use(I don't know where I got this from, lol)
args["-scripts"] = "/Users/tyler/irc/"; //The directory that contains our Lua scripts.
args["-socket"] = "input.lua"; //The main execution script. This is the very first thing, and the only thing, the C++ stub runs.
//Now we loop through the args and fill in the array.
for(int i=1;i<argc;i+=2)
if(i+1 < argc)
args[argv[i]] = argv[i+1];
//We make a shortcut that contains the absolute path to our main script
std::string scriptpath = args["-scripts"] + "/" + args["-socket"];
//Now we create a variable to hold our Lua enviornments 'state'
lua_State *SocketParser = lua_open();
//Lua has a TON of builtins, so first we load those...
luaL_openlibs(SocketParser);
return 0;
}
[/highlight]
So lets take a look;
- We've included our Lua header files
- we've included a couple of C++ standard libraries
- and some headers you may not have seen before. These are the networking headers under Unix/Linux/OSX.
- We created a std::map, which just pairs up a name ("-s") with a value ("irc.freenode.net".)
- We populated the map with some default values
- We read in the apps arguments and also add them to the map.
- We create a new Lua 'state'
- We imported the standard Lua libraries into our 'state'
Onwards! Now lets get it executing something. Right after lua_openlibs, we include the following:
[highlight=c++]
//Try to load the file, but not execute it
int s = 0;
if ( !(s = luaL_loadfile(SocketParser,scriptpath.c_str())) ) {
//using lua_pcall, we make a 'protected' call. That means if it errors, it doesn't take everything down with it.
s = lua_pcall(SocketParser, 0, LUA_MULTRET, 0);
}
//Clean up
lua_close(SocketParser);
[/highlight]
Try building it now. All good? Yeap. It runs, and exits with status 0...but wait, what? We don't even HAVE a script, yet its supposedly working? This is because we used lua_pcall. It suppresses errors instead of terminating, so its up to us to check. Now our If statement expands to:
[highlight=c++]
if ( !(s = luaL_loadfile(SocketParser,scriptpath.c_str())) ) {
//using lua_pcall, we make a 'protected' call. That means if it errors, it doesn't take everything down with it.
s = lua_pcall(SocketParser, 0, LUA_MULTRET, 0);
}else{
//There was an error, so lets inform our master. Its broken up for comments.
std::cout << "\033[33m-- "; //Pretty colors on any linux terminal.
//Convert a Lua string entity to a printable C string, given the index. -1 is debug.
std::cout << lua_tostring(SocketParser, -1);
std::cout << "\033[39m\n"; //Ends pretty colors.
//Remove the error message from Lua's stack
lua_pop(SocketParser, 1);
}
[/highlight]
Hey, we're getting somewhere now. We now have a C++ stub that tries to load a file and execute it, so we're gona change files and work on input.lua, our main script.
Create a new file in whatever directory is specified by args["-scripts"] and call it input.lua.
[highlight=lua]
-- Part of the Lua tutorial by Tyler Kennedy, (C) 2009. Free for use in any project as long as this line remains.
-- Lua's a bit of an odd-ball. Just look at its comment syntax
-- Lets setup some globals...
MAJOR_VERSION = 0
MINOR_VERSION = 1
DEBUG_VERSION = 0
-- Stores our current channel
CURRENT_CHANNEL = ""
-- 'io' is an 'object' from the standard library. Remember that luaL_openLibs call earlier?
-- We can use '..' to concat a string, mixing types.
io.write("Welcome to LuaIRC " .. MAJOR_VERSION .. "." .. MINOR_VERSION .. "." .. DEBUG_VERSION .. "\n")
[/highlight]
Alright, at this point, if you go ahead and run you should have a fully functional stub and script that does....nothing much, just prints a message. So, lets expand Lua with some networking functions. Add these function prototypes to main.cpp:
[highlight=c++]
int sConnect(lua_State *state);
int sClose(lua_State *state);
int sRead(lua_State *state);
[/highlight]
Then place this at the bottom of main.cpp. Its a good idea to go over the comments, but you don't really need to know. Its a tutorial on Lua after all, not C++.
[highlight=c++]
//Our socket connection wrapper. All lua functions return the number of arguements on the stack.
int sConnect(lua_State *state){
//The handle returned by the OS that represents our socket
int SocketHandle;
//The port we'll be connecting to
int Port;
//The hostname we'll be connecting to
std::string Host;
//The following are structures used to store host information.
sockaddr_in serv_addr;
hostent *server;
//The first parameter is going to be a string - the host.
Host = lua_tostring(state, 1);
//The second paremeter is going to be a number - the port.
Port = lua_tonumber(state, 2);
//Try to create the socket, on the internet domain (AF_INET) as a stream socket (SOCK_STREAM) which defaults to TCP/IP.
if((SocketHandle = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
//Crap, something went wrong. Most likely the socket doesn't have permissions.
lua_pushnumber(state, 0);
return 1;
}
//Now we try to lookup the host. If this fails, it means it couldn't find the host.
if((server = gethostbyname(Host.c_str())) == NULL)
{
lua_pushnumber(state, 0);
return 1;
}
// Black magic. You really don't need to care about this, its outside the scope of this tutorial.
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr,(char *)&serv_addr.sin_addr.s_addr,server->h_length);
serv_addr.sin_port = htons(Port);
if(connect(SocketHandle,(const sockaddr*)&serv_addr,sizeof(serv_addr)) < 0)
{
lua_pushnumber(state, 0);
return 1;
}
// Start caring again.
// Now we're going to push the socket handle to the stack and return.
lua_pushnumber(state, SocketHandle);
// Since we're returning 1 thing, we return 1. If we had pushed two things, we would have returned 2.
// Using the right numbers here is essential for Lua's garbage management.
return 1 ;
};
//Clean up our socket.
int sClose(lua_State *state)
{
//This just closes the handle.
close(lua_tonumber(state, 1));
return 0;
}
//Read some data from a socket handle
int sRead(lua_State *state)
{
int n = 0;
char buffer[4096] = {0};
//Attempt to read in up to 4096 bytes at a time.
if((n = read(lua_tonumber(state, 1),buffer,sizeof(buffer)-1)) == -1)
{
lua_pushnumber(state,0);
return 1;
}
lua_pushnumber(state, n );
lua_pushlstring(state, buffer, n );
return 2;
}
[/highlight]
Well that's all nice and good, but how is Lua expected to know about these new functions? We register them with Lua using lua_register. Go back up to where we included the standard libraries (luaL_openlibs) and add this:
[highlight=c++]
//Add our custom functions.
lua_register(SocketParser,"_sConnect",sConnect);
lua_register(SocketParser,"_sClose",sClose);
lua_register(SocketParser,"_sRead",sRead);
[/highlight]
Lets try it out; Open up input.lua and add this at the bottom:
[highlight=lua]
io.write("Socket handle:" .. _sConnect("irc.freenode.net",6667) .. "\n");
[/highlight]
That's how easy it is to expand Lua with C/C++ functions. But..._sConnect, thats rather ugly isn't it? How about we use some of Lua 5.1's new features, and create something akin to a class to handle the network for us?
-- This is all for now! Stay tuned for part 2 --
Excellent tutorial! +rep!
nice tutorial .. +rep![]()
There are currently 1 users browsing this thread. (0 members and 1 guests)
Bookmarks