好长时间以来,一直都想在网上找一个C风格并且可以与C/C++配合良好的脚本语言。可是无奈,用过很多的脚本,lua,Python、 ruby等等,但总是感觉让我不是很满意(自己水平差,没办法),除了LUA一直在使用以外,其它脚本都是一看就放弃了。
越来越感觉用lua不顺手,它的书写风格让我感觉不舒服,可是,功夫不负有心人,7月底的时候,让我找到了ChaiScript,简单的看了介绍就赶紧的用了下试试……
由于本人菜鸟,有点发现总是不敢独享,于是鉴于chaiScript没有现成的使用文档,所以就写了这个破烂文章,仅为帮助像我一样菜的朋友,故高手略过……
本人也只是简单的测试和使用了一下,因此很多的东西摸得不是很透,所以,倘若我写的内容有错误或者理解不多的地方,还请各位朋友帮忙指正,以免误人子弟。
besterChen
2009年7月29日
ChaiScript的正常运行需要有Boost的支持,所以,我们需要先在配置ChaiScript环境之前,安装和配置好boost库,下面我将分开介绍boost和ChaiScript的配置。
1、Boost库的安装和使用
大家可以官网(http://www.boost.org/)来获取最新的boost库代码及Jam编译器。
将bjam.exe复制到boost根目录下:
将命令行也定位boost根目录下。
编译时通过设置参数可以指定编译器和编译版本以及存放路径这里没哟编译python.
bjam --toolset=msvc-8.0 --prefix=x:\boost stage
这个命令将会生成release版的lib和dll文件
bjam --toolset=msvc-8.0 --prefix=x:\boost debug stage
这个命令将会生成带gd字符的debug版的lib和dll文件
bjam --tooset=msvc-8.0 --prefix=x:\boost debug runtime-link=static stage
这个命令将会生成带sgd字符的静态debug版的lib和dll文件
如果不是用--prefix参数将默认生成在c:\boost目录下
编译完成后可以建立一个bat文件自动将生成的lib和dll文件拷贝到lib目录下面
cplib.bat
dir /W/S/B *vc80*.lib,*vc80*.dll >liblist
if exist lib (echo exist lib fold) else (mkdir lib)
for /f %%x in (liblist) do @copy "%%x" lib\
拷贝之后可以做一下清理工作,同个建立一个bat文件来自动清理编译过程产生的.obj文件
delobj.bat
del /s/q *.obj
将这两个文件放在x:\boost目录下执行就可以了。
配置环境变量
Tools -> Options -> Projects and Solutions -> VC++ Directories 在Library files加上x:\boost\lib
在Include files加上x:\boos
测试程序:
#include <iostream>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string_regex.hpp>
using namespace std;
using namespace boost;
int main()
{
string s = " Hello boost!! ";
trim(s);
cout << s << endl;
getchar();
std::string regstr = "a+";
boost::regex expression(regstr);
std::string testString = "aaa";
// 匹配至少一个a
if( boost::regex_match(testString, expression) )
{
std::cout<< "Match" << std::endl;
}
else
{
std::cout<< "Not Match" << std::endl;
}
}
说明:本库的配置方法引用与http://paul216.blog.hexun.com/29507251_d.html
2、ChaiScript运行环境的配置。
ChaiScript的使用方法比较简单,只要在IDE中配置一下包含目录就可以了。
首先,我们从我们下载回来的文件中把Include拷贝到指定的一个库文件夹中,比如我的目录是:D:\OtherLib\ChaiScript,如下图:
然后在VC环境中点击:
Tools -> Options -> Projects and Solutions -> VC++ Directories 在Include files加上D:\OtherLib\ChaiScript\Include
这样我们就可以开始写可以嵌ChaiScript的程序了。
关于ChaiScript的基础语法讲解并不像如何使用ChaiScript那样模糊,在官网上已经讲的很详尽清楚了,只不过是英文版本,我就简单的翻译一下,我尽量建议大家看英文版本,因为我的英文水平我自己都不放心……
1、 条件模块(if)
ChaiScript支持If语句,但是需要说明:"else if"是一个单独的关键字而不是两个分开的关键字。大概的用法格式如下:
var i = 2
if (i < 2)
{
print("less than 2")
}
else if (i == 2)
{
print("equal to 2")
}
else
{
print("more than 2")
}
2、 循环语句
ChaiScript跟C + +的循环风格差不多有两种:for循环和while循环。大概的用法如下:
1) For 循环:
for (var i = 0; i < 10; ++i)
{
print("i: " + i.to_string())
}
2) While 循环:
var i = 0
while (i < 10)
{
print("i: " + i.to_string())
++i
}
说明:跟C++一样,在ChaiScript中也可以使用break关键字来中断循环。
3、 容器(Vectors、Maps)
ChaiScript支持少数容器类型,也允许用简捷的写法创建Vectors和Maps。比如:
1) Vectors:
var x = [1, 2, 3]
print(x[1])
2) Maps:
var x = ["bob":1, "fred":2]
print(x["fred"])
同样,我们也可以通过一个值的范围来快速的创建Vector容器,例如:
var x = [1..10]
print(x)
4、 函数
在ChaiScript中定义函数有两种书写方法。
1) 使用“def”关键字定义静态函数,在全局范围内都可见。比如:
def add_elems(x, y)
{
x + y
}
print(add_elems(5, 10))
2) 创建一个匿名函数,通常赋值给一个变量来使用。
var add_elems = fun(x, y) { x + y }
print(add_elems(5, 10))
5、 返回值
函数的返回值也可以有两种写法。
一种是跟C++一样,明确的使用关键字”return”返回,用法跟C++一样,例如:
def five()
{
return 5
}
print(five())
另一种是隐式的返回函数结果,就是当一个函数没有明确的使用return语句返回时,最后的这个数值就作为函数的返回值,比如:
def five()
{
5
}
print(five())
这样定义函数的返回值与上一种写法是一样的。
6、 方法(Methods)
其实ChaiScript并不支持什么方法,只是利用了函数的一个被叫作“syntactic sugar”的技术代替的方法。例如:
5.to_string()
to_string(5)
这就是:值在“.”的左边作为第一个参数命名的函数而已,没有别的区别。
7、 派遣函数(Dispatch Functions)
ChaiScript允许函数重载,使用派遣函数可以自动的使用相应的函数已达到相应的功能。比如说,你想从一堆数中打印出正确的复数,可以这样写代码:
def print_num(x) : x == 0 || x > 1
{
print(x.to_string() + " units")
}
def print_num(x) : x == 1
{
print(x.to_string() + " unit")
}
print_num(2)
print_num(1)
print_num(0)
在VC工程中使用ChaiScript
1、一个chaiscript的测试程序
在我们下载回来的源代码中有一个TestExample工程的例子,我不知道它是在VS的多少版本下创建的,改了一下它的工程文件,在2005下编译过了,但是它目录设置的很杂,所以,我们在这里自己新建一个控制台工程,把它的源代码copy过来,我们研究下,源代码如下:
// TestExample.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
void log(const std::string &msg)
{
std::cout << "[" << boost::posix_time::microsec_clock::local_time() << "] " << msg << std::endl;
}
void log(const std::string &module, const std::string &msg)
{
std::cout << "[" << boost::posix_time::microsec_clock::local_time() << "] <" << module << "> " << msg << std::endl;
}
struct System
{
std::map<std::string, boost::function<std::string (const std::string &) > > m_callbacks;
void add_callback(const std::string &t_name,
const chaiscript::Proxy_Function &t_func)
{
m_callbacks[t_name] = chaiscript::functor<std::string (const std::string &)>(t_func);
}
void do_callbacks(const std::string &inp)
{
log("Running Callbacks: " + inp);
for (std::map<std::string, boost::function<std::string (const std::string &)> >::iterator itr = m_callbacks.begin();
itr != m_callbacks.end();
++itr)
{
log("Callback: " + itr->first, itr->second(inp));
}
}
};
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
ChaiScript chai;
//创建一个新的system 对象
System system;
//把它添加到chaiscript引擎中
chai.add(var(&system), "system");
//注册System结构体的两个方法.
chai.add(fun(&System::add_callback), "add_callback");
chai.add(fun(&System::do_callbacks), "do_callbacks");
// 让我们用chaiscript给我们的system结构添加一个新的回调函数。
// 在Chaiscript中创建函数"{ 'Callback1' + x }" 并通过我们C++程序System结构中的add_callback函数
// 将这个chaiscript脚本函数转换到boost::function,
// so it can be handled and called easily and type-safely
chai.eval("system.add_callback(\"#1\", fun(x) { \"Callback1 \" + x });");
// 由于我们在chaiscript引擎中共享了“system”对象
// 所以我们可以同时在C++代码中和Chaiscript脚本中使用这个对像
system.do_callbacks("TestString");
chai.eval("system.do_callbacks(\"TestString\");");
// 日志函数被(log)重载过了, 因此我们必须得同时C++编译器,我们要注册那个版本的日志函数。
// 这样,唯一的一个方法就是用typedef创建一个函数指针,然后注册这个函数指针就可以了
typedef void (*PlainLog)(const std::string &);
typedef void (*ModuleLog)(const std::string &, const std::string &);
chai.add(fun(PlainLog(&log)), "log");
chai.add(fun(ModuleLog(&log)), "log");
chai.eval("log(\"Test Message\")");
// 由于eval函数重载了操作符(),因此我们可以直接使用chai关键字而省去eval方法。
chai("log(\"Test Module\", \"Test Message\");");
//最后, 我们可以注册任何一个boost::function 作为sys对象的函数, 按照这样来说,
//我们可以给system对象添加一个受限的成员函数,例如:
chai.add(fun(boost::function<void ()>(boost::bind(&System::do_callbacks, boost::ref(system), "Bound Test"))), "do_callbacks");
//调用限制版本的do_callbacks函数
chai("do_callbacks()");
boost::function<void ()> caller = chai.functor<void ()>("fun() { system.do_callbacks(\"From Functor\"); }");
caller();
//如果我们想从所有的调用中得到一个类型安全的返回值,那我们可以使用eval的模板,如下:
int i = chai.eval<int>("5+5");
std::cout << "5+5: " << i << std::endl;
//添加一个新的变量
chai("var scripti = 15");
//我们甚至可以操作system的变量
int &scripti = chai.eval<int &>("scripti");
std::cout << "scripti: " << scripti << std::endl;
scripti *= 2;
std::cout << "scripti (updated): " << scripti << std::endl;
chai("print(\"Scripti from chai: \" + to_string(scripti))");
//要做到: 在需要时添加一个直接处理Boxed_Values 的实例。
//在堆栈上创建和使用函数
int x = chai.functor<int (int, int)>("fun (x, y) { return x + y; }")(5, 6);
log("Functor test output", boost::lexical_cast<std::string>(x));
//在需要的时候,可以创建自己的容器. 目前可以支持大多数的std::vector 和std::map
chai.add(bootstrap::vector_type<std::vector<int> >("IntVector"));
return 0;
}
我这破英文底子,哎~~我硬着头皮大概翻译了一下,方便比我还差的朋友阅读,翻译的肯定有不对的,大家可以参考源程序的英文注释,这里还望大家多多指正。
根据这些注释,我想看懂这个程序应该不难的。这样,我们也就知道ChaiScript的基础用法了。
下面我将更详细的讲述着程序中的知识点。
2、将ChaiScript添加到现有的VC工程中。
在ChaiScript文件夹的Readme.txt文件告诉我们:
² 把ChaiScript的include目录添加到工程的包含目录中。
² 在我们的工程原文件中包含 "#include <chaiscript/chaiscript.hpp> 。
² 在我们的工程中创建ChaiScript引擎的实例.
例如, 创建一个新的名为“chai”的引擎可以这样编写代码:
using namespace chaiscript;
ChaiScript chai;
或者写成:
chaiscript::ChaiScript chai;
3、ChaiScript脚本与C++相互调用
1) 在C++中调用/执行ChaiScript代码
现在,我们有两种方法来调用/执行ChaiScript脚本代码:
² 第一种方法:直接在C++代码中编写并执行ChaiScript代码,例如:
//添加一个新的变量
chai("var scripti = 15");
甚至我们都可以直接获取脚本的表达式的值,例如:
int i = chai.eval<int>("5+5");
² 第二种方式是直接调用ChaiScript脚本文件来执行。例如:
我们可以编写脚本如下:
// HelloWorld.chai
print("hello chaiscript world...")
编写控制台程序如下:
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
ChaiScript chai;
chai.eval_file(".\\HelloWorld.chai\0"); // 调用我们刚写的脚本
return 0;
}
程序执行结果如下:
2) 在C++中使用脚本的变量(对象)
//添加一个新的变量
chai("var scripti = 15");
//我们甚至可以操作system的变量
int &scripti = chai.eval<int &>("scripti");
std::cout << "scripti: " << scripti << std::endl;
scripti *= 2;
std::cout << "scripti (updated): " << scripti << std::endl;
chai("print(\"Scripti from chai: \" + to_string(scripti))");
上面这段代码,相信大家应该都有印象吧,这段代码很简明的描述了如何在C++中操作ChaiScript中的变量的方法:使用引用。
3) 在C++中调用ChaiScript中定义的函数。
总是感觉ChaiScript::functor这个模板的功能很神奇哈,在我们一开始的测试程序中,看到了关于它的很多神奇的用法,由于我的个人能力有限,值说明一下它的基本用法,至于其它的,大家可以自己去摸索。
先看下functor的声明吧:
template<typename FunctionType>
boost::function<FunctionType> functor(const std::string &script)
{
return chaiscript::functor<FunctionType>(eval(script));
}
很明白了吧,我们这个字符串就相当于直接在ChaiScript写脚本了,没有什么别的估计的,直接调用我们编写的函数就好,OK,不多废话,在脚本中创建函数如下:
// TestFor.chai
def TestFor()
{
for (var i = 0; i < 10; ++i)
{
print("i: " + i.to_string())
}
}
编写我们的C程序如下:
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
ChaiScript chai;
chai.eval_file(".\\TestFor.chai\0");
boost::function<void ()> TestFor = chai.functor<void ()>("TestFor"); // 关联我们的脚本函数
TestFor(); // 调用脚本函数
return 0;
}
程序执行结果如下:
4) 在脚本中调用C++函数/ 对象
到现在,我们应该对ChaiScript有一定得了解了,应该知道,如果要在脚本中使用C++对象或者使用C++的函数应该先把函数注册到ChaiScript引擎中。
在ChaiScript1.1中注册函数或者对象的方法已经与1.0版本发生的一点变化,1.1版本注册函数/对象的语法格式如下:
注册对象的语法格式:
/创建一个新的system 对象
System system;
//把它添加到chaiscript引擎中
chai.add(var(&system), "system");
注册函数的语法格式:
chai.add(fun(&MyClass::function), "method");
上述格式注册的是我们的类成员函数,如果要注册一个独立的函数,那可以先声明一个这个函数的指针然后在注册,如下例子:
我们可以编写脚本如下:
// HelloWorld.chai
log("hello chaiscript world...")
编写控制台程序如下:
#include <iostream>
#include <chaiscript/chaiscript.hpp>
#include <chaiscript/dispatchkit/function_call.hpp>
#include <boost/function.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/regex.hpp>
void log(const std::string &msg)
{
std::cout << "[" << boost::posix_time::microsec_clock::local_time() << "] " << msg << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
using namespace chaiscript;
typedef void (*PlainLog)(const std::string &);
ChaiScript chai;
chai.add(fun(PlainLog(&log)), "log"); // 注册C++函数,以便在脚本中调用它
chai.eval_file(".\\HelloWorld.chai\0"); // 调用我们刚写的脚本
return 0;
}
程序执行结果如下:
好,到这里,在VC中使用ChaiScript脚本的内容就讲完了,其实重点就是几个ChaiScript中的几个API的使用,如:ChaiScript::add、ChaiScript::eval、ChaiScript::eval_file、 ChaiScript::functor等等。
这些ChaiScript API更多更详细的介绍,请大家关注:http://www.chaiscript.com/node/28