我的本职工作是为机器人开发应用程序,每个项目可能使用到的业务场景不一样,导致没做一次不同的项目都需要在业务层的代码中耗费很多时间。何不把业务层的代码逻辑与程序底层的逻辑分离开,让用户使用一种统一的语言对机器人行为进行编程。程序员就可以从业务层代码中解脱出来,参与更加底层的驱动开发。
Basic语言是一种简单的入门级语言,是一种非常方便客户掌握的语言,项目中交付一些示例代码,就可以让客户自行进行二次开发,定义机器人行为。
本次用到的basic语言解析器来自:
https://github.com/paladin-t/my_basic
MY-BASIC是用标准C语言编写的轻量级BASIC解释器,包含在两个文件中。它的目标是可嵌入,可扩展和可移植。它是一种动态类型化的编程语言,保留了结构化的语法,支持一种基于原型的编程(OOP)的样式,还通过lambda抽象实现了一种功能范例。内核写入C源文件和关联的头文件中。既可以将其用作独立的解释器,也可以将其嵌入C,C ++,Java,Objective-C,Swift,C#等现有项目中,并且可以通过添加自己的脚本接口进行完全自定义。
这里有一个使用my_basic进行二次开发的例子:
https://github.com/jacmoe/nasl/tree/fd1ea456d8d13c5e4d9cc5b5ae1eb6be792e0049
里面有基本的函数绑定,my_basic解析器的初始化和使用等案例。
1. 从basic中传入值
/* Register an API function to a MY-BASIC environment */
int mb_register_func(struct mb_interpreter_t* s, const char* n, mb_func_t f) {
if(!s || !n || !f) return 0;
return _register_func(s, (char*)n, f, false);
}
使用方式:
//进行绑定
void static basic_arm_script_init() {
/**
* 用于绑定basic函数和用户定义函数
* 1. 参数 s ,全局上下文参数
* 2. 参数 n ,basic中的函数名
* 3. 参数 f ,用户定义的函数的入口地址
*/
mb_register_func(basic_script_get_interpreter(), "startJob", _start_job);
}
//对应用户定义的函数
static int _start_job(struct mb_interpreter_t *s, void **l) {
char *job_name_c = nullptr;
int result = MB_FUNC_OK;
//确定送入的指针非空
mb_assert(s && l);
//开左边括号
mb_check(mb_attempt_open_bracket(s, l));
//尝试取字符串值
mb_check(mb_pop_string(s, l, &job_name_c));
//关括号 完成解析
mb_check(mb_attempt_close_bracket(s, l));
//do something
return result;
}
在basic文件中调用
startJob ("job name");
2.给basic返回值
/* Register an API function to a MY-BASIC environment */
int mb_register_func(struct mb_interpreter_t* s, const char* n, mb_func_t f) {
if(!s || !n || !f) return 0;
return _register_func(s, (char*)n, f, false);
}
使用方式:
//进行绑定,参数见1
void static basic_arm_script_init() {
mb_register_func(basic_script_get_interpreter(), "getVar", _getVarFormArm);
}
//对应用户定义的函数
static int _getVarFormArm (struct mb_interpreter_t *s, void **l){
int result = MB_FUNC_OK;
int var_index;
mb_assert(s && l);
mb_check(mb_attempt_open_bracket(s, l));
mb_check(mb_pop_int(s, l, &var_index));
mb_check(mb_attempt_close_bracket(s, l));
//给basic放入返回值,这里直接+1
mb_check(mb_push_int(s,l,var_index+1));
return result;
}
在basic文件中调用,就可以把数值打出来。
print getVar (25);
3. 使用collection双向传值
使用collection进行传值,目前完成了list传值的内容,dict还未完成
basic代码中,初始化一个list数据格式,送入getMutiVar函数中,并从中返回一个list
index_array = list(1, 2, 3, 4,5,6,7,8,9)
l = getMutiVar (index_array)
在c++代码中:
static int _getMutiVar(struct mb_interpreter_t *s, void **l) {
int result = MB_FUNC_OK;
mb_value_t val;
mb_make_nil(val);
mb_assert(s && l);
mb_check(mb_attempt_open_bracket(s, l));
// 从传入参数中拿到list的元数据,下面通过build_vector_from_val转换为vector
mb_check(mb_pop_value(s, l, &val));
mb_check(mb_attempt_close_bracket(s, l));
std::vector<int> list;
//通过build_vector_from_val转换为vector
BasicUtil::build_vector_from_val<int>(s, l, val, list);
//do something
//下面是把vector转换为元数据返回给basic
// 新建一个coll表
mb_value_t coll;
coll->type = MB_DT_LIST;
mb_init_coll(s, l, coll);
//通过build_val_from_vector转换为coll
BasicUtil::build_val_from_vector(s, l, feedback, &coll);
//将数据传入my_basic 即可在上层得到返回
mb_push_value(s, l, coll);
return result;
}
/**
* 用于把获取的元数据转换成vector的形式
* 1. 参数 s ,全局上下文参数
* 2. 参数 l ,也是上下文相关的,不知道是干嘛的
* 3. 参数 val ,list元数据,在mb_pop_value()中获取
* 4. 参数 vtr , 用于接收结果的vector
*/
static void
build_vector_from_val(struct mb_interpreter_t *s, void **l, mb_value_t val, std::vector<T> &vtr) {
mb_value_t idx;
//新建迭代器并初始化
mb_value_t element = mb_value_t();
int size = 0;
mb_count_coll(s, l, val, &size);
for (int i = 0; i < size; i++) {
//推进迭代器,并且通过mb_get_coll获得元数据中具体的数值
mb_make_int(idx, i);
mb_get_coll(s, l, val, idx, &element);
vtr.push_back(element.value.integer);
}
}
static void
build_val_from_vector(struct mb_interpreter_t *s, void **l, const std::vector<T> &vtr, mb_value_t *val) {
int feedback_index = 0;
for (auto item : vtr) {
//推进迭代器,并且通过mb_set_coll获得元数据中具体的数值
mb_value_t mb_feedback_index;
mb_value_t feedback_value;
mb_make_int(mb_feedback_index, feedback_index++);
mb_make_int(feedback_value, item);
mb_set_coll(s, l, *val, mb_feedback_index, feedback_value);
}
}
4. 使用usertype保存用户自定义的上下文参数
该功能用于在basic中保存复杂的数据类型,该数据在源码中是以对象指针的方式保存的,如果处理不慎,可能
引起数据被提前释放或者是内存泄漏等问题。建议专门写一些api进行内存管理
另外,由于是对象指针,usertype在basic中无法直接进行修改、查看等操作,需要提供专门对操作函数。
在basic中:
blitbuf = buffer_create()
buffer_clear(blitbuf, ORANGE)
该程序段返回一个blitbuf的用户类型,用户无法直接在basic中读取到blitbuf内的内容和数据结构,于是作者提供了多个
api帮助basic用户对该结构进行操作,例如:
BUFFER_CREATE
MAIN_BUFFER
SET_MAINBUFFER
BUFFER_DESTROY
BUFFER_BLIT
BUFFER_CLEAR
SUB_BUFFER
BUFFER_SET_PIXEL
BUFFER_GET_PIXEL
在c++代码中,实现一个_BUFFER_CREATE
static int _buffer_create(struct mb_interpreter_t *s, void **l) {
int result = MB_FUNC_OK;
auto *buffer = new Buffer;
mb_assert(s && l);
mb_check(mb_attempt_open_bracket(s, l));
mb_check(mb_attempt_close_bracket(s, l));
buffer->width = 100;
buffer->height = 60;
//do something
//强制转换为空指针,该函数不会进行拷贝,不要调用delete删除指针内容
mb_check(mb_push_usertype(s, l, (void *) buffer));
//除非在析构函数中,否则不能调用delete 这样会销毁在basic中存放对basic
// delete buffer;
return result;
}
在c++代码中,实现一个_BUFFER_CLEAR
static int _buffer_clear(struct mb_interpreter_t *s, void **l) {
int result = MB_FUNC_OK;
//Buffer是一个定义的结构体,内容自定即可
auto *buffer = new Buffer;
//用于接收数据的空指针
void *up = 0;
mb_assert(s && l);
mb_check(mb_attempt_open_bracket(s, l));
//取出元数据
mb_check(mb_pop_usertype(s, l, &up));
mb_check(mb_attempt_close_bracket(s, l));
if (!up)
return MB_FUNC_ERR;
//强制类型转换
buffer = (Buffer *) up;
std::cout << buffer->width << std::endl;
std::cout << buffer->height << std::endl;
// do something
//即可在对buffer数据进行操作
//除非在析构函数中,否则不能调用delete 这样会销毁在basic中存放basic中存放的buffer
// delete buffer;
return result;
}