[Статья] SMC Parser, как смысл жизни

Nico Yazawa

Бывший MrChester =(
Сообщения
326
Реакции
303
[SourcePawn] Статья - Работа с SMC Parser

Писал статью в стиле уроков от @R1KO, чтобы было удобно и приятно ее читать.

Оригинальная статья
textparse.inc
textparse · SourceMod Scripting API Reference

SMC Parser - универсальный инструмент (парсер), основанный на каллбеках, позволяющий читать файлы конфигурации SourceMod.
SMC Parser проходит по всему конфигу сверху вниз, читая все секции и ключи в них. Каждая новая секция или ключ это отдельный вызов каллбека.

Чем SMC Parser отличается от KeyValues?
1)
SMC Parser не может обрабатывать отдельные записи (то есть ключ без значения).
2) SMC Parser поддерживает многострочные комментарии (/* комментарий /*), когда KeyValues - нет.
3) SMC Parser прочитает все категории и ключи по порядку, когда в KeyValues нельзя узнать значение двух ключей с одинаковым названием.
4) SMC Parser не поддерживает изменения самого конфиг файла, когда в KeyValues это реализуемо.

Методы и свойства для для работы с SMC Parser можно найти здесь: SMCParser · textparse · SourceMod Scripting API Reference

Давайте рассмотрим их подробнее:
  • Методы SMC Parser:
    • SMCParser - Создает объект типа SMC Parser.
      C-подобный:
      SMCParser hParser = new SMCParser(); // Создание нового объекта SMCParser
    • GetErrorString - Получает строку ошибки SMCError.
      C-подобный:
      char error[128]; // Создаем строку для хранения ошибки
      SMC_GetErrorString(result, error, sizeof(error)); // Записываем ошибку в нашу строку
    • ParseFile - Запускает инструмент (парсер).
      C-подобный:
      SMCError ParseFile(const char[] file, int& line, int& col)
      • const char[] file - Путь к конфиг файлу
      • int& line - Номер строки, на которой находится парсер
      • int& col - Номер столбца, на котором находится парсер
    • C-подобный:
      char file[128]; // Условно, наш конфиг файл, который мы будем парсить
      int line = 0; // Буффер, в котором будет храниться строка, на которой находится парсер
      int col = 0; // Буффер, в котором будет храниться столбец, на котором находится парсер
      hParser.ParseFile(file, line, col); // Запускаем наш парсер
  • Свойства SMC Parser:
    • OnStart - Устанавливает каллбек, который вызывается в начале работы парсера.
      C-подобный:
      // Такое в коде писать не надо, тут показано присваивание каллбека к парсеру и как должен выглядеть каллбек.
      hParser.OnStart = SMC_ParseStart;
      typedef SMC_ParseStart = function void(SMCParser smc);
    • OnEnd - Устанавливает каллбек, который вызывается в конце работы парсера.
      C-подобный:
      // Такое в коде писать не надо, тут показано присваивание каллбека к парсеру и как должен выглядеть каллбек.
      hParser.OnEnd = SMC_ParseEnd;
      typedef SMC_ParseEnd = function void(SMCParser smc, bool halted, bool failed);
    • OnEnterSection - Устанавливает каллбек, который вызывается, как только парсер открыл новую секцию.
      C-подобный:
      // Такое в коде писать не надо, тут показано присваивание каллбека к парсеру и как должен выглядеть каллбек.
      hParser.OnEnterSection = SMC_NewSection;
      typedef SMC_NewSection = function SMCResult(SMCParser smc, const char[] name, bool opt_quotes);
    • OnLeaveSection - Устанавливает каллбек, который вызывается, как только парсер покинул текущую секцию.
      C-подобный:
      // Такое в коде писать не надо, тут показано присваивание каллбека к парсеру и как должен выглядеть каллбек.
      hParser.OnLeaveSection = SMC_EndSection;
      typedef SMC_EndSection = function SMCResult(SMCParser smc);
    • OnKeyValue - Устанавливает каллбек, который вызывается, когда парсер нашел новый ключ и значение к нему.
      C-подобный:
      // Такое в коде писать не надо, тут показано присваивание каллбека к парсеру и как должен выглядеть каллбек.
      hParser.OnKeyValue = SMC_KeyValue;
      typedef SMC_KeyValue = function SMCResult(SMCParser smc, const char[] key, const char[] value, bool key_quotes, bool value_quotes);
    • OnRawLine - Устанавливает каллбек, который вызывается, когда парсер обнаружил новую строку? Никогда этим не пользовался .-.
      C-подобный:
      // Такое в коде писать не надо, тут показано присваивание каллбека к парсеру и как должен выглядеть каллбек.
      hParser.OnRawLine = SMC_KeyValue;
      typedef SMC_RawLine = function SMCResult(SMCParser smc, const char[] line, int lineno);

А теперь давайте рассмотрим работу SMC Parser в коде:

Рекомендую всегда создавать отдельную функцию для таких затей:
C-подобный:
ProcessConfigFile("configs/someconfigfile.cfg");
Ее нужно прописать в той части кода, где вы хотите пропарсить ваш конфиг файл.

А вот и сама функция:
C-подобный:
public void ProcessConfigFile(const char[] file)
{
    char ConfigFile[PLATFORM_MAX_PATH]; // Создаем нашу строку, где будет храниться реальный путь к файлу
    BuildPath(Path_SM, ConfigFile, sizeof(ConfigFile), file); // Записываем наш путь в строку
    if (!FileExists(ConfigFile)) // Проверяем, что файл валиден и существует
    {
        // Файл не существует, останавливаем плагин и пишем об ошибке!
        LogError("[SM] Plugin is not running! Could not find file %s", ConfigFile);
        SetFailState("Could not find file %s", ConfigFile);
    }
    // Наша новая функция будет возвращать bool
    else if (!ParseConfigFile(ConfigFile)) // Проверяем, что все прошло без ошибок
    {
        // У нас какая-то ошибка, останавливаем плагин и пишем об ошибке!
        LogError("[SM] Plugin is not running! Failed to parse %s", ConfigFile);
        SetFailState("Parse error on file %s", ConfigFile);
    }
}

Вы могли заметить новую функцию ParseConfigFile(sConfigFile), ее сейчас и напишем:
C-подобный:
public bool ParseConfigFile(const char[] file)
{
    SMCParser hParser = new SMCParser(); // Создаем SMCParser
    char error[128]; // Создаем строку для хранения ошибки
    int line = 0, col = 0; // Создаем буффера для хранения строки и колонки
  
    // Устанавливаем все необходимые каллбеки
    hParser.OnEnterSection = Config_NewSection;
    hParser.OnLeaveSection = Config_EndSection;
    hParser.OnKeyValue = Config_KeyValue;
    hParser.OnEnd = Config_End;
  
    // Запускаем SMCParser, получаем его результат и закрываем
    SMCError result = SMC_ParseFile(hParser, file, line, col);
    CloseHandle(hParser);

    // Убеждаемся, что все без ошибок прошло
    if (result != SMCError_Okay)
    {
        // В случае ошибки просто пишем об этом в еррор лог
        SMC_GetErrorString(result, error, sizeof(error));
        LogError("%s on line %d, col %d of %s", error, line, col, file);
    }
  
    // Ну и возвращаем true/false в зависимости от успешности парсера
    return (result == SMCError_Okay);
}

Ну и остались наши дорогие каллбеки:
C-подобный:
public SMCResult Config_NewSection(Handle parser, const char[] section, bool quotes) // Каллбек новой секции
{
    // Давайте убедимся, что наша секция не является самой главной (как это есть в KV)
    if (StrEqual(section, "top_sectioname"))
    {
        return SMCParse_Continue;
    }
    // Тут можно дальше продолжать проверки...
    // У нас есть название секции (section) и наличие кавычек (quotes)
    return SMCParse_Continue;
}

public SMCResult Config_KeyValue(Handle parser, char[] key, char[] value, bool key_quotes, bool value_quotes) // Каллбек нового ключа
{
    // В данном каллбеке у нас есть сам ключ (key) и значение к нему (value)
    // А также наличие кавычек в обоих случаях
  
    if(StrEqual(key, "somekey", false))
    {
        // Можно тут что-нибудь сделать
        PushArrayString(hSomeArray, value);
    }
    else if(StrEqual(key, "someotherkey", false))
    {
        // Или тут
    }
  
    return SMCParse_Continue;
}

public SMCResult Config_EndSection(Handle parser) // Калбек конца секции
{
    return SMCParse_Continue;
}

public Config_End(Handle parser, bool halted, bool failed) // Калбек окончания парсера
{
    // Проверяем все ли хорошо
    if (failed)
    {
        // В случае неудачи останавливаем работу плагина
        SetFailState("Plugin configuration error");
    }
}

Я надеюсь, что это статья была полезна для вас)
С радостью прочитаю всю конструктивную критику, может дополнить статью вашими силами)
 
Последнее редактирование:

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #2
1) SMC Parser не может обрабатывать отдельные записи (то есть ключ без значения).
2) SMC Parser поддерживает многострочные комментарии (/* комментарий /*), когда KeyValues - нет.
3) SMC Parser прочитает все категории и ключи по порядку, когда в KeyValues нельзя узнать название всех ключей в секции.
1. Это как? а кв может?
3. Ну а пройтись по ним циклом и получить их все? Парсер разве не это делает (просто с вызовом каллбеков) ?



Я бы добавил пример конфига с реальными данными и пример кода который его спарсит чтобы наглядо видеть что и куда попадает
 

Nico Yazawa

Бывший MrChester =(
Сообщения
326
Реакции
303
1. Это как? а кв может?
Основывался на этом, сам не проверял)
1598365449037.png


3. Ну а пройтись по ним циклом и получить их все? Парсер разве не это делает (просто с вызовом каллбеков) ?
Я знаю как через while пройтись по всем секциям, но разве это можно устроить с ключами?

Я бы добавил пример конфига с реальными данными и пример кода который его спарсит чтобы наглядо видеть что и куда попадает
Спасибо за идею, обязательно сделаю)
 

xstage

🏹
Сообщения
726
Реакции
754
К отличиям можно ещё отнести то, что SMC сохраняет регистр в отличии от KV, который может некоторые ключи вывести с неправильным регистром.
 

R1KO

fuck society
Сообщения
9,457
Реакции
7,786
  • Команда форума
  • #7
Ну ключ это ключ со значением, секция это сборище ключей, нет?)
Ну по сути ключ это секция, только она ключ к блоку, а он к значению.

К отличиям можно ещё отнести то, что SMC сохраняет регистр в отличии от KV, который может некоторые ключи вывести с неправильным регистром.
точно! как мы могли забыть самую большую боль всех времен и народов
 

Tomiks

Участник
Сообщения
419
Реакции
288
Ну ключ это ключ со значением, секция это сборище ключей, нет?)
C++:
void CreateMenuVip()
{
    char sColor[32], sSectionName[64];
    ColoredSmokeMenu = new Menu(MenuHandler_Colors);
    ColoredSmokeMenu.SetTitle("Выбор цвета дыма");

    g_kConfig.Rewind();
    if(g_kConfig.JumpToKey("Colors", false) && g_kConfig.GotoFirstSubKey(false))
    {
        do // вот здесь я начинаю циклом проходить по ключам и получать их название + значение
        {
            g_kConfig.GetSectionName(sSectionName, sizeof(sSectionName)); // название ключа
            g_kConfig.GetString(NULL_STRING, sColor, sizeof(sColor)); // значение ключа
            ColoredSmokeMenu.AddItem(sColor, sSectionName);
        } while(g_kConfig.GotoNextKey(false)); // следующий ключ
    }

    ColoredSmokeMenu.ExitBackButton = false;
    ColoredSmokeMenu.ExitButton = true;

    delete g_kConfig;
}
C-подобный:
"VIP_COLORED_SMOKE"
{
    "colors"
    {
        "Синий"        "0 0 255"
        "Зелёный"        "0 255 0"
        "Красный"        "255 0 0"
    }
}

После этого выведет меню типа:
1. Синий
2. Зелёный
3. Красный
 

rejchev

менеджер клоунов
Сообщения
1,669
Реакции
1,291
Можно добавить, к отличиям SMC, отсутствие возможности реврайта как таковой (пусть это и очевидно).
 

Nico Yazawa

Бывший MrChester =(
Сообщения
326
Реакции
303
C++:
void CreateMenuVip()
{
    char sColor[32], sSectionName[64];
    ColoredSmokeMenu = new Menu(MenuHandler_Colors);
    ColoredSmokeMenu.SetTitle("Выбор цвета дыма");

    g_kConfig.Rewind();
    if(g_kConfig.JumpToKey("Colors", false) && g_kConfig.GotoFirstSubKey(false))
    {
        do // вот здесь я начинаю циклом проходить по ключам и получать их название + значение
        {
            g_kConfig.GetSectionName(sSectionName, sizeof(sSectionName)); // название ключа
            g_kConfig.GetString(NULL_STRING, sColor, sizeof(sColor)); // значение ключа
            ColoredSmokeMenu.AddItem(sColor, sSectionName);
        } while(g_kConfig.GotoNextKey(false)); // следующий ключ
    }

    ColoredSmokeMenu.ExitBackButton = false;
    ColoredSmokeMenu.ExitButton = true;

    delete g_kConfig;
}
C-подобный:
"VIP_COLORED_SMOKE"
{
    "colors"
    {
        "Синий"        "0 0 255"
        "Зелёный"        "0 255 0"
        "Красный"        "255 0 0"
    }
}

После этого выведет меню типа:
1. Синий
2. Зелёный
3. Красный
Пасиба, не знал такого)
Сообщения автоматически склеены:

Подправил 3 и 4 пункты отличий:

3) SMC Parser прочитает все категории и ключи по порядку, когда в KeyValues нельзя узнать значение двух ключей с одинаковым названием.
4) SMC Parser не поддерживает изменения самого конфиг файла, когда в KeyValues это реализуемо.
 
Сверху Снизу