📜 ⬆️ ⬇️

Adding scripting to a program using Lua


Lua is a powerful, fast, easy, embedded scripting language. With it, you can easily and quickly add scripting support to your program.
This may be necessary in cases where you want to allow users to make an independent customization (customization) of your program, when you do not want to recompile the entire project, when making any changes to the logic of the program, or you want to separate the work on the engine and work on logic between developers (for example, when writing games).

In this article, using a simple program, I want to set an example for embedding Lua into your project.

There are a lot of examples of programs that use Lua. Not a complete list of programs that use Lua, you can see here the Lua Wiki and here Wikipedia

I will give an example of a simple program that takes as an argument the path to a directory, lists files and subdirectories, transfers them to the script. The script, using regular expressions, looks for a match in the paths of the transferred files and, if it finds it, renames the file according to certain rules.
')
I created an example in Visual Studio under Windows. Despite this, the above code, with the exception of a few functions (enumerating files, renaming a file) that are specific to Windows, after a small adaptation will work on other platforms as well. Lua is a cross-platform scripting language.

To begin with, visit the official website and download Lua for your platform . For Windows, this archive is suitable, which includes linker libraries, dynamic libraries and Lua header files.

Unpack, specify in Visual Studio settings the paths to header files and linker libraries ( Tools -> Options -> Projects and Solutions -> VC ++ Directories ).

Briefly, step by step, adding Lua scripting to a project occurs in several steps:

Create a console project. You can call it whatever you like. First, add the header files:

extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}

Why extern "C"? Lua is written in ANSI C, if you try to include files without extern "C", then we get a lot of errors, such as:

unresolved external symbol "char const * __cdecl lua_tolstring(struct lua_State *, int ,unsigned int *)" (?lua_tolstring@@YAPBDPAUlua_State@@HPAI@Z)

This is because calling conventions in C differ from conventions in C ++.

Do not forget to connect the linker library:

#pragma comment (lib, "lua51.lib")

Now you need to declare and initialize an instance of Lua interpreter.

//
lua_State *g_LuaVM = NULL;

int _tmain( int argc, _TCHAR* argv[])
{
//
g_LuaVM = lua_open();
...
//
lua_close(g_LuaVM);
}

Now we need to declare and implement two functions that will be called from Lua. The first one will look for matching the file name to a regular expression:

int LuaMatchString(lua_State *luaVM)

The second one renames the file:

int LuaRenameFile(lua_State *luaVM)

Let us dwell on the implementation of the second:

//
// 1, 0

int LuaRenameFile(lua_State *luaVM)
{
// Lua
int argc = lua_gettop(luaVM);

// -
if (argc < 2)
{
cerr << "RenameFile - wrong number of arguments!" << endl;
// 0 Lua
lua_pushnumber(luaVM, 0);

//
return 1;
}

// ,
// 0 ( )

if (!lua_isstring(luaVM, 1) || !lua_isstring(luaVM, 2))
{
cout << "RenameFile - invalid arguments!" << endl;
lua_pushnumber(luaVM, 0);

//
return 1;
}

// ,
// - - 1, 0, ++

string strSource = lua_tostring(luaVM, 1);
// ,
string strDestination = lua_tostring(luaVM, 2);

strDestination = strSource.substr(0, strSource.rfind('\\') + 1) + strDestination;

int nResult = ( int )::MoveFileEx(strSource.c_str(), strDestination.c_str(), MOVEFILE_REPLACE_EXISTING|MOVEFILE_WRITE_THROUGH);

// Lua MoveFileEx
lua_pushnumber(luaVM, nResult);

//
return 1;
}

As you can see, nothing complicated: we check the number of arguments passed, check the type of the arguments, extract, rename the file and return the result.

Now we need to let Lua know about the exported functions, this is done simply:

lua_register(g_LuaVM, "RenameFile", LuaRenameFile);
lua_register(g_LuaVM, "MatchString", LuaMatchString);

RenameFile and MatchString are the names of the functions that will be “visible” in the script.

Create a script that does all the work:

--
--
-- , -- Lua

function onFileFound(fileName)
patt = MatchString('103([0-9]{1,12}).txt', fileName)

-- patt
-- - ([0-9]{1,12})
-- nil

if patt ~= nil then
-- , 103 XXX
-- : 103180998.txt, XXX180858.txt

RenameFile(fileName, 'XXX' .. patt .. '.txt')
end
end

To make it completely clear, I provide a piece of code that calls this function.

// onFileFound
lua_getglobal(g_LuaVM, "onFileFound");
// (fileName )
lua_pushstring(g_LuaVM, strFilePath.c_str());

// onFileFound
if (lua_pcall(g_LuaVM, 1, 0, 0) != 0)
{
// ,
// 0,
// lua_tostring(g_LuaVM, -1)

cerr << "Error calling function onFileFound: " << lua_tostring(g_LuaVM, -1) << endl;
}

It remains only to download the script from our program:

BOOL LoadScript()
{
//
CHAR szScriptPath[1024] = {0};
GetModuleFileName(NULL, szScriptPath, _countof(szScriptPath) - 1);
TCHAR *szSlashPos = strrchr(szScriptPath, '\\');
if (szSlashPos != NULL)
{
szScriptPath[szSlashPos - szScriptPath + 1] = '\0';
}
strcat_s(szScriptPath, _countof(szScriptPath), _T("script.lua"));

//
int s = luaL_loadfile(g_LuaVM, szScriptPath);

// 0 -
// lua_tostring(g_LuaVM, -1)

if (s != 0)
{
cout << "Error while loading script file!" << endl << "Error: " << lua_tostring(g_LuaVM, -1) << endl;

return FALSE;
}

//
s = lua_pcall(g_LuaVM, 0, LUA_MULTRET, 0);

return TRUE;
}

As you can see, embedding Lua scripting in the program is, in fact, a trivial task and at the same time provides a wide field for creativity.

Project sources

Below is a list of resources where you can read about Lua in more detail.

Official site
Live demo
Russian documentation
I love Lua. I love lua
Creating embedded scripts in the Lua language

Source: https://habr.com/ru/post/74382/


All Articles