ObjectScript API, интеграция с C++. Часть 3: подключение модуля с функциями на C++

в 12:59, , рубрики: api, javascript, Lua, ObjectScript, objectscript api, open source, php, Программирование, метки: , , , , ,

ObjectScript — новый объектно-ориентированный язык программирования с открытым исходным кодом. ObjectScript расширяет возможности таких языков, как JavaScript, Lua и PHP.

Часть 3: подключение модуля с функциями на C++

Давайте создадим свой модуль с фунциями, которые будут доступны в коде на OS. Назовем модуль my, он будет содержать две функции:

  • isdigit будет проверять, состоит ли строка, переданная в параметре, только из чисел
  • hash будет будет преобразовывать параметр в строку хеша по нашему алгоритму

Для начала нум нужно написать функции нашего будущего модуля на C++, первая my_isdigit:

static int my_isdigit(OS * os, int params, int, int, void*)
{
    OS::String str = os->toString(-params);
    int len = str.getLen();
    for(int i = 0; i < len; i++){
        if(!isdigit(str[i])){
            os->pushBool(false);
            return 1;
        }
    }
    os->pushBool(len > 0);
    return 1;
}

Небольшие пояснения на счет:

OS::String str = os->toString(-params);

Тут мы получаем первый параметр в виде строки. params — это количество параметров, которые были переданы в функцию из OS. -params — это относительный указатель в стеке на первый параметр. Если бы нам нужно было обратится ко второму параметру, то это выглядело бы так: os->toString(-params+1), к третьему — os->toString(-params+2) и т.д.

API на C++ предоставляет целый ряд функций для получения разных простых типов из стека: toFloat, toDouble, toInt, toString, toUserdata, popFloat и т.д.

Иногда в программе на C++ происходит большая работа со стеком. В этом случае, чтобы не держать в уме относительные смещения (т.к. они будут меняться, когда что-либо добавляется или вынимается из стека), удобно работать с абсолютным указателем на значение в стеке, его можно получить функцией getAbsoluteOffs(int offs) и работать тогда вот так:

int params_offs = os->getAbsoluteOffs(-params);
OS::String str = os->toString(params_offs); // перпый параметр
OS::String str = os->toString(params_offs+1); // второй

Это было лирическое отступление, теперь вторая фукнция нашего модуля — my_hash:

static int my_hash(OS * os, int params, int, int, void*)
{
    OS::String str = os->toString(-params);
    int i, len = str.getLen(), hash = 5381;
    for(i = 0; i < len; i++){
        hash = ((hash << 5) + hash) + str[i];
    }
    hash &= 0x7fffffff;
    char buf[16];
    for(i = 0; hash > 0; hash >>= 4){
        buf[i++] = "0123456789abcdef"[hash & 0xf];
    }
    buf[i] = 0;
    os->pushString(buf);
    return 1;
}

Небольшое пояснение на счет:

    return 1;

Это количество возвращаемых функцией значений. Функции готовы, теперь надо сообщить OS о том, что у нас есть новый модуль с такими функциями:

void initMyModule(OS * os)
{
    OS::FuncDef funcs[] = {
        {"isdigit", my_isdigit},
        {"hash", my_hash},
        {}
    };
    os->getModule("my");
    os->setFuncs(funcs);
    os->pop();
}

Теперь давайте проверим, как это работает, напишем программу на OS (main.os), которая для каждого значения тестового массива вызовет наши функции на C++:

for(var i, s in ["123", "12w", 1234, " df", "    "]){
    print("my.isdigit("..s..") = "my.isdigit(s)" my.hash("..s..") = "my.hash(s))
}

Программа выведет следующий результат:

my.isdigit(123) =       true     my.hash(123) =         bf9878b
my.isdigit(12w) =       false    my.hash(12w) =         f3a878b
my.isdigit(1234) =      true     my.hash(1234) =        f89c87c7
my.isdigit( df) =       false    my.hash( df) =         f48478b
my.isdigit(    ) =      false    my.hash(    ) =        5082f6c7

Полный текст исходников на C++:

#include "objectscript.h"
#include <ctype.h>

using namespace ObjectScript;

static int my_isdigit(OS * os, int params, int, int, void*)
{
    OS::String str = os->toString(-params);
    int len = str.getLen();
    for(int i = 0; i < len; i++){
        if(!isdigit(str[i])){
            os->pushBool(false);
            return 1;
        }
    }
    os->pushBool(len > 0);
    return 1;
}

static int my_hash(OS * os, int params, int, int, void*)
{
    OS::String str = os->toString(-params);
    int i, len = str.getLen(), hash = 5381;
    for(i = 0; i < len; i++){
        hash = ((hash << 5) + hash) + str[i];
    }
    char buf[16];
    hash &= 0x7fffffff;
    for(i = 0; hash > 0; hash >>= 4){
        buf[i++] = "0123456789abcdef"[hash & 0xf];
    }
    buf[i] = 0;
    os->pushString(buf);
    return 1;
}

void initMyModule(OS * os)
{
    OS::FuncDef funcs[] = {
        {"isdigit", my_isdigit},
        {"hash", my_hash},
        {}
    };
    os->getModule("my");
    os->setFuncs(funcs);
    os->pop();
}

void main()
{
    OS * os = OS::create();
    initMyModule(os);
    os->require("main.os");
    os->release();
}

Вы можете скачать исходники ObjectScript и пример из данной статьи по этой ссылке, открыть proj.win32examples.sln, проект add_user_module.

Другие релевантные статьи об ObjectScript:

P.S. Небольшой опус об OS::String

OS::String — это объект со строкой ObjectScript, который может быть сохранен в пользовательском коде. Такая строка сохраняет валидное значение все время своего существования. Чтобы получить указать на null-terminated строку, нужно воспользоваться функцией toChar(). ObjectScript хранит в памяти все разные строки в единственном экземпляре, поэтому OS::String — это константная строка, ее нельзя изменять ни при каких условиях. Но можно получить новую строку. OS::String реализует целый ряд конструкторов и оператор конкатенации, так что OS::String вполне можно создать из пользовательского кода и работать с ней (при необходимости).

Q: Что будет, если выполнить os->release() раньше, чем разрушаться строки OS::String, сохраненные в пользовательском коде?

A: OS::String захватывает экземпляр OS, давая ему понять, что он используется во внешнем коде. Поэтому os->release() не разрушит экземпляр OS, но он будет разрушен, когда последняя строка OS::String прекратит своё существование.

Автор: evgeniyup

Поделиться

* - обязательные к заполнению поля